[2591] | 1 | From 427d432a56df94d69a11cc438b08adb070615005 Mon Sep 17 00:00:00 2001 |
---|
| 2 | From: Alexander Chernyakhovsky <achernya@mit.edu> |
---|
| 3 | Date: Fri, 3 May 2013 21:38:58 -0400 |
---|
| 4 | Subject: [PATCH] Add scripts-specific support to suexec |
---|
[103] | 5 | |
---|
[2591] | 6 | This patch make suexec aware of static-cat, Scripts' tool to serve |
---|
| 7 | static content out of AFS. Specifically, this introduces a whitelist |
---|
| 8 | of extensions for which suexec is supposed to invoke static-cat as a |
---|
| 9 | content-handler. |
---|
[103] | 10 | |
---|
[2591] | 11 | Additionally, this patch also sets JAVA_TOOL_OPTIONS, to allow the JVM |
---|
| 12 | to start up in Scripts' limited memory environment. |
---|
| 13 | |
---|
| 14 | Furthermore, this patch deals with some of suexec's paranoia being |
---|
| 15 | incorrect in an AFS world, by ignoring some of the irrelevant stat |
---|
| 16 | results. |
---|
| 17 | |
---|
| 18 | Finally, add support for invoking php-cgi for php files, in a safe |
---|
| 19 | manner that will strip arguments passed by Apache to php-cgi. |
---|
| 20 | --- |
---|
| 21 | configure.in | 4 ++ |
---|
| 22 | support/suexec.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
---|
| 23 | 2 files changed, 173 insertions(+), 3 deletions(-) |
---|
| 24 | |
---|
| 25 | diff --git a/configure.in b/configure.in |
---|
| 26 | index 811aace..a95349f 100644 |
---|
| 27 | --- a/configure.in |
---|
| 28 | +++ b/configure.in |
---|
| 29 | @@ -721,6 +721,10 @@ AC_ARG_WITH(suexec-userdir, |
---|
[823] | 30 | APACHE_HELP_STRING(--with-suexec-userdir,User subdirectory),[ |
---|
| 31 | AC_DEFINE_UNQUOTED(AP_USERDIR_SUFFIX, "$withval", [User subdirectory] ) ] ) |
---|
| 32 | |
---|
| 33 | +AC_ARG_WITH(suexec-trusteddir, |
---|
| 34 | +APACHE_HELP_STRING(--with-suexec-trusteddir,Trusted SuExec directory),[ |
---|
| 35 | + AC_DEFINE_UNQUOTED(AP_TRUSTED_DIRECTORY, "$withval", [Trusted SuExec directory] ) ] ) |
---|
| 36 | + |
---|
| 37 | AC_ARG_WITH(suexec-docroot, |
---|
| 38 | APACHE_HELP_STRING(--with-suexec-docroot,SuExec root directory),[ |
---|
| 39 | AC_DEFINE_UNQUOTED(AP_DOC_ROOT, "$withval", [SuExec root directory] ) ] ) |
---|
[2591] | 40 | diff --git a/support/suexec.c b/support/suexec.c |
---|
| 41 | index 32e7320..3a4d802 100644 |
---|
| 42 | --- a/support/suexec.c |
---|
| 43 | +++ b/support/suexec.c |
---|
[842] | 44 | @@ -30,6 +30,9 @@ |
---|
[298] | 45 | * |
---|
| 46 | */ |
---|
| 47 | |
---|
[1590] | 48 | +#define STATIC_CAT_PATH "/usr/bin/static-cat" |
---|
[842] | 49 | +#define PHP_PATH "/usr/bin/php-cgi" |
---|
[298] | 50 | + |
---|
| 51 | #include "apr.h" |
---|
| 52 | #include "ap_config.h" |
---|
| 53 | #include "suexec.h" |
---|
[2591] | 54 | @@ -92,6 +95,7 @@ static const char *const safe_env_lst[] = |
---|
[1] | 55 | { |
---|
| 56 | /* variable name starts with */ |
---|
| 57 | "HTTP_", |
---|
| 58 | + "HTTPS_", |
---|
| 59 | "SSL_", |
---|
| 60 | |
---|
| 61 | /* variable name is */ |
---|
[2591] | 62 | @@ -268,9 +272,108 @@ static void clean_env(void) |
---|
[298] | 63 | environ = cleanenv; |
---|
| 64 | } |
---|
| 65 | |
---|
| 66 | +static const char *static_extensions[] = { |
---|
| 67 | + "html", |
---|
| 68 | + "css", |
---|
| 69 | + "gif", |
---|
| 70 | + "jpg", |
---|
| 71 | + "png", |
---|
| 72 | + "htm", |
---|
| 73 | + "jpeg", |
---|
| 74 | + "js", |
---|
| 75 | + "ico", |
---|
| 76 | + "xml", |
---|
| 77 | + "xsl", |
---|
| 78 | + "tiff", |
---|
| 79 | + "tif", |
---|
| 80 | + "tgz", |
---|
| 81 | + "tar", |
---|
| 82 | + "jar", |
---|
| 83 | + "zip", |
---|
| 84 | + "pdf", |
---|
| 85 | + "ps", |
---|
| 86 | + "doc", |
---|
| 87 | + "xls", |
---|
| 88 | + "ppt", |
---|
[1877] | 89 | + "dot", |
---|
| 90 | + "docx", |
---|
| 91 | + "dotx", |
---|
| 92 | + "docm", |
---|
| 93 | + "dotm", |
---|
| 94 | + "xlt", |
---|
| 95 | + "xla", |
---|
| 96 | + "xlsx", |
---|
| 97 | + "xltx", |
---|
| 98 | + "xlsm", |
---|
| 99 | + "xltm", |
---|
| 100 | + "xlam", |
---|
| 101 | + "xlsb", |
---|
| 102 | + "pot", |
---|
| 103 | + "pps", |
---|
| 104 | + "ppa", |
---|
| 105 | + "pptx", |
---|
| 106 | + "potx", |
---|
| 107 | + "ppsx", |
---|
| 108 | + "ppam", |
---|
| 109 | + "pptm", |
---|
| 110 | + "potm", |
---|
| 111 | + "ppsm", |
---|
[298] | 112 | + "swf", |
---|
| 113 | + "mp3", |
---|
| 114 | + "mov", |
---|
| 115 | + "wmv", |
---|
| 116 | + "mpg", |
---|
| 117 | + "mpeg", |
---|
| 118 | + "avi", |
---|
| 119 | + "il", |
---|
[315] | 120 | + "xhtml", |
---|
[618] | 121 | + "svg", |
---|
[944] | 122 | + "xaml", |
---|
| 123 | + "xap", |
---|
[1464] | 124 | + "wav", |
---|
| 125 | + "mid", |
---|
| 126 | + "midi", |
---|
[1785] | 127 | + "ttf", |
---|
| 128 | + "otf", |
---|
[1877] | 129 | + "odc", |
---|
| 130 | + "odb", |
---|
| 131 | + "odf", |
---|
| 132 | + "odg", |
---|
| 133 | + "otg", |
---|
| 134 | + "odi", |
---|
| 135 | + "odp", |
---|
| 136 | + "otp", |
---|
| 137 | + "ods", |
---|
| 138 | + "ots", |
---|
| 139 | + "odt", |
---|
| 140 | + "odm", |
---|
| 141 | + "ott", |
---|
| 142 | + "oth", |
---|
[298] | 143 | + NULL |
---|
| 144 | +}; |
---|
| 145 | + |
---|
| 146 | +static int is_static_extension(const char *file) |
---|
| 147 | +{ |
---|
| 148 | + const char *extension = strrchr(file, '.'); |
---|
| 149 | + const char **p; |
---|
| 150 | + if (extension == NULL) return 0; |
---|
| 151 | + for (p = static_extensions; *p; ++p) { |
---|
[1464] | 152 | + if (strcasecmp(extension + 1, *p) == 0) return 1; |
---|
[298] | 153 | + } |
---|
| 154 | + return 0; |
---|
| 155 | +} |
---|
| 156 | + |
---|
[842] | 157 | +static int is_php_extension(const char *file) |
---|
| 158 | +{ |
---|
| 159 | + const char *extension = strrchr(file, '.'); |
---|
| 160 | + if (extension == NULL) return 0; |
---|
| 161 | + return strcmp(extension + 1, "php") == 0; |
---|
| 162 | +} |
---|
| 163 | + |
---|
[298] | 164 | int main(int argc, char *argv[]) |
---|
| 165 | { |
---|
| 166 | int userdir = 0; /* ~userdir flag */ |
---|
[823] | 167 | + int trusteddir = 0; /* TRUSTED_DIRECTORY flag */ |
---|
| 168 | uid_t uid; /* user information */ |
---|
| 169 | gid_t gid; /* target group placeholder */ |
---|
| 170 | char *target_uname; /* target user name */ |
---|
[2591] | 171 | @@ -290,6 +393,7 @@ int main(int argc, char *argv[]) |
---|
[1169] | 172 | * Start with a "clean" environment |
---|
| 173 | */ |
---|
| 174 | clean_env(); |
---|
| 175 | + setenv("JAVA_TOOL_OPTIONS", "-Xmx128M", 1); /* scripts.mit.edu local hack */ |
---|
[1259] | 176 | |
---|
[1169] | 177 | /* |
---|
[2591] | 178 | * Check existence/validity of the UID of the user |
---|
| 179 | @@ -373,6 +477,20 @@ int main(int argc, char *argv[]) |
---|
[823] | 180 | #endif /*_OSD_POSIX*/ |
---|
| 181 | |
---|
| 182 | /* |
---|
| 183 | + * First check if this is an absolute path to the directory |
---|
| 184 | + * of trusted executables. These are supposed to be security |
---|
| 185 | + * audited to check parameters and validity on their own... |
---|
| 186 | + */ |
---|
| 187 | + if (strstr(cmd, AP_TRUSTED_DIRECTORY) == cmd) { |
---|
| 188 | + if (strstr(cmd, "/../") != NULL) { |
---|
| 189 | + log_err("invalid command (%s)\n", cmd); |
---|
| 190 | + exit(104); |
---|
| 191 | + } |
---|
| 192 | + trusteddir = 1; |
---|
| 193 | + goto TRUSTED_DIRECTORY; |
---|
| 194 | + } |
---|
| 195 | + |
---|
| 196 | + /* |
---|
| 197 | * Check for a leading '/' (absolute path) in the command to be executed, |
---|
| 198 | * or attempts to back up out of the current directory, |
---|
| 199 | * to protect against attacks. If any are |
---|
[2591] | 200 | @@ -394,6 +512,7 @@ int main(int argc, char *argv[]) |
---|
[823] | 201 | userdir = 1; |
---|
| 202 | } |
---|
| 203 | |
---|
| 204 | +TRUSTED_DIRECTORY: |
---|
| 205 | /* |
---|
| 206 | * Error out if the target username is invalid. |
---|
| 207 | */ |
---|
[2591] | 208 | @@ -482,7 +601,7 @@ int main(int argc, char *argv[]) |
---|
[103] | 209 | * Error out if attempt is made to execute as root or as |
---|
| 210 | * a UID less than AP_UID_MIN. Tsk tsk. |
---|
| 211 | */ |
---|
| 212 | - if ((uid == 0) || (uid < AP_UID_MIN)) { |
---|
[1474] | 213 | + if ((uid == 0) || (uid < AP_UID_MIN && uid != 102)) { /* uid 102 = signup */ |
---|
[2591] | 214 | log_err("cannot run as forbidden uid (%lu/%s)\n", (unsigned long)uid, cmd); |
---|
[103] | 215 | exit(107); |
---|
| 216 | } |
---|
[2591] | 217 | @@ -514,6 +633,7 @@ int main(int argc, char *argv[]) |
---|
| 218 | log_err("failed to setuid (%lu: %s)\n", (unsigned long)uid, cmd); |
---|
[103] | 219 | exit(110); |
---|
| 220 | } |
---|
[908] | 221 | + setenv("HOME", target_homedir, 1); |
---|
[103] | 222 | |
---|
| 223 | /* |
---|
| 224 | * Get the current working directory, as well as the proper |
---|
[2591] | 225 | @@ -536,6 +656,21 @@ int main(int argc, char *argv[]) |
---|
[823] | 226 | log_err("cannot get docroot information (%s)\n", target_homedir); |
---|
| 227 | exit(112); |
---|
[1] | 228 | } |
---|
[823] | 229 | + size_t expected_len = strlen(target_homedir)+1+strlen(AP_USERDIR_SUFFIX)+1; |
---|
| 230 | + char *expected = malloc(expected_len); |
---|
| 231 | + snprintf(expected, expected_len, "%s/%s", target_homedir, AP_USERDIR_SUFFIX); |
---|
| 232 | + if (strncmp(cwd, expected, expected_len-1) != 0) { |
---|
| 233 | + log_err("error: file's directory not a subdirectory of user's home directory (%s, %s)\n", cwd, expected); |
---|
| 234 | + exit(114); |
---|
| 235 | + } |
---|
| 236 | + } |
---|
| 237 | + else if (trusteddir) { |
---|
| 238 | + if (((chdir(AP_TRUSTED_DIRECTORY)) != 0) || |
---|
| 239 | + ((getcwd(dwd, AP_MAXPATH)) == NULL) | |
---|
| 240 | + ((chdir(cwd)) != 0)) { |
---|
| 241 | + log_err("cannot get docroot information (%s)\n", AP_TRUSTED_DIRECTORY); |
---|
| 242 | + exit(112); |
---|
| 243 | + } |
---|
[1] | 244 | } |
---|
[823] | 245 | else { |
---|
| 246 | if (((chdir(AP_DOC_ROOT)) != 0) || |
---|
[2591] | 247 | @@ -562,15 +697,17 @@ int main(int argc, char *argv[]) |
---|
[1] | 248 | /* |
---|
| 249 | * Error out if cwd is writable by others. |
---|
| 250 | */ |
---|
| 251 | +#if 0 |
---|
| 252 | if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) { |
---|
| 253 | log_err("directory is writable by others: (%s)\n", cwd); |
---|
| 254 | exit(116); |
---|
| 255 | } |
---|
| 256 | +#endif |
---|
| 257 | |
---|
| 258 | /* |
---|
| 259 | * Error out if we cannot stat the program. |
---|
| 260 | */ |
---|
| 261 | - if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) { |
---|
| 262 | + if (((lstat(cmd, &prg_info)) != 0) /*|| (S_ISLNK(prg_info.st_mode))*/) { |
---|
| 263 | log_err("cannot stat program: (%s)\n", cmd); |
---|
| 264 | exit(117); |
---|
| 265 | } |
---|
[2591] | 266 | @@ -578,10 +715,12 @@ int main(int argc, char *argv[]) |
---|
[1] | 267 | /* |
---|
| 268 | * Error out if the program is writable by others. |
---|
| 269 | */ |
---|
| 270 | +#if 0 |
---|
| 271 | if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) { |
---|
| 272 | log_err("file is writable by others: (%s/%s)\n", cwd, cmd); |
---|
| 273 | exit(118); |
---|
| 274 | } |
---|
| 275 | +#endif |
---|
| 276 | |
---|
| 277 | /* |
---|
| 278 | * Error out if the file is setuid or setgid. |
---|
[2591] | 279 | @@ -595,6 +734,7 @@ int main(int argc, char *argv[]) |
---|
[1] | 280 | * Error out if the target name/group is different from |
---|
| 281 | * the name/group of the cwd or the program. |
---|
| 282 | */ |
---|
| 283 | +#if 0 |
---|
| 284 | if ((uid != dir_info.st_uid) || |
---|
| 285 | (gid != dir_info.st_gid) || |
---|
| 286 | (uid != prg_info.st_uid) || |
---|
[2591] | 287 | @@ -606,12 +746,14 @@ int main(int argc, char *argv[]) |
---|
| 288 | (unsigned long)prg_info.st_uid, (unsigned long)prg_info.st_gid); |
---|
[1] | 289 | exit(120); |
---|
| 290 | } |
---|
| 291 | +#endif |
---|
| 292 | /* |
---|
| 293 | * Error out if the program is not executable for the user. |
---|
| 294 | * Otherwise, she won't find any error in the logs except for |
---|
[842] | 295 | * "[error] Premature end of script headers: ..." |
---|
| 296 | */ |
---|
| 297 | - if (!(prg_info.st_mode & S_IXUSR)) { |
---|
| 298 | + if (!is_static_extension(cmd) && !is_php_extension(cmd) && |
---|
[873] | 299 | + !(prg_info.st_mode & S_IXUSR)) { |
---|
[842] | 300 | log_err("file has no execute permission: (%s/%s)\n", cwd, cmd); |
---|
[873] | 301 | exit(121); |
---|
[842] | 302 | } |
---|
[2591] | 303 | @@ -660,6 +802,30 @@ int main(int argc, char *argv[]) |
---|
[1355] | 304 | /* |
---|
| 305 | * Execute the command, replacing our image with its own. |
---|
| 306 | */ |
---|
[298] | 307 | + if (is_static_extension(cmd)) { |
---|
[1590] | 308 | + if (setenv("PATH_TRANSLATED", cmd, 1) != 0) { |
---|
| 309 | + log_err("setenv failed\n"); |
---|
| 310 | + exit(255); |
---|
| 311 | + } |
---|
| 312 | + execl(STATIC_CAT_PATH, STATIC_CAT_PATH, (const char *)NULL); |
---|
| 313 | + log_err("(%d)%s: static-cat exec failed (%s)\n", errno, strerror(errno), STATIC_CAT_PATH); |
---|
[1259] | 314 | + exit(255); |
---|
[298] | 315 | + } |
---|
[842] | 316 | + if (is_php_extension(cmd)) { |
---|
| 317 | + setenv("PHPRC", ".", 1); |
---|
| 318 | + argv[1] = PHP_PATH; |
---|
| 319 | + argv[2] = "-f"; |
---|
[2186] | 320 | + /* |
---|
| 321 | + * argv[3] is the command to run. argv[4] is either an argument or |
---|
| 322 | + * already null. We don't want to pass any arguments through from |
---|
| 323 | + * Apache (since they're untrusted), so we chop off the remainder |
---|
| 324 | + * of argv here. |
---|
| 325 | + */ |
---|
| 326 | + argv[4] = 0; |
---|
[842] | 327 | + execv(PHP_PATH, &argv[1]); |
---|
[1354] | 328 | + log_err("(%d)%s: php exec failed (%s)\n", errno, strerror(errno), argv[1]); |
---|
[1259] | 329 | + exit(255); |
---|
[842] | 330 | + } |
---|
[1355] | 331 | #ifdef NEED_HASHBANG_EMUL |
---|
| 332 | /* We need the #! emulation when we want to execute scripts */ |
---|
| 333 | { |
---|
[2591] | 334 | -- |
---|
| 335 | 1.8.1.2 |
---|
| 336 | |
---|