diff options
| author | Ben Fuhrmannek | 2022-04-19 19:01:52 +0200 |
|---|---|---|
| committer | Ben Fuhrmannek | 2022-04-19 19:01:52 +0200 |
| commit | 490dc4e2a43a5f8256f0b00d5efd3c2a84487905 (patch) | |
| tree | 0be55ca90c58151d4a36a50c5ecbd8c69be39dc7 | |
| parent | bb59f4396a7dd51a2490a12fd2714c68e19f7b66 (diff) | |
extended checks for readonly_exec, enabled by default
introduced config options:
readonly_exec.extended_checks() or xchecks()
readonly_exec.no_extended_checks() or noxchecks()
| -rw-r--r-- | src/snuffleupagus.c | 2 | ||||
| -rw-r--r-- | src/sp_config.h | 1 | ||||
| -rw-r--r-- | src/sp_config_keywords.c | 7 | ||||
| -rw-r--r-- | src/sp_execute.c | 77 |
4 files changed, 73 insertions, 14 deletions
diff --git a/src/snuffleupagus.c b/src/snuffleupagus.c index ebb7f9c..c723199 100644 --- a/src/snuffleupagus.c +++ b/src/snuffleupagus.c | |||
| @@ -348,6 +348,7 @@ static void dump_config() { | |||
| 348 | add_assoc_bool(&arr, "readonly_exec.enable", SPCFG(readonly_exec).enable); | 348 | add_assoc_bool(&arr, "readonly_exec.enable", SPCFG(readonly_exec).enable); |
| 349 | add_assoc_bool(&arr, "readonly_exec.sim", SPCFG(readonly_exec).simulation); | 349 | add_assoc_bool(&arr, "readonly_exec.sim", SPCFG(readonly_exec).simulation); |
| 350 | ADD_ASSOC_ZSTR(&arr, SP_TOKEN_READONLY_EXEC "." SP_TOKEN_DUMP, SPCFG(readonly_exec).dump); | 350 | ADD_ASSOC_ZSTR(&arr, SP_TOKEN_READONLY_EXEC "." SP_TOKEN_DUMP, SPCFG(readonly_exec).dump); |
| 351 | add_assoc_bool(&arr, "readonly_exec.extended_checks", SPCFG(readonly_exec).extended_checks); | ||
| 351 | 352 | ||
| 352 | add_assoc_bool(&arr, "global_strict.enable", SPCFG(global_strict).enable); | 353 | add_assoc_bool(&arr, "global_strict.enable", SPCFG(global_strict).enable); |
| 353 | 354 | ||
| @@ -499,6 +500,7 @@ static PHP_INI_MH(OnUpdateConfiguration) { | |||
| 499 | 500 | ||
| 500 | // set some defaults | 501 | // set some defaults |
| 501 | SPCFG(show_old_php_warning) = true; | 502 | SPCFG(show_old_php_warning) = true; |
| 503 | SPCFG(readonly_exec).extended_checks = true; | ||
| 502 | 504 | ||
| 503 | char *str = new_value->val; | 505 | char *str = new_value->val; |
| 504 | 506 | ||
diff --git a/src/sp_config.h b/src/sp_config.h index 6d48240..e7d4e4d 100644 --- a/src/sp_config.h +++ b/src/sp_config.h | |||
| @@ -35,6 +35,7 @@ typedef struct { | |||
| 35 | typedef struct { | 35 | typedef struct { |
| 36 | bool enable; | 36 | bool enable; |
| 37 | bool simulation; | 37 | bool simulation; |
| 38 | bool extended_checks; | ||
| 38 | zend_string *dump; | 39 | zend_string *dump; |
| 39 | zend_string *textual_representation; | 40 | zend_string *textual_representation; |
| 40 | } sp_config_readonly_exec; | 41 | } sp_config_readonly_exec; |
diff --git a/src/sp_config_keywords.c b/src/sp_config_keywords.c index f7be731..e0e5166 100644 --- a/src/sp_config_keywords.c +++ b/src/sp_config_keywords.c | |||
| @@ -96,7 +96,7 @@ SP_PARSE_FN(parse_unserialize) { | |||
| 96 | } | 96 | } |
| 97 | 97 | ||
| 98 | SP_PARSE_FN(parse_readonly_exec) { | 98 | SP_PARSE_FN(parse_readonly_exec) { |
| 99 | bool enable = false, disable = false; | 99 | bool enable = false, disable = false, xchecks = false, no_xchecks = false; |
| 100 | sp_config_readonly_exec *cfg = (sp_config_readonly_exec*)retval; | 100 | sp_config_readonly_exec *cfg = (sp_config_readonly_exec*)retval; |
| 101 | 101 | ||
| 102 | sp_config_keyword config_keywords[] = { | 102 | sp_config_keyword config_keywords[] = { |
| @@ -105,6 +105,10 @@ SP_PARSE_FN(parse_readonly_exec) { | |||
| 105 | {parse_empty, SP_TOKEN_SIMULATION, &(cfg->simulation)}, | 105 | {parse_empty, SP_TOKEN_SIMULATION, &(cfg->simulation)}, |
| 106 | {parse_empty, SP_TOKEN_SIM, &(cfg->simulation)}, | 106 | {parse_empty, SP_TOKEN_SIM, &(cfg->simulation)}, |
| 107 | {parse_str, SP_TOKEN_DUMP, &(cfg->dump)}, | 107 | {parse_str, SP_TOKEN_DUMP, &(cfg->dump)}, |
| 108 | {parse_empty, "extended_checks", &(xchecks)}, | ||
| 109 | {parse_empty, "xchecks", &(xchecks)}, | ||
| 110 | {parse_empty, "no_extended_checks", &(no_xchecks)}, | ||
| 111 | {parse_empty, "noxchecks", &(no_xchecks)}, | ||
| 108 | {0, 0, 0}}; | 112 | {0, 0, 0}}; |
| 109 | 113 | ||
| 110 | SP_PROCESS_CONFIG_KEYWORDS_ERR(); | 114 | SP_PROCESS_CONFIG_KEYWORDS_ERR(); |
| @@ -112,6 +116,7 @@ SP_PARSE_FN(parse_readonly_exec) { | |||
| 112 | cfg->textual_representation = sp_get_textual_representation(parsed_rule); | 116 | cfg->textual_representation = sp_get_textual_representation(parsed_rule); |
| 113 | 117 | ||
| 114 | SP_SET_ENABLE_DISABLE(enable, disable, cfg->enable); | 118 | SP_SET_ENABLE_DISABLE(enable, disable, cfg->enable); |
| 119 | if (xchecks) { cfg->extended_checks = true; } else if (no_xchecks) { cfg->extended_checks = false; } | ||
| 115 | 120 | ||
| 116 | return SP_PARSER_STOP; | 121 | return SP_PARSER_STOP; |
| 117 | } | 122 | } |
diff --git a/src/sp_execute.c b/src/sp_execute.c index 9cf44e1..3a474a3 100644 --- a/src/sp_execute.c +++ b/src/sp_execute.c | |||
| @@ -1,4 +1,5 @@ | |||
| 1 | #include "php_snuffleupagus.h" | 1 | #include "php_snuffleupagus.h" |
| 2 | #include "ext/standard/php_string.h" | ||
| 2 | 3 | ||
| 3 | static void (*orig_execute_ex)(zend_execute_data *execute_data) = NULL; | 4 | static void (*orig_execute_ex)(zend_execute_data *execute_data) = NULL; |
| 4 | static void (*orig_zend_execute_internal)(zend_execute_data *execute_data, | 5 | static void (*orig_zend_execute_internal)(zend_execute_data *execute_data, |
| @@ -12,22 +13,72 @@ static zend_result (*orig_zend_stream_open)(zend_file_handle *handle) = NULL; | |||
| 12 | // FIXME handle symlink | 13 | // FIXME handle symlink |
| 13 | ZEND_COLD static inline void terminate_if_writable(const char *filename) { | 14 | ZEND_COLD static inline void terminate_if_writable(const char *filename) { |
| 14 | const sp_config_readonly_exec *config_ro_exec = &(SPCFG(readonly_exec)); | 15 | const sp_config_readonly_exec *config_ro_exec = &(SPCFG(readonly_exec)); |
| 16 | char *errmsg = "unknown access problem"; | ||
| 17 | |||
| 18 | // check write access | ||
| 15 | if (0 == access(filename, W_OK)) { | 19 | if (0 == access(filename, W_OK)) { |
| 16 | if (config_ro_exec->dump) { | 20 | errmsg = "Attempted execution of a writable file"; |
| 17 | sp_log_request(config_ro_exec->dump, config_ro_exec->textual_representation); | 21 | goto violation; |
| 18 | } | 22 | } |
| 19 | if (true == config_ro_exec->simulation) { | 23 | if (errno != EACCES) { |
| 20 | sp_log_simulation("readonly_exec", "Attempted execution of a writable file (%s)", filename); | 24 | goto err; |
| 21 | } else { | 25 | } |
| 22 | sp_log_drop("readonly_exec", "Attempted execution of a writable file (%s)", filename); | 26 | |
| 23 | } | 27 | // other checks are 'extended checks' that can be enabled/disabled via config |
| 28 | if (!config_ro_exec->extended_checks) { | ||
| 29 | return; | ||
| 30 | } | ||
| 31 | |||
| 32 | // check effective uid | ||
| 33 | struct stat buf; | ||
| 34 | if (0 != stat(filename, &buf)) { | ||
| 35 | goto err; | ||
| 36 | } | ||
| 37 | if (buf.st_uid == geteuid()) { | ||
| 38 | errmsg = "Attempted execution of file owned by process"; | ||
| 39 | goto violation; | ||
| 40 | } | ||
| 41 | |||
| 42 | // check write access on directory | ||
| 43 | char *dirname = estrndup(filename, strlen(filename)); | ||
| 44 | php_dirname(dirname, strlen(dirname)); | ||
| 45 | if (0 == access(dirname, W_OK)) { | ||
| 46 | errmsg = "Attempted execution of file in writable directory"; | ||
| 47 | efree(dirname); | ||
| 48 | goto violation; | ||
| 49 | } | ||
| 50 | if (errno != EACCES) { | ||
| 51 | efree(dirname); | ||
| 52 | goto err; | ||
| 53 | } | ||
| 54 | |||
| 55 | // check effecite uid of directory | ||
| 56 | if (0 != stat(dirname, &buf)) { | ||
| 57 | efree(dirname); | ||
| 58 | goto err; | ||
| 59 | } | ||
| 60 | efree(dirname); | ||
| 61 | if (buf.st_uid == geteuid()) { | ||
| 62 | errmsg = "Attempted execution of file in directory owned by process"; | ||
| 63 | goto violation; | ||
| 64 | } | ||
| 65 | |||
| 66 | // we would actually need to check all parent directories as well, but that task is left for other tools | ||
| 67 | return; | ||
| 68 | |||
| 69 | violation: | ||
| 70 | if (config_ro_exec->dump) { | ||
| 71 | sp_log_request(config_ro_exec->dump, config_ro_exec->textual_representation); | ||
| 72 | } | ||
| 73 | if (config_ro_exec->simulation) { | ||
| 74 | sp_log_simulation("readonly_exec", "%s (%s)", errmsg, filename); | ||
| 24 | } else { | 75 | } else { |
| 25 | if (EACCES != errno) { | 76 | sp_log_drop("readonly_exec", "%s (%s)", errmsg, filename); |
| 26 | // LCOV_EXCL_START | ||
| 27 | sp_log_err("Writable execution", "Error while accessing %s: %s", filename, strerror(errno)); | ||
| 28 | // LCOV_EXCL_STOP | ||
| 29 | } | ||
| 30 | } | 77 | } |
| 78 | return; | ||
| 79 | |||
| 80 | err: | ||
| 81 | sp_log_err("readonly_exec", "Error while accessing %s: %s", filename, strerror(errno)); | ||
| 31 | } | 82 | } |
| 32 | 83 | ||
| 33 | inline static void is_builtin_matching( | 84 | inline static void is_builtin_matching( |
