Download | Plain Text | No Line Numbers


  1. This patch is based on http://kyberdigi.cz/projects/execdir/ but instead trying
  2. to sanitize the exec command string inside PHP this job is handled by bash and
  3. a special restricted PHP-mode which strips off all path informations.
  4.  
  5. This patch depends on:
  6. - bash restricted php mode patch
  7. https://manuel.mausz.at/coding/patches/php/bash-4.2-restricted-php.patch
  8.  
  9. No warranty!
  10. --- a/ext/standard/exec.c 2026-10-06 18:53:18.000000000 +0200
  11. +++ b/ext/standard/exec.c 2026-10-15 12:01:08.035681675 +0200
  12. @@ -112,17 +112,28 @@
  13. FILE *fp;
  14. char *buf;
  15. int pclose_return;
  16. - char *b, *d=NULL;
  17. + char *cmd_p, *b, *d=NULL;
  18. php_stream *stream;
  19. size_t buflen, bufl = 0;
  20. #if PHP_SIGCHILD
  21. void (*sig_handler)(int) = signal(SIGCHLD, SIG_DFL);
  22. #endif
  23.  
  24. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  25. + zend_string *cmd_str = zend_string_init(cmd, strlen(cmd), 0);
  26. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  27. + spprintf(&d, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  28. + zend_string_release(cmd_esc);
  29. + zend_string_release(cmd_str);
  30. + cmd_p = d;
  31. + } else {
  32. + cmd_p = cmd;
  33. + }
  34. +
  35. #ifdef PHP_WIN32
  36. - fp = VCWD_POPEN(cmd, "rb");
  37. + fp = VCWD_POPEN(cmd_p, "rb");
  38. #else
  39. - fp = VCWD_POPEN(cmd, "r");
  40. + fp = VCWD_POPEN(cmd_p, "r");
  41. #endif
  42. if (!fp) {
  43. php_error_docref(NULL, E_WARNING, "Unable to fork [%s]", cmd);
  44. @@ -496,7 +507,7 @@
  45. PHP_FUNCTION(shell_exec)
  46. {
  47. FILE *in;
  48. - char *command;
  49. + char *command, *command_p;
  50. size_t command_len;
  51. zend_string *ret;
  52. php_stream *stream;
  53. @@ -510,14 +521,26 @@
  54. RETURN_THROWS();
  55. }
  56.  
  57. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  58. + zend_string *cmd_str = zend_string_init(command, command_len, 0);
  59. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  60. + spprintf(&command_p, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  61. + zend_string_release(cmd_esc);
  62. + zend_string_release(cmd_str);
  63. + } else {
  64. + command_p = estrdup(command);
  65. + }
  66. +
  67. #ifdef PHP_WIN32
  68. - if ((in=VCWD_POPEN(command, "rt"))==NULL) {
  69. + if ((in=VCWD_POPEN(command_p, "rt"))==NULL) {
  70. #else
  71. - if ((in=VCWD_POPEN(command, "r"))==NULL) {
  72. + if ((in=VCWD_POPEN(command_p, "r"))==NULL) {
  73. #endif
  74. php_error_docref(NULL, E_WARNING, "Unable to execute '%s'", command);
  75. + efree(command_p);
  76. RETURN_FALSE;
  77. }
  78. + efree(command_p);
  79.  
  80. stream = php_stream_fopen_from_pipe(in, "rb");
  81. ret = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0);
  82. --- a/ext/standard/file.c 2026-10-06 18:53:18.000000000 +0200
  83. +++ b/ext/standard/file.c 2026-10-15 12:01:08.036634835 +0200
  84. @@ -26,6 +26,7 @@
  85. #include "ext/standard/basic_functions.h"
  86. #include "php_ini.h"
  87. #include "zend_smart_str.h"
  88. +#include "ext/standard/exec.h"
  89.  
  90. #include <stdio.h>
  91. #include <stdlib.h>
  92. @@ -834,7 +835,18 @@
  93. RETURN_THROWS();
  94. }
  95.  
  96. - fp = VCWD_POPEN(command, posix_mode);
  97. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  98. + zend_string *cmd_str = zend_string_init(command, command_len, 0);
  99. + zend_string *cmd_esc = php_escape_shell_arg(cmd_str);
  100. + char *tmp;
  101. + spprintf(&tmp, 0, "PATH=%s /bin/bash --php -rc %s", PG(exec_dir), ZSTR_VAL(cmd_esc));
  102. + fp = VCWD_POPEN(tmp, posix_mode);
  103. + efree(tmp);
  104. + zend_string_release(cmd_esc);
  105. + zend_string_release(cmd_str);
  106. + } else {
  107. + fp = VCWD_POPEN(command, posix_mode);
  108. + }
  109. if (!fp) {
  110. php_error_docref(NULL, E_WARNING, "%s", strerror(errno));
  111. efree(posix_mode);
  112. --- a/ext/standard/filestat.c 2026-10-06 18:53:18.000000000 +0200
  113. +++ b/ext/standard/filestat.c 2026-10-15 12:01:08.037176106 +0200
  114. @@ -728,6 +728,7 @@
  115. zend_stat_t *stat_sb = &ssb.sb;
  116. int flags = 0, rmask=S_IROTH, wmask=S_IWOTH, xmask=S_IXOTH; /* access rights defaults to other */
  117. const char *local = NULL;
  118. + char local_safe[MAXPATHLEN];
  119. php_stream_wrapper *wrapper = NULL;
  120.  
  121. ZEND_ASSERT(!zend_str_has_nul_byte(filename));
  122. @@ -738,7 +739,7 @@
  123. if (IS_ACCESS_CHECK(type)) {
  124. wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(filename), &local, 0);
  125. if (wrapper == &php_plain_files_wrapper) {
  126. - if (php_check_open_basedir(local)) {
  127. + if (type != FS_IS_X && php_check_open_basedir(local)) {
  128. RETURN_FALSE;
  129. }
  130.  
  131. @@ -764,8 +765,24 @@
  132. RETURN_BOOL(VCWD_ACCESS(file_path_to_check, R_OK) == 0);
  133. #endif
  134. #ifdef X_OK
  135. - case FS_IS_X:
  136. - RETURN_BOOL(VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  137. + case FS_IS_X: {
  138. + bool xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  139. + if (!xok && PG(exec_dir) && strlen(PG(exec_dir))) {
  140. + if (strstr(file_path_to_check, "..")) {
  141. + RETURN_FALSE;
  142. + } else {
  143. + char *b = strrchr(file_path_to_check, PHP_DIR_SEPARATOR);
  144. + if (b) {
  145. + snprintf(local_safe, MAXPATHLEN, "%s%s", PG(exec_dir), b);
  146. + } else {
  147. + snprintf(local_safe, MAXPATHLEN, "%s%c%s", PG(exec_dir), PHP_DIR_SEPARATOR, file_path_to_check);
  148. + }
  149. + file_path_to_check = (char *)&local_safe;
  150. + }
  151. + xok = (VCWD_ACCESS(file_path_to_check, X_OK) == 0);
  152. + }
  153. + RETURN_BOOL(xok);
  154. + }
  155. #endif
  156. }
  157. }
  158. --- a/ext/standard/proc_open.c 2026-10-06 18:53:18.000000000 +0200
  159. +++ b/ext/standard/proc_open.c 2026-10-15 12:01:08.037623011 +0200
  160. @@ -795,7 +795,17 @@
  161. zend_string *command = NULL;
  162. uint32_t i = 0;
  163.  
  164. - *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
  165. + *argv = safe_emalloc(sizeof(char *), num_elems + 1 + 5, 0);
  166. +
  167. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  168. + command = zend_string_init("/bin/bash", sizeof("/bin/bash") - 1, 0);
  169. + (*argv)[i++] = estrdup("/bin/bash");
  170. + (*argv)[i++] = estrdup("--php");
  171. + (*argv)[i++] = estrdup("-rc");
  172. + (*argv)[i++] = estrdup("exec \"$@\"");
  173. + (*argv)[i++] = estrdup("bash");
  174. + (*argv)[i] = NULL;
  175. + }
  176.  
  177. ZEND_HASH_FOREACH_VAL(array, arg_zv) {
  178. zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
  179. @@ -1294,8 +1304,37 @@
  180. }
  181. #endif
  182.  
  183. + HashTable *envpath = NULL;
  184. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  185. + zend_string *key;
  186. + zend_ulong idx;
  187. + zval *val;
  188. + uint32_t count = environment ? zend_hash_num_elements(environment) : 0;
  189. + ALLOC_HASHTABLE(envpath);
  190. + zend_hash_init(envpath, count + 1, NULL, ZVAL_PTR_DTOR, 0);
  191. + if (count) {
  192. + ZEND_HASH_FOREACH_KEY_VAL(environment, idx, key, val) {
  193. + if (key) {
  194. + zend_hash_add_new(envpath, key, val);
  195. + } else {
  196. + zend_hash_index_add_new(envpath, idx, val);
  197. + }
  198. + zval_add_ref(val);
  199. + } ZEND_HASH_FOREACH_END();
  200. + }
  201. + zval tmp;
  202. + ZVAL_STRING(&tmp, PG(exec_dir));
  203. + zend_hash_str_update(envpath, "PATH", strlen("PATH"), &tmp);
  204. + environment = envpath;
  205. + }
  206. +
  207. if (environment) {
  208. env = php_array_to_envp(environment);
  209. + if (envpath) {
  210. + zend_hash_destroy(envpath);
  211. + FREE_HASHTABLE(envpath);
  212. + environment = NULL;
  213. + }
  214. }
  215.  
  216. descriptors = alloc_descriptor_array(descriptorspec);
  217. @@ -1417,9 +1456,15 @@
  218. if (argv) {
  219. r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
  220. } else {
  221. - r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  222. - (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  223. - env.envarray ? env.envarray : environ);
  224. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  225. + r = posix_spawn(&child, "/bin/bash" , &factions, NULL,
  226. + (char * const[]) {"bash", "--php", "-rc", ZSTR_VAL(command_str), NULL},
  227. + env.envarray ? env.envarray : environ);
  228. + } else {
  229. + r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
  230. + (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
  231. + env.envarray ? env.envarray : environ);
  232. + }
  233. }
  234. posix_spawn_file_actions_destroy(&factions);
  235. if (r != 0) {
  236. @@ -1451,10 +1496,14 @@
  237. }
  238. execvp(ZSTR_VAL(command_str), argv);
  239. } else {
  240. - if (env.envarray) {
  241. - execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  242. + if (PG(exec_dir) && strlen(PG(exec_dir))) {
  243. + execle("/bin/bash", "bash", "--php", "-rc", ZSTR_VAL(command_str), NULL, env.envarray);
  244. } else {
  245. - execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  246. + if (env.envarray) {
  247. + execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
  248. + } else {
  249. + execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
  250. + }
  251. }
  252. }
  253.  
  254. --- a/main/main.c 2026-10-06 18:53:18.000000000 +0200
  255. +++ b/main/main.c 2026-10-15 12:01:08.038188195 +0200
  256. @@ -894,6 +894,7 @@
  257. STD_PHP_INI_ENTRY("syslog.facility", "LOG_USER", PHP_INI_SYSTEM, OnSetFacility, syslog_facility, php_core_globals, core_globals)
  258. STD_PHP_INI_ENTRY("syslog.ident", "php", PHP_INI_SYSTEM, OnUpdateString, syslog_ident, php_core_globals, core_globals)
  259. STD_PHP_INI_ENTRY("syslog.filter", "no-ctrl", PHP_INI_ALL, OnSetLogFilter, syslog_filter, php_core_globals, core_globals)
  260. + STD_PHP_INI_ENTRY("exec_dir", NULL, PHP_INI_SYSTEM, OnUpdateString, exec_dir, php_core_globals, core_globals)
  261. PHP_INI_END()
  262. /* }}} */
  263.  
  264. --- a/main/php_globals.h 2026-10-06 18:53:18.000000000 +0200
  265. +++ b/main/php_globals.h 2026-10-15 12:01:08.038589464 +0200
  266. @@ -170,6 +170,8 @@
  267. char *syslog_ident;
  268. zend_long syslog_filter;
  269. zend_long error_log_mode;
  270. +
  271. + char *exec_dir;
  272. };
  273.  
  274.  
  275. --- a/php.ini-development 2026-10-06 18:53:18.000000000 +0200
  276. +++ b/php.ini-development 2026-10-15 12:01:08.038845147 +0200
  277. @@ -317,6 +317,10 @@
  278. ; https://php.net/open-basedir
  279. ;open_basedir =
  280.  
  281. +; Only executables located in the exec_dir will be allowed to be executed
  282. +; via the exec family of functions.
  283. +exec_dir =
  284. +
  285. ; This directive allows you to disable certain functions.
  286. ; It receives a comma-delimited list of function names.
  287. ; https://php.net/disable-functions
  288. --- a/php.ini-production 2026-10-06 18:53:18.000000000 +0200
  289. +++ b/php.ini-production 2026-10-15 12:01:08.039287442 +0200
  290. @@ -317,6 +317,10 @@
  291. ; https://php.net/open-basedir
  292. ;open_basedir =
  293.  
  294. +; Only executables located in the exec_dir will be allowed to be executed
  295. +; via the exec family of functions.
  296. +exec_dir =
  297. +
  298. ; This directive allows you to disable certain functions.
  299. ; It receives a comma-delimited list of function names.
  300. ; https://php.net/disable-functions
  301.