1 /* suid_script_wrapper.c 2 * 3 * Wrapper around scripts for chmod +s purposes. Scripts can only be setuid if 4 * their interpreter is, which most interpreters are not. You can work around 5 * this limitation with this program. You must hardcode the full path to both 6 * interpreter and script to prevent security breaches. 7 * 8 * An example script is included below. 9 10 % cat /tmp/suidtest.py 11 import os, sys 12 print "UID: %d GID: %d" % (os.getuid(), os.getgid()) 13 print "EUID: %d EGID: %d" % (os.geteuid(), os.getegid()) 14 print "ARGV: %s" % str(sys.argv) 15 print "ENVP: %s" % str(os.environ)
1 /* suid_script_wrapper.c
2 *
3 * Wrapper around scripts for chmod +s purposes. Scripts can only be setuid if
4 * their interpreter is, which most interpreters are not. You can work around
5 * this limitation with this program. You must hardcode the full path to both
6 * interpreter and script to prevent security breaches.
7 *
8 * An example script is included below.
9
10 % cat /tmp/suidtest.py
11 import os, sys
12 print "UID: %d GID: %d" % (os.getuid(), os.getgid())
13 print "EUID: %d EGID: %d" % (os.geteuid(), os.getegid())
14 print "ARGV: %s" % str(sys.argv)
15 print "ENVP: %s" % str(os.environ)
16
17 * (c)2008 Dennis Kaarsemaker - Dedicated to the public domain
18 */
19
20 /* Full path to interpreter */
21 #define INTERPRETER_PATH "/usr/bin/python"
22 /* Full path to script */
23 #define SCRIPT_PATH "/tmp/suidtest.py"
24 /* Pass argv on or not */
25 #define TAKE_ARGV 1
26 /* Paranoid mode: Every involved file should be owned by root */
27 #define PARANOID 1
28
29 #define _GNU_SOURCE
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/stat.h>
35 #include <sys/types.h>
36 #include <unistd.h>
37
38 extern char **environ;
39
40 /* Clear the environment. Only preserve USER/USERNAME/LOGNAME/HOME/LANG/LC_* */
41 void clear_environ() {
42 char **env = environ;
43 size_t len;
44 char *key;
45 while(*env) env++;
46 do {
47 env--;
48 len = strchr(*env, '=') - *env;
49 key = strndup(*env, len);
50 /* You can extend this list if you want, but be careful */
51 if(strcmp(key, "USER") &&
52 strcmp(key, "USERNAME") &&
53 strcmp(key, "LOGNAME") &&
54 strcmp(key, "HOME") &&
55 strcmp(key, "LANG") &&
56 strncmp(key, "LC_", 3)
57 )
58 unsetenv(key);
59 free(key);
60 } while (env != environ);
61 /* Inject a few safe things */
62 setenv("PATH","/bin:/sbin:/usr/bin:/usr/sbin:/usr/games:/usr/X11/bin", 1);
63 setenv("IFS"," \t\n", 1);
64 }
65
66 int main(int argc, char **argv) {
67 int ret = 0;
68 struct stat buf1, buf2;
69 char *interpreter_path = INTERPRETER_PATH;
70 char *script_path = SCRIPT_PATH;
71
72 clear_environ();
73
74 /* Config errors */
75 if(strchr(interpreter_path, '/') != interpreter_path) {
76 fprintf(stderr, "Path to interpreter not absolute\n");
77 ret |= 1;
78 }
79 if(strchr(script_path, '/') != script_path) {
80 fprintf(stderr, "Path to script not absolute\n");
81 ret |= 1;
82 }
83
84 /* Access errors */
85 if(stat(argv[0], &buf1) == -1) {
86 perror("stat on argv[0] failed");
87 ret |= 2;
88 }
89 if(!(buf1.st_mode & (S_ISGID | S_ISUID))) {
90 fprintf(stderr, "Wrapper isn't suid/sgid\n");
91 ret |= 2;
92 }
93 if(access(interpreter_path, X_OK)) {
94 perror("Cannot execute interpreter " INTERPRETER_PATH);
95 ret |= 2;
96 }
97 if(access(script_path, R_OK)) {
98 perror("Cannot read script " SCRIPT_PATH);
99 ret |= 2;
100 }
101
102 /* Permission/owner errors */
103 if(!stat(argv[0], &buf1) && !stat(script_path, &buf2)) {
104 #if PARANOID
105 if((buf1.st_uid != 0) || (buf1.st_gid != 0)) {
106 fprintf(stderr, "%s is not owned by root\n", argv[0]);
107 ret |= 4;
108 }
109 if((buf2.st_uid != 0) || (buf2.st_gid != 0)) {
110 fprintf(stderr, "%s is not owned by root\n", script_path);
111 ret |= 4;
112 }
113 #else
114 if((buf1.st_mode & S_ISGID) && (buf1.st_gid != buf2.st_gid)) {
115 fprintf(stderr, "Mismatch betweeen the gid of %s and %s\n", argv[0], script_path);
116 ret |= 4;
117 }
118 if((buf1.st_mode & S_ISUID) && (buf1.st_uid != buf2.st_uid)) {
119 fprintf(stderr, "Mismatch betweeen the uid of %s and %s\n", argv[0], script_path);
120 ret |= 4;
121 }
122 #endif
123 if((buf1.st_mode & S_ISGID) && (buf1.st_mode & S_IWGRP)) {
124 fprintf(stderr, "%s is world writable\n", argv[0]);
125 ret |= 4;
126 }
127 if((buf1.st_mode & S_ISGID) && (buf2.st_mode & S_IWGRP)) {
128 fprintf(stderr, "%s is world writable\n", script_path);
129 ret |= 4;
130 }
131 if((buf1.st_mode & S_ISGID) && (buf1.st_mode & (S_IWGRP | S_IWOTH))) {
132 fprintf(stderr, "%s is world/group writable\n", argv[0]);
133 ret |= 4;
134 }
135 if((buf1.st_mode & S_ISGID) && (buf2.st_mode & (S_IWGRP | S_IWOTH))) {
136 fprintf(stderr, "%s is world/group writable\n", script_path);
137 ret |= 4;
138 }
139 }
140
141 /* Interpreter check */
142 if(!stat(interpreter_path, &buf1)) {
143 if(buf1.st_uid != 0) {
144 fprintf(stderr, "Invalid interpreter, not owned by root\n");
145 ret |= 8;
146 }
147 if(buf1.st_mode & (S_IWGRP | S_IWOTH)) {
148 fprintf(stderr, "%s is world/group writable\n", interpreter_path);
149 ret |= 8;
150 }
151 }
152
153 if(ret)
154 return ret | 128;
155
156 #if TAKE_ARGV
157 char **new_argv = (char**)malloc(argc+2 * sizeof(char*));
158 int i = 1;
159 new_argv[0] = interpreter_path;
160 new_argv[1] = script_path;
161 while(i < argc) {
162 new_argv[i+1] = argv[i];
163 i++;
164 }
165 new_argv[i+1] = NULL;
166 #else
167 char* new_argv[3] = {interpreter_path, script_path, NULL};
168 #endif
169
170 execve(interpreter_path, new_argv, environ);
171 perror("Could not execute wrapped script " INTERPRETER_PATH " " SCRIPT_PATH);
172 return 16 | 128;
173 }
Show all