From 4c4e1092334c4c01134b576c9fbb98f49c7e9e9d Mon Sep 17 00:00:00 2001 From: Ravin Perera <33562092+ravinsp@users.noreply.github.com> Date: Mon, 15 Feb 2021 16:36:10 +0530 Subject: [PATCH] Contract process resource limits. (#243) --- examples/c_contract/hotpocket_contract.h | 40 +++++++++++++++++---- examples/nodejs_contract/hp-contract-lib.js | 3 +- src/conf.cpp | 6 ++++ src/conf.hpp | 3 ++ src/pchheader.hpp | 1 + src/sc/sc.cpp | 36 ++++++++++++++++++- src/sc/sc.hpp | 2 ++ 7 files changed, 83 insertions(+), 8 deletions(-) diff --git a/examples/c_contract/hotpocket_contract.h b/examples/c_contract/hotpocket_contract.h index ee4ed8e5..eb62b3de 100644 --- a/examples/c_contract/hotpocket_contract.h +++ b/examples/c_contract/hotpocket_contract.h @@ -138,6 +138,9 @@ struct hp_round_limits_config size_t user_input_bytes; size_t user_output_bytes; size_t npl_output_bytes; + size_t proc_cpu_seconds; + size_t proc_mem_bytes; + size_t proc_ofd_count; }; struct hp_config @@ -505,7 +508,8 @@ int hp_update_config(const struct hp_config *config) if (!config->npl || strlen(config->npl) == 0 || (strcmp(config->npl, "public") != 0 && strcmp(config->npl, "private")) != 0) __HP_UPDATE_CONFIG_ERROR("Invalid npl flag. Valid values: public|private"); - if (config->round_limits.user_input_bytes < 0 || config->round_limits.user_output_bytes < 0 || config->round_limits.npl_output_bytes < 0) + if (config->round_limits.user_input_bytes < 0 || config->round_limits.user_output_bytes < 0 || config->round_limits.npl_output_bytes < 0 || + config->round_limits.proc_cpu_seconds < 0 || config->round_limits.proc_mem_bytes < 0 || config->round_limits.proc_ofd_count < 0) __HP_UPDATE_CONFIG_ERROR("Invalid round limits."); const int fd = open(PATCH_FILE_PATH, O_RDWR); @@ -634,7 +638,8 @@ int __hp_write_to_patch_file(const int fd, const struct hp_config *config) // Top-level field values. - const char *json_string = " \"bin_path\": \"%s\",\n \"bin_args\": \"%s\",\n \"roundtime\": %s,\n \"consensus\": \"%s\",\n \"npl\": \"%s\",\n"; + const char *json_string = " \"bin_path\": \"%s\",\n \"bin_args\": \"%s\",\n \"roundtime\": %s,\n" + " \"consensus\": \"%s\",\n \"npl\": \"%s\",\n"; char roundtime_str[16]; sprintf(roundtime_str, "%d", config->roundtime); @@ -656,16 +661,27 @@ int __hp_write_to_patch_file(const int fd, const struct hp_config *config) // Round limits field valies. - const char *round_limits_json = " \"round_limits\": {\n \"user_input_bytes\": %s,\n \"user_output_bytes\": %s,\n \"npl_output_bytes\": %s\n }\n}"; + const char *round_limits_json = " \"round_limits\": {\n" + " \"user_input_bytes\": %s,\n \"user_output_bytes\": %s,\n \"npl_output_bytes\": %s,\n" + " \"proc_cpu_seconds\": %s,\n \"proc_mem_bytes\": %s,\n \"proc_ofd_count\": %s\n }\n}"; + + char user_input_bytes_str[20], user_output_bytes_str[20], npl_output_bytes_str[20], + proc_cpu_seconds_str[20], proc_mem_bytes_str[20], proc_ofd_count_str[20]; - char user_input_bytes_str[20], user_output_bytes_str[20], npl_output_bytes_str[20]; sprintf(user_input_bytes_str, "%" PRIu64, config->round_limits.user_input_bytes); sprintf(user_output_bytes_str, "%" PRIu64, config->round_limits.user_output_bytes); sprintf(npl_output_bytes_str, "%" PRIu64, config->round_limits.npl_output_bytes); - const size_t round_limits_json_len = 119 + strlen(user_input_bytes_str) + strlen(user_output_bytes_str) + strlen(npl_output_bytes_str); + sprintf(proc_cpu_seconds_str, "%" PRIu64, config->round_limits.proc_cpu_seconds); + sprintf(proc_mem_bytes_str, "%" PRIu64, config->round_limits.proc_mem_bytes); + sprintf(proc_ofd_count_str, "%" PRIu64, config->round_limits.proc_ofd_count); + + const size_t round_limits_json_len = 205 + strlen(user_input_bytes_str) + strlen(user_output_bytes_str) + strlen(npl_output_bytes_str) + + strlen(proc_cpu_seconds_str) + strlen(proc_mem_bytes_str) + strlen(proc_ofd_count_str); char round_limits_buf[round_limits_json_len]; - sprintf(round_limits_buf, round_limits_json, user_input_bytes_str, user_output_bytes_str, npl_output_bytes_str); + sprintf(round_limits_buf, round_limits_json, + user_input_bytes_str, user_output_bytes_str, npl_output_bytes_str, + proc_cpu_seconds_str, proc_mem_bytes_str, proc_ofd_count_str); iov_vec[4].iov_base = round_limits_buf; iov_vec[4].iov_len = round_limits_json_len; @@ -769,6 +785,18 @@ void __hp_populate_patch_from_json_object(struct hp_config *config, const struct { __HP_ASSIGN_UINT64(config->round_limits.npl_output_bytes, sub_ele); } + else if (strcmp(sub_ele->name->string, "proc_cpu_seconds") == 0) + { + __HP_ASSIGN_UINT64(config->round_limits.proc_cpu_seconds, sub_ele); + } + else if (strcmp(sub_ele->name->string, "proc_mem_bytes") == 0) + { + __HP_ASSIGN_UINT64(config->round_limits.proc_mem_bytes, sub_ele); + } + else if (strcmp(sub_ele->name->string, "proc_ofd_count") == 0) + { + __HP_ASSIGN_UINT64(config->round_limits.proc_ofd_count, sub_ele); + } sub_ele = sub_ele->next; } while (sub_ele); } diff --git a/examples/nodejs_contract/hp-contract-lib.js b/examples/nodejs_contract/hp-contract-lib.js index 19649f89..bfdad449 100644 --- a/examples/nodejs_contract/hp-contract-lib.js +++ b/examples/nodejs_contract/hp-contract-lib.js @@ -144,7 +144,8 @@ class PatchConfig { throw "Invalid consensus flag configured in patch file. Valid values: public|private"; if (config.npl != "public" && config.npl != "private") throw "Invalid npl flag configured in patch file. Valid values: public|private"; - if (config.round_limits.user_input_bytes < 0 || config.round_limits.user_output_bytes < 0 || config.round_limits.npl_output_bytes < 0) + if (config.round_limits.user_input_bytes < 0 || config.round_limits.user_output_bytes < 0 || config.round_limits.npl_output_bytes < 0 || + config.round_limits.proc_cpu_seconds < 0 || config.round_limits.proc_mem_bytes < 0 || config.round_limits.proc_ofd_count < 0) throw "Invalid round limits."; } } diff --git a/src/conf.cpp b/src/conf.cpp index 6f3fee60..b5f88c55 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -859,6 +859,9 @@ namespace conf round_limits.insert_or_assign("user_input_bytes", contract.round_limits.user_input_bytes); round_limits.insert_or_assign("user_output_bytes", contract.round_limits.user_output_bytes); round_limits.insert_or_assign("npl_output_bytes", contract.round_limits.npl_output_bytes); + round_limits.insert_or_assign("proc_cpu_seconds", contract.round_limits.proc_cpu_seconds); + round_limits.insert_or_assign("proc_mem_bytes", contract.round_limits.proc_mem_bytes); + round_limits.insert_or_assign("proc_ofd_count", contract.round_limits.proc_ofd_count); jdoc.insert_or_assign("round_limits", round_limits); } @@ -945,6 +948,9 @@ namespace conf contract.round_limits.user_input_bytes = jdoc["round_limits"]["user_input_bytes"].as(); contract.round_limits.user_output_bytes = jdoc["round_limits"]["user_output_bytes"].as(); contract.round_limits.npl_output_bytes = jdoc["round_limits"]["npl_output_bytes"].as(); + contract.round_limits.proc_cpu_seconds = jdoc["round_limits"]["proc_cpu_seconds"].as(); + contract.round_limits.proc_mem_bytes = jdoc["round_limits"]["proc_mem_bytes"].as(); + contract.round_limits.proc_ofd_count = jdoc["round_limits"]["proc_ofd_count"].as(); } catch (const std::exception &e) { diff --git a/src/conf.hpp b/src/conf.hpp index 18a3f8c6..48026fe7 100644 --- a/src/conf.hpp +++ b/src/conf.hpp @@ -90,6 +90,9 @@ namespace conf size_t user_input_bytes = 0; // Max contract input bytes per user per round. size_t user_output_bytes = 0; // Max contract output bytes per user per round. size_t npl_output_bytes = 0; // Max npl output bytes per round. + size_t proc_cpu_seconds = 0; // Max CPU time the contract process can consume. + size_t proc_mem_bytes = 0; // Max memory the contract process can allocate. + size_t proc_ofd_count = 0; // Max no. of open file descriptors the contract process can allocate. }; struct contract_config diff --git a/src/pchheader.hpp b/src/pchheader.hpp index 4b623a28..4c59dfc0 100644 --- a/src/pchheader.hpp +++ b/src/pchheader.hpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include diff --git a/src/sc/sc.cpp b/src/sc/sc.cpp index 48cb06cc..ae6f357e 100644 --- a/src/sc/sc.cpp +++ b/src/sc/sc.cpp @@ -98,6 +98,13 @@ namespace sc // Set up the process environment and overlay the contract binary program with execv(). + // Set process resource limits. + if (set_process_rlimits() == -1) + { + std::cerr << errno << ": Failed to set contract process resource limits." << (ctx.args.readonly ? " (rdonly)" : "") << "\n"; + exit(1); + } + // Close all fds unused by SC process. close_unused_fds(ctx, false); @@ -153,6 +160,33 @@ namespace sc return ret; } + int set_process_rlimits() + { + rlimit lim; + if (conf::cfg.contract.round_limits.proc_cpu_seconds > 0) + { + lim.rlim_cur = lim.rlim_max = conf::cfg.contract.round_limits.proc_cpu_seconds; + if (setrlimit(RLIMIT_CPU, &lim) == -1) + return -1; + } + + if (conf::cfg.contract.round_limits.proc_mem_bytes > 0) + { + lim.rlim_cur = lim.rlim_max = conf::cfg.contract.round_limits.proc_mem_bytes; + if (setrlimit(RLIMIT_DATA, &lim) == -1) + return -1; + } + + if (conf::cfg.contract.round_limits.proc_ofd_count > 0) + { + lim.rlim_cur = lim.rlim_max = conf::cfg.contract.round_limits.proc_ofd_count; + if (setrlimit(RLIMIT_NOFILE, &lim) == -1) + return -1; + } + + return 0; + } + /** * Checks whether the contract process has exited. * @param ctx Contract execution context. @@ -185,7 +219,7 @@ namespace sc } else { - LOG_ERROR << "Contract process" << (ctx.args.readonly ? " (rdonly)" : "") << " ended with code " << WEXITSTATUS(scstatus); + LOG_ERROR << "Contract process" << (ctx.args.readonly ? " (rdonly)" : "") << " ended prematurely with code " << WEXITSTATUS(scstatus); return -1; } } diff --git a/src/sc/sc.hpp b/src/sc/sc.hpp index b34231f9..8dd9ab5f 100644 --- a/src/sc/sc.hpp +++ b/src/sc/sc.hpp @@ -143,6 +143,8 @@ namespace sc //------Internal-use functions for this namespace. + int set_process_rlimits(); + int check_contract_exited(execution_context &ctx, const bool block); int start_hpfs_session(execution_context &ctx);