/hphp/test/frameworks/Isolation.php

https://gitlab.com/Blueprint-Marketing/hhvm · PHP · 255 lines · 204 code · 28 blank · 23 comment · 24 complexity · 49cc3eb58aa8ff25db9d0a44bca2357d MD5 · raw file

  1. <?hh
  2. /**
  3. * Try to detect (and forbid) external dependencies.
  4. *
  5. * Use this by setting:
  6. * - hhvm.jit_enable_rename_function=true
  7. * - auto_prepend_file=/path/to/Isolation.php
  8. */
  9. class IsolationException extends Exception { }
  10. class Isolation {
  11. private static $allowedDirectories = Set { };
  12. private static $allowedFiles = Set { };
  13. public static function AllowDirectory(string $path) {
  14. self::$allowedDirectories[] = realpath($path);
  15. }
  16. public static function AllowFile(string $path) {
  17. self::$allowedFiles[] = realpath($path);
  18. }
  19. public static function CheckURL(string $url) {
  20. if (strpos($url, 'localhost') === false) {
  21. throw new IsolationException('Tried to open URL: '.$url);
  22. }
  23. }
  24. public static function CheckReadFile(string $path) {
  25. if (strpos($path, 'http://') === 0 || strpos($path, 'https://') === 0) {
  26. CheckURL($path);
  27. return;
  28. }
  29. }
  30. public static function CheckWriteFile(string $path) {
  31. $scheme = parse_url($path, PHP_URL_SCHEME);
  32. if ($scheme === 'vfs') {
  33. // vfsstream already provides isolation for unit tests, allow it
  34. return;
  35. } else if ($scheme === 'php') {
  36. // memory, temp, etc
  37. return;
  38. }
  39. $realpath = realpath($path);
  40. if ($realpath === false) {
  41. throw new IsolationException('Unable to confirm if path is safe: '.$path);
  42. }
  43. $path = $realpath;
  44. if (self::$allowedFiles->contains($path)) {
  45. return;
  46. }
  47. foreach (self::$allowedDirectories as $dir) {
  48. if (strpos($path, $dir.'/') === 0) {
  49. return;
  50. }
  51. }
  52. throw new IsolationException('Tried to write to file: '.$path);
  53. }
  54. public static function CheckExec($name, $obj, $args, $data, &$done) {
  55. if (preg_match(',(^|[/"\'])(node|git|hhvm)[ "\'],', $args[0])) {
  56. // Allow
  57. $done = false;
  58. return;
  59. }
  60. throw new IsolationException(
  61. 'Tried to '.$name.'() with args:'.var_export($args, true)
  62. );
  63. }
  64. }
  65. //////////
  66. // URLs //
  67. //////////
  68. fb_intercept(
  69. 'curl_init',
  70. function($name, $obj, $args, $data, &$done) {
  71. if (count($args) > 0) {
  72. Isolation::CheckURL($args[0]);
  73. }
  74. $done = false;
  75. }
  76. );
  77. fb_intercept(
  78. 'curl_setopt',
  79. function($name, $obj, $args, $data, &$done) {
  80. list ($ch, $option, $value) = $args;
  81. if ($option === CURLOPT_URL) {
  82. Isolation::CheckURL($value);
  83. }
  84. $done = false;
  85. }
  86. );
  87. fb_intercept(
  88. 'curl_setopt_array',
  89. function($name, $obj, $args, $data, &$done) {
  90. list ($ch, $options) = $args;
  91. foreach ($options as $opt => $value) {
  92. if ($opt === CURLOPT_URL) {
  93. Isolation::CheckURL($value);
  94. }
  95. }
  96. $done = false;
  97. }
  98. );
  99. //////////////////////////
  100. // Files - Whitelisting //
  101. //////////////////////////
  102. function sys_get_temp_dir_ISOLATION_WRAPPER() {
  103. // Make sure we don't just return '/tmp' or other easily-guessable location.
  104. static $dir = null;
  105. if ($dir === null) {
  106. $dir = ORIG_sys_get_temp_dir();
  107. $dir .= '/'.uniqid('test_isolation', true);
  108. mkdir($dir);
  109. }
  110. return $dir;
  111. }
  112. fb_rename_function('sys_get_temp_dir', 'ORIG_sys_get_temp_dir');
  113. fb_rename_function('sys_get_temp_dir_ISOLATION_WRAPPER', 'sys_get_temp_dir');
  114. Isolation::AllowDirectory(sys_get_temp_dir());
  115. function tempnam_ISOLATION_WRAPPER() {
  116. $path = call_user_func_array('ORIG_tempnam', func_get_args());
  117. Isolation::AllowFile($path);
  118. return $path;
  119. }
  120. fb_rename_function('tempnam', 'ORIG_tempnam');
  121. fb_rename_function('tempnam_ISOLATION_WRAPPER', 'tempnam');
  122. function time_ISOLATION_WRAPPER() {
  123. static $time;
  124. $org_time = ORIG_time();
  125. if ($time === null) {
  126. $time = $org_time;
  127. return $time;
  128. }
  129. if ($time >= $org_time) {
  130. ++$time;
  131. } else {
  132. $time = $org_time;
  133. }
  134. return $time;
  135. }
  136. fb_rename_function('time', 'ORIG_time');
  137. fb_rename_function('time_ISOLATION_WRAPPER', 'time');
  138. function microtime_ISOLATION_WRAPPER(bool $get_as_float = false) {
  139. static $time;
  140. list($msec, $sec) = explode(" ", ORIG_microtime());
  141. if ($time === null) {
  142. $time = $sec;
  143. } else if ($time >= $sec){
  144. ++$time;
  145. } else {
  146. $time = $sec;
  147. }
  148. if ($get_as_float)
  149. return ((float)$msec + (float)$time);
  150. else
  151. return "{$msec} {$time}";
  152. }
  153. fb_rename_function('microtime', 'ORIG_microtime');
  154. fb_rename_function('microtime_ISOLATION_WRAPPER', 'microtime');
  155. function date_ISOLATION_WRAPPER(?string $format, ?int $timestamp = null) {
  156. $timestamp = ($timestamp === null ? time() : $timestamp);
  157. return ORIG_date($format, $timestamp);
  158. }
  159. fb_rename_function('date', 'ORIG_date');
  160. fb_rename_function('date_ISOLATION_WRAPPER', 'date');
  161. function gmdate_ISOLATION_WRAPPER(?string $format, ?int $timestamp = null) {
  162. $timestamp = ($timestamp === null ? time() : $timestamp);
  163. return ORIG_gmdate($format, $timestamp);
  164. }
  165. fb_rename_function('gmdate', 'ORIG_gmdate');
  166. fb_rename_function('gmdate_ISOLATION_WRAPPER', 'gmdate');
  167. function strftime_ISOLATION_WRAPPER(?string $format, ?int $timestamp = null) {
  168. $timestamp = ($timestamp === null ? time() : $timestamp);
  169. return ORIG_strftime($format, $timestamp);
  170. }
  171. fb_rename_function('strftime', 'ORIG_strftime');
  172. fb_rename_function('strftime_ISOLATION_WRAPPER', 'strftime');
  173. function strtotime_ISOLATION_WRAPPER(?string $time, ?int $now = null) {
  174. $now = ($now === null ? time() : $now);
  175. return ORIG_strtotime($time, $now);
  176. }
  177. fb_rename_function('strtotime', 'ORIG_strtotime');
  178. fb_rename_function('strtotime_ISOLATION_WRAPPER', 'strtotime');
  179. function idate_ISOLATION_WRAPPER(?string $format, ?int $timestamp = null) {
  180. $timestamp = ($timestamp === null ? time() : $timestamp);
  181. return ORIG_idate($format, $timestamp);
  182. }
  183. fb_rename_function('idate', 'ORIG_idate');
  184. fb_rename_function('idate_ISOLATION_WRAPPER', 'idate');
  185. function getdate_ISOLATION_WRAPPER(?int $timestamp = null) {
  186. $timestamp = ($timestamp === null ? time() : $timestamp);
  187. return ORIG_getdate($timestamp);
  188. }
  189. fb_rename_function('getdate', 'ORIG_getdate');
  190. fb_rename_function('getdate_ISOLATION_WRAPPER', 'getdate');
  191. function localtime_ISOLATION_WRAPPER(?int $timestamp = null,
  192. bool $is_associative = false) {
  193. $timestamp = ($timestamp === null ? time() : $timestamp);
  194. return ORIG_localtime($timestamp, $is_associative);
  195. }
  196. fb_rename_function('localtime', 'ORIG_localtime');
  197. fb_rename_function('localtime_ISOLATION_WRAPPER', 'localtime');
  198. //////////////////////
  199. // Files - Checking //
  200. //////////////////////
  201. fb_intercept(
  202. 'file_get_contents',
  203. function($name, $obj, $args, $data, &$done) {
  204. Isolation::CheckReadFile($args[0]);
  205. $done = false;
  206. }
  207. );
  208. fb_intercept(
  209. 'fopen',
  210. function($name, $obj, $args, $data, &$done) {
  211. $mode = $args[1];
  212. if (strpbrk($mode, '+waxc')) {
  213. Isolation::CheckWriteFile($args[0]);
  214. } else {
  215. Isolation::CheckReadFile($args[0]);
  216. }
  217. $done = false;
  218. }
  219. );
  220. ///////////////////////////
  221. // exec()-like functions //
  222. ///////////////////////////
  223. fb_intercept('exec', class_meth('Isolation', 'CheckExec'));
  224. fb_intercept('popen', class_meth('Isolation', 'CheckExec'));
  225. fb_intercept('proc_open', class_meth('Isolation', 'CheckExec'));
  226. fb_intercept('shell_exec', class_meth('Isolation', 'CheckExec'));
  227. fb_intercept('system', class_meth('Isolation', 'CheckExec'));