]> granicus.if.org Git - apache/blob - support/suexec.c
Add CHANGES' security entries for 2.4.27.
[apache] / support / suexec.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache
19  *
20  ***********************************************************************
21  *
22  * NOTE! : DO NOT edit this code!!!  Unless you know what you are doing,
23  *         editing this code might open up your system in unexpected
24  *         ways to would-be crackers.  Every precaution has been taken
25  *         to make this code as safe as possible; alter it at your own
26  *         risk.
27  *
28  ***********************************************************************
29  *
30  *
31  */
32
33 #include "apr.h"
34 #include "ap_config.h"
35 #include "suexec.h"
36
37 #include <sys/param.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <string.h>
41 #include <time.h>
42 #if APR_HAVE_UNISTD_H
43 #include <unistd.h>
44 #endif
45
46 #include <stdio.h>
47 #include <stdarg.h>
48 #include <stdlib.h>
49 #if APR_HAVE_FCNTL_H
50 #include <fcntl.h>
51 #endif
52
53 #ifdef HAVE_PWD_H
54 #include <pwd.h>
55 #endif
56
57 #ifdef HAVE_GRP_H
58 #include <grp.h>
59 #endif
60
61 #if defined(PATH_MAX)
62 #define AP_MAXPATH PATH_MAX
63 #elif defined(MAXPATHLEN)
64 #define AP_MAXPATH MAXPATHLEN
65 #else
66 #define AP_MAXPATH 8192
67 #endif
68
69 #define AP_ENVBUF 256
70
71 extern char **environ;
72 static FILE *log = NULL;
73
74 static const char *const safe_env_lst[] =
75 {
76     /* variable name starts with */
77     "HTTP_",
78     "SSL_",
79
80     /* variable name is */
81     "AUTH_TYPE=",
82     "CONTENT_LENGTH=",
83     "CONTENT_TYPE=",
84     "CONTEXT_DOCUMENT_ROOT=",
85     "CONTEXT_PREFIX=",
86     "DATE_GMT=",
87     "DATE_LOCAL=",
88     "DOCUMENT_ARGS=",
89     "DOCUMENT_NAME=",
90     "DOCUMENT_PATH_INFO=",
91     "DOCUMENT_ROOT=",
92     "DOCUMENT_URI=",
93     "GATEWAY_INTERFACE=",
94     "HTTPS=",
95     "LAST_MODIFIED=",
96     "PATH_INFO=",
97     "PATH_TRANSLATED=",
98     "QUERY_STRING=",
99     "QUERY_STRING_UNESCAPED=",
100     "REMOTE_ADDR=",
101     "REMOTE_HOST=",
102     "REMOTE_IDENT=",
103     "REMOTE_PORT=",
104     "REMOTE_USER=",
105     "REDIRECT_ERROR_NOTES=",
106     "REDIRECT_HANDLER=",
107     "REDIRECT_QUERY_STRING=",
108     "REDIRECT_REMOTE_USER=",
109     "REDIRECT_SCRIPT_FILENAME=",
110     "REDIRECT_STATUS=",
111     "REDIRECT_URL=",
112     "REQUEST_METHOD=",
113     "REQUEST_URI=",
114     "REQUEST_SCHEME=",
115     "SCRIPT_FILENAME=",
116     "SCRIPT_NAME=",
117     "SCRIPT_URI=",
118     "SCRIPT_URL=",
119     "SERVER_ADMIN=",
120     "SERVER_NAME=",
121     "SERVER_ADDR=",
122     "SERVER_PORT=",
123     "SERVER_PROTOCOL=",
124     "SERVER_SIGNATURE=",
125     "SERVER_SOFTWARE=",
126     "UNIQUE_ID=",
127     "USER_NAME=",
128     "TZ=",
129     NULL
130 };
131
132 static void log_err(const char *fmt,...) 
133     __attribute__((format(printf,1,2)));
134 static void log_no_err(const char *fmt,...)  
135     __attribute__((format(printf,1,2)));
136 static void err_output(int is_error, const char *fmt, va_list ap) 
137     __attribute__((format(printf,2,0)));
138
139 static void err_output(int is_error, const char *fmt, va_list ap)
140 {
141 #ifdef AP_LOG_EXEC
142     time_t timevar;
143     struct tm *lt;
144
145     if (!log) {
146 #if defined(_LARGEFILE64_SOURCE) && HAVE_FOPEN64
147         if ((log = fopen64(AP_LOG_EXEC, "a")) == NULL) {
148 #else
149         if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) {
150 #endif
151             fprintf(stderr, "suexec failure: could not open log file\n");
152             perror("fopen");
153             exit(1);
154         }
155     }
156
157     if (is_error) {
158         fprintf(stderr, "suexec policy violation: see suexec log for more "
159                         "details\n");
160     }
161
162     time(&timevar);
163     lt = localtime(&timevar);
164
165     fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
166             lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
167             lt->tm_hour, lt->tm_min, lt->tm_sec);
168
169     vfprintf(log, fmt, ap);
170
171     fflush(log);
172 #endif /* AP_LOG_EXEC */
173     return;
174 }
175
176 static void log_err(const char *fmt,...)
177 {
178 #ifdef AP_LOG_EXEC
179     va_list ap;
180
181     va_start(ap, fmt);
182     err_output(1, fmt, ap); /* 1 == is_error */
183     va_end(ap);
184 #endif /* AP_LOG_EXEC */
185     return;
186 }
187
188 static void log_no_err(const char *fmt,...)
189 {
190 #ifdef AP_LOG_EXEC
191     va_list ap;
192
193     va_start(ap, fmt);
194     err_output(0, fmt, ap); /* 0 == !is_error */
195     va_end(ap);
196 #endif /* AP_LOG_EXEC */
197     return;
198 }
199
200 static void clean_env(void)
201 {
202     char pathbuf[512];
203     char **cleanenv;
204     char **ep;
205     int cidx = 0;
206     int idx;
207
208     /* While cleaning the environment, the environment should be clean.
209      * (e.g. malloc() may get the name of a file for writing debugging info.
210      * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd.  Sprintf() may be
211      * susceptible to bad locale settings....)
212      * (from PR 2790)
213      */
214     char **envp = environ;
215     char *empty_ptr = NULL;
216
217     environ = &empty_ptr; /* VERY safe environment */
218
219     if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) {
220         log_err("failed to malloc memory for environment\n");
221         exit(123);
222     }
223
224     sprintf(pathbuf, "PATH=%s", AP_SAFE_PATH);
225     cleanenv[cidx] = strdup(pathbuf);
226     if (cleanenv[cidx] == NULL) {
227         log_err("failed to malloc memory for environment\n");
228         exit(124);
229     }
230     cidx++;
231
232     for (ep = envp; *ep && cidx < AP_ENVBUF-1; ep++) {
233         for (idx = 0; safe_env_lst[idx]; idx++) {
234             if (!strncmp(*ep, safe_env_lst[idx],
235                          strlen(safe_env_lst[idx]))) {
236                 cleanenv[cidx] = *ep;
237                 cidx++;
238                 break;
239             }
240         }
241     }
242
243     cleanenv[cidx] = NULL;
244
245     environ = cleanenv;
246 }
247
248 int main(int argc, char *argv[])
249 {
250     int userdir = 0;        /* ~userdir flag             */
251     uid_t uid;              /* user information          */
252     gid_t gid;              /* target group placeholder  */
253     char *target_uname;     /* target user name          */
254     char *target_gname;     /* target group name         */
255     char *target_homedir;   /* target home directory     */
256     char *actual_uname;     /* actual user name          */
257     char *actual_gname;     /* actual group name         */
258     char *cmd;              /* command to be executed    */
259     char cwd[AP_MAXPATH];   /* current working directory */
260     char dwd[AP_MAXPATH];   /* docroot working directory */
261     struct passwd *pw;      /* password entry holder     */
262     struct group *gr;       /* group entry holder        */
263     struct stat dir_info;   /* directory info holder     */
264     struct stat prg_info;   /* program info holder       */
265
266     /*
267      * Start with a "clean" environment
268      */
269     clean_env();
270
271     /*
272      * Check existence/validity of the UID of the user
273      * running this program.  Error out if invalid.
274      */
275     uid = getuid();
276     if ((pw = getpwuid(uid)) == NULL) {
277         log_err("crit: invalid uid: (%lu)\n", (unsigned long)uid);
278         exit(102);
279     }
280     /*
281      * See if this is a 'how were you compiled' request, and
282      * comply if so.
283      */
284     if ((argc > 1)
285         && (! strcmp(argv[1], "-V"))
286         && ((uid == 0)
287 #ifdef _OSD_POSIX
288         /* User name comparisons are case insensitive on BS2000/OSD */
289             || (! strcasecmp(AP_HTTPD_USER, pw->pw_name)))
290 #else  /* _OSD_POSIX */
291             || (! strcmp(AP_HTTPD_USER, pw->pw_name)))
292 #endif /* _OSD_POSIX */
293         ) {
294 #ifdef AP_DOC_ROOT
295         fprintf(stderr, " -D AP_DOC_ROOT=\"%s\"\n", AP_DOC_ROOT);
296 #endif
297 #ifdef AP_GID_MIN
298         fprintf(stderr, " -D AP_GID_MIN=%d\n", AP_GID_MIN);
299 #endif
300 #ifdef AP_HTTPD_USER
301         fprintf(stderr, " -D AP_HTTPD_USER=\"%s\"\n", AP_HTTPD_USER);
302 #endif
303 #ifdef AP_LOG_EXEC
304         fprintf(stderr, " -D AP_LOG_EXEC=\"%s\"\n", AP_LOG_EXEC);
305 #endif
306 #ifdef AP_SAFE_PATH
307         fprintf(stderr, " -D AP_SAFE_PATH=\"%s\"\n", AP_SAFE_PATH);
308 #endif
309 #ifdef AP_SUEXEC_UMASK
310         fprintf(stderr, " -D AP_SUEXEC_UMASK=%03o\n", AP_SUEXEC_UMASK);
311 #endif
312 #ifdef AP_UID_MIN
313         fprintf(stderr, " -D AP_UID_MIN=%d\n", AP_UID_MIN);
314 #endif
315 #ifdef AP_USERDIR_SUFFIX
316         fprintf(stderr, " -D AP_USERDIR_SUFFIX=\"%s\"\n", AP_USERDIR_SUFFIX);
317 #endif
318         exit(0);
319     }
320     /*
321      * If there are a proper number of arguments, set
322      * all of them to variables.  Otherwise, error out.
323      */
324     if (argc < 4) {
325         log_err("too few arguments\n");
326         exit(101);
327     }
328     target_uname = argv[1];
329     target_gname = argv[2];
330     cmd = argv[3];
331
332     /*
333      * Check to see if the user running this program
334      * is the user allowed to do so as defined in
335      * suexec.h.  If not the allowed user, error out.
336      */
337 #ifdef _OSD_POSIX
338     /* User name comparisons are case insensitive on BS2000/OSD */
339     if (strcasecmp(AP_HTTPD_USER, pw->pw_name)) {
340         log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER);
341         exit(103);
342     }
343 #else  /*_OSD_POSIX*/
344     if (strcmp(AP_HTTPD_USER, pw->pw_name)) {
345         log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER);
346         exit(103);
347     }
348 #endif /*_OSD_POSIX*/
349
350     /*
351      * Check for a leading '/' (absolute path) in the command to be executed,
352      * or attempts to back up out of the current directory,
353      * to protect against attacks.  If any are
354      * found, error out.  Naughty naughty crackers.
355      */
356     if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
357         || (strstr(cmd, "/../") != NULL)) {
358         log_err("invalid command (%s)\n", cmd);
359         exit(104);
360     }
361
362     /*
363      * Check to see if this is a ~userdir request.  If
364      * so, set the flag, and remove the '~' from the
365      * target username.
366      */
367     if (!strncmp("~", target_uname, 1)) {
368         target_uname++;
369         userdir = 1;
370     }
371
372     /*
373      * Error out if the target username is invalid.
374      */
375     if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
376         if ((pw = getpwnam(target_uname)) == NULL) {
377             log_err("invalid target user name: (%s)\n", target_uname);
378             exit(105);
379         }
380     }
381     else {
382         if ((pw = getpwuid(atoi(target_uname))) == NULL) {
383             log_err("invalid target user id: (%s)\n", target_uname);
384             exit(121);
385         }
386     }
387
388     /*
389      * Error out if the target group name is invalid.
390      */
391     if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
392         if ((gr = getgrnam(target_gname)) == NULL) {
393             log_err("invalid target group name: (%s)\n", target_gname);
394             exit(106);
395         }
396     }
397     else {
398         if ((gr = getgrgid(atoi(target_gname))) == NULL) {
399             log_err("invalid target group id: (%s)\n", target_gname);
400             exit(106);
401         }
402     }
403     gid = gr->gr_gid;
404     if ((actual_gname = strdup(gr->gr_name)) == NULL) {
405         log_err("failed to alloc memory\n");
406         exit(125);
407     }
408
409 #ifdef _OSD_POSIX
410     /*
411      * Initialize BS2000 user environment
412      */
413     {
414         pid_t pid;
415         int status;
416
417         switch (pid = ufork(target_uname)) {
418         case -1:    /* Error */
419             log_err("failed to setup bs2000 environment for user %s: %s\n",
420                     target_uname, strerror(errno));
421             exit(150);
422         case 0:     /* Child */
423             break;
424         default:    /* Father */
425             while (pid != waitpid(pid, &status, 0))
426                 ;
427             /* @@@ FIXME: should we deal with STOP signals as well? */
428             if (WIFSIGNALED(status)) {
429                 kill (getpid(), WTERMSIG(status));
430             }
431             exit(WEXITSTATUS(status));
432         }
433     }
434 #endif /*_OSD_POSIX*/
435
436     /*
437      * Save these for later since initgroups will hose the struct
438      */
439     uid = pw->pw_uid;
440     actual_uname = strdup(pw->pw_name);
441     target_homedir = strdup(pw->pw_dir);
442     if (actual_uname == NULL || target_homedir == NULL) {
443         log_err("failed to alloc memory\n");
444         exit(126);
445     }
446
447     /*
448      * Log the transaction here to be sure we have an open log
449      * before we setuid().
450      */
451     log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n",
452                target_uname, actual_uname,
453                target_gname, actual_gname,
454                cmd);
455
456     /*
457      * Error out if attempt is made to execute as root or as
458      * a UID less than AP_UID_MIN.  Tsk tsk.
459      */
460     if ((uid == 0) || (uid < AP_UID_MIN)) {
461         log_err("cannot run as forbidden uid (%lu/%s)\n", (unsigned long)uid, cmd);
462         exit(107);
463     }
464
465     /*
466      * Error out if attempt is made to execute as root group
467      * or as a GID less than AP_GID_MIN.  Tsk tsk.
468      */
469     if ((gid == 0) || (gid < AP_GID_MIN)) {
470         log_err("cannot run as forbidden gid (%lu/%s)\n", (unsigned long)gid, cmd);
471         exit(108);
472     }
473
474     /*
475      * Change UID/GID here so that the following tests work over NFS.
476      *
477      * Initialize the group access list for the target user,
478      * and setgid() to the target group. If unsuccessful, error out.
479      */
480     if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) {
481         log_err("failed to setgid (%lu: %s)\n", (unsigned long)gid, cmd);
482         exit(109);
483     }
484
485     /*
486      * setuid() to the target user.  Error out on fail.
487      */
488     if ((setuid(uid)) != 0) {
489         log_err("failed to setuid (%lu: %s)\n", (unsigned long)uid, cmd);
490         exit(110);
491     }
492
493     /*
494      * Get the current working directory, as well as the proper
495      * document root (dependant upon whether or not it is a
496      * ~userdir request).  Error out if we cannot get either one,
497      * or if the current working directory is not in the docroot.
498      * Use chdir()s and getcwd()s to avoid problems with symlinked
499      * directories.  Yuck.
500      */
501     if (getcwd(cwd, AP_MAXPATH) == NULL) {
502         log_err("cannot get current working directory\n");
503         exit(111);
504     }
505
506     if (userdir) {
507         if (((chdir(target_homedir)) != 0) ||
508             ((chdir(AP_USERDIR_SUFFIX)) != 0) ||
509             ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
510             ((chdir(cwd)) != 0)) {
511             log_err("cannot get docroot information (%s)\n", target_homedir);
512             exit(112);
513         }
514     }
515     else {
516         if (((chdir(AP_DOC_ROOT)) != 0) ||
517             ((getcwd(dwd, AP_MAXPATH)) == NULL) ||
518             ((chdir(cwd)) != 0)) {
519             log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT);
520             exit(113);
521         }
522     }
523
524     if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
525         log_err("command not in docroot (%s/%s)\n", cwd, cmd);
526         exit(114);
527     }
528
529     /*
530      * Stat the cwd and verify it is a directory, or error out.
531      */
532     if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
533         log_err("cannot stat directory: (%s)\n", cwd);
534         exit(115);
535     }
536
537     /*
538      * Error out if cwd is writable by others.
539      */
540     if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
541         log_err("directory is writable by others: (%s)\n", cwd);
542         exit(116);
543     }
544
545     /*
546      * Error out if we cannot stat the program.
547      */
548     if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) {
549         log_err("cannot stat program: (%s)\n", cmd);
550         exit(117);
551     }
552
553     /*
554      * Error out if the program is writable by others.
555      */
556     if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
557         log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
558         exit(118);
559     }
560
561     /*
562      * Error out if the file is setuid or setgid.
563      */
564     if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
565         log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
566         exit(119);
567     }
568
569     /*
570      * Error out if the target name/group is different from
571      * the name/group of the cwd or the program.
572      */
573     if ((uid != dir_info.st_uid) ||
574         (gid != dir_info.st_gid) ||
575         (uid != prg_info.st_uid) ||
576         (gid != prg_info.st_gid)) {
577         log_err("target uid/gid (%lu/%lu) mismatch "
578                 "with directory (%lu/%lu) or program (%lu/%lu)\n",
579                 (unsigned long)uid, (unsigned long)gid,
580                 (unsigned long)dir_info.st_uid, (unsigned long)dir_info.st_gid,
581                 (unsigned long)prg_info.st_uid, (unsigned long)prg_info.st_gid);
582         exit(120);
583     }
584     /*
585      * Error out if the program is not executable for the user.
586      * Otherwise, she won't find any error in the logs except for
587      * "[error] Premature end of script headers: ..."
588      */
589     if (!(prg_info.st_mode & S_IXUSR)) {
590         log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
591         exit(121);
592     }
593
594 #ifdef AP_SUEXEC_UMASK
595     /*
596      * umask() uses inverse logic; bits are CLEAR for allowed access.
597      */
598     if ((~AP_SUEXEC_UMASK) & 0022) {
599         log_err("notice: AP_SUEXEC_UMASK of %03o allows "
600                 "write permission to group and/or other\n", AP_SUEXEC_UMASK);
601     }
602     umask(AP_SUEXEC_UMASK);
603 #endif /* AP_SUEXEC_UMASK */
604
605     /* Be sure to close the log file so the CGI can't mess with it. */
606     if (log != NULL) {
607 #if APR_HAVE_FCNTL_H
608         /*
609          * ask fcntl(2) to set the FD_CLOEXEC flag on the log file,
610          * so it'll be automagically closed if the exec() call succeeds.
611          */
612         fflush(log);
613         setbuf(log, NULL);
614         if ((fcntl(fileno(log), F_SETFD, FD_CLOEXEC) == -1)) {
615             log_err("error: can't set close-on-exec flag");
616             exit(122);
617         }
618 #else
619         /*
620          * In this case, exec() errors won't be logged because we have already
621          * dropped privileges and won't be able to reopen the log file.
622          */
623         fclose(log);
624         log = NULL;
625 #endif
626     }
627
628     /*
629      * Execute the command, replacing our image with its own.
630      */
631 #ifdef NEED_HASHBANG_EMUL
632     /* We need the #! emulation when we want to execute scripts */
633     {
634         extern char **environ;
635
636         ap_execve(cmd, &argv[3], environ);
637     }
638 #else /*NEED_HASHBANG_EMUL*/
639     execv(cmd, &argv[3]);
640 #endif /*NEED_HASHBANG_EMUL*/
641
642     /*
643      * (I can't help myself...sorry.)
644      *
645      * Uh oh.  Still here.  Where's the kaboom?  There was supposed to be an
646      * EARTH-shattering kaboom!
647      *
648      * Oh well, log the failure and error out.
649      */
650     log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd);
651     exit(255);
652 }