Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/cmd/ksh93/sh/suid_exec.c
1810 views
1
/***********************************************************************
2
* *
3
* This software is part of the ast package *
4
* Copyright (c) 1982-2011 AT&T Intellectual Property *
5
* and is licensed under the *
6
* Eclipse Public License, Version 1.0 *
7
* by AT&T Intellectual Property *
8
* *
9
* A copy of the License is available at *
10
* http://www.eclipse.org/org/documents/epl-v10.html *
11
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12
* *
13
* Information and Software Systems Research *
14
* AT&T Research *
15
* Florham Park NJ *
16
* *
17
* David Korn <[email protected]> *
18
* *
19
***********************************************************************/
20
#pragma prototyped
21
/*
22
* This is a program to execute 'execute only' and suid/sgid shell scripts.
23
* This program must be owned by root and must have the set uid bit set.
24
* It must not have the set group id bit set. This program must be installed
25
* where the define parameter THISPROG indicates to work correctly on system V
26
*
27
* Written by David Korn
28
* AT&T Labs
29
* Enhanced by Rob Stampfli
30
*/
31
32
/* The file name of the script to execute is argv[0]
33
* Argv[1] is the program name
34
* The basic idea is to open the script as standard input, set the effective
35
* user and group id correctly, and then exec the shell.
36
* The complicated part is getting the effective uid of the caller and
37
* setting the effective uid/gid. The program which execs this program
38
* may pass file descriptor FDIN as an open file with mode SPECIAL if
39
* the effective user id is not the real user id. The effective
40
* user id for authentication purposes will be the owner of this
41
* open file. On systems without the setreuid() call, e[ug]id is set
42
* by copying this program to a /tmp/file, making it a suid and/or sgid
43
* program, and then execing this program.
44
* A forked version of this program waits until it can unlink the /tmp
45
* file and then exits. Actually, we fork() twice so the parent can
46
* wait for the child to complete. A pipe is used to guarantee that we
47
* do not remove the /tmp file too soon.
48
*/
49
50
#include <ast.h>
51
#include "FEATURE/externs"
52
#include <ls.h>
53
#include <sig.h>
54
#include <error.h>
55
#include <sys/wait.h>
56
#include "version.h"
57
58
#define SPECIAL 04100 /* setuid execute only by owner */
59
#define FDIN 10 /* must be same as /dev/fd below */
60
#undef FDSYNC
61
#define FDSYNC 11 /* used on sys5 to synchronize cleanup */
62
#define FDVERIFY 12 /* used to validate /tmp process */
63
#undef BLKSIZE
64
#define BLKSIZE sizeof(char*)*1024
65
#define THISPROG "/etc/suid_exec"
66
#define DEFSHELL "/bin/sh"
67
68
static void error_exit(const char*);
69
static int in_dir(const char*, const char*);
70
static int endsh(const char*);
71
#ifndef _lib_setregid
72
# undef _lib_setreuid
73
#endif
74
#ifndef _lib_setreuid
75
static void setids(int,uid_t,gid_t);
76
static int mycopy(int, int);
77
static void maketemp(char*);
78
#else
79
static void setids(int,int,int);
80
#endif /* _lib_setreuid */
81
82
static const char version[] = "\n@(#)$Id: suid_exec "SH_RELEASE" $\n";
83
static const char badopen[] = "cannot open";
84
static const char badexec[] = "cannot exec";
85
static const char devfd[] = "/dev/fd/10"; /* must match FDIN above */
86
static char tmpname[] = "/tmp/SUIDXXXXXX";
87
static char **arglist;
88
89
static char *shell;
90
static char *command;
91
static uid_t ruserid;
92
static uid_t euserid;
93
static gid_t rgroupid;
94
static gid_t egroupid;
95
static struct stat statb;
96
97
int main(int argc,char *argv[])
98
{
99
register int m,n;
100
register char *p;
101
struct stat statx;
102
int mode;
103
uid_t effuid;
104
gid_t effgid;
105
NOT_USED(argc);
106
arglist = argv;
107
if((command = argv[1]) == 0)
108
error_exit(badexec);
109
ruserid = getuid();
110
euserid = geteuid();
111
rgroupid = getgid();
112
egroupid = getegid();
113
p = argv[0];
114
#ifndef _lib_setreuid
115
maketemp(tmpname);
116
if(strcmp(p,tmpname)==0)
117
{
118
/* At this point, the presumption is that we are the
119
* version of THISPROG copied into /tmp, with the owner,
120
* group, and setuid/gid bits correctly set. This copy of
121
* the program is executable by anyone, so we must be careful
122
* not to allow just any invocation of it to succeed, since
123
* it is setuid/gid. Validate the proper execution by
124
* examining the FDVERIFY file descriptor -- if it is owned
125
* by root and is mode SPECIAL, then this is proof that it was
126
* passed by a program with superuser privileges -- hence we
127
* can presume legitimacy. Otherwise, bail out, as we suspect
128
* an impostor.
129
*/
130
if(fstat(FDVERIFY,&statb) < 0 || statb.st_uid != 0 ||
131
(statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0)
132
error_exit(badexec);
133
/* This enables the grandchild to clean up /tmp file */
134
close(FDSYNC);
135
/* Make sure that this is a valid invocation of the clone.
136
* Perhaps unnecessary, given FDVERIFY, but what the heck...
137
*/
138
if(stat(tmpname,&statb) < 0 || statb.st_nlink != 1 ||
139
!S_ISREG(statb.st_mode))
140
error_exit(badexec);
141
if(ruserid != euserid &&
142
((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid))
143
error_exit(badexec);
144
goto exec;
145
}
146
/* Make sure that this is the real setuid program, not the clone.
147
* It is possible by clever hacking to get past this point in the
148
* clone, but it doesn't do the hacker any good that I can see.
149
*/
150
if(euserid)
151
error_exit(badexec);
152
#endif /* _lib_setreuid */
153
/* Open the script for reading first and then validate it. This
154
* prevents someone from pulling a switcheroo while we are validating.
155
*/
156
n = open(p,0);
157
if(n == FDIN)
158
{
159
n = dup(n);
160
close(FDIN);
161
}
162
if(n < 0)
163
error_exit(badopen);
164
/* validate execution rights to this script */
165
if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL)
166
euserid = ruserid;
167
else
168
euserid = statb.st_uid;
169
/* do it the easy way if you can */
170
if(euserid == ruserid && egroupid == rgroupid)
171
{
172
if(access(p,X_OK) < 0)
173
error_exit(badexec);
174
}
175
else
176
{
177
/* have to check access on each component */
178
while(*p++)
179
{
180
if(*p == '/' || *p == 0)
181
{
182
m = *p;
183
*p = 0;
184
if(eaccess(argv[0],X_OK) < 0)
185
error_exit(badexec);
186
*p = m;
187
}
188
}
189
p = argv[0];
190
}
191
if(fstat(n, &statb) < 0 || !S_ISREG(statb.st_mode))
192
error_exit(badopen);
193
if(stat(p, &statx) < 0 ||
194
statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev)
195
error_exit(badexec);
196
if(stat(THISPROG, &statx) < 0 ||
197
(statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev))
198
error_exit(badexec);
199
close(FDIN);
200
if(fcntl(n,F_DUPFD,FDIN) != FDIN)
201
error_exit(badexec);
202
close(n);
203
204
/* compute the desired new effective user and group id */
205
effuid = euserid;
206
effgid = egroupid;
207
mode = 0;
208
if(statb.st_mode & S_ISUID)
209
effuid = statb.st_uid;
210
if(statb.st_mode & S_ISGID)
211
effgid = statb.st_gid;
212
213
/* see if group needs setting */
214
if(effgid != egroupid)
215
if(effgid != rgroupid || setgid(rgroupid) < 0)
216
mode = S_ISGID;
217
218
/* now see if the uid needs setting */
219
if(mode)
220
{
221
if(effuid != ruserid)
222
mode |= S_ISUID;
223
}
224
else if(effuid)
225
{
226
if(effuid != ruserid || setuid(ruserid) < 0)
227
mode = S_ISUID;
228
}
229
230
if(mode)
231
setids(mode, effuid, effgid);
232
#ifndef _lib_setreuid
233
exec:
234
#endif /* _lib_setreuid */
235
/* only use SHELL if file is in trusted directory and ends in sh */
236
shell = getenv("SHELL");
237
if(shell == 0 || !endsh(shell) || (
238
!in_dir("/bin",shell) &&
239
!in_dir("/usr/bin",shell) &&
240
!in_dir("/usr/lbin",shell) &&
241
!in_dir("/usr/local/bin",shell)))
242
shell = DEFSHELL;
243
argv[0] = command;
244
argv[1] = (char*)devfd;
245
execv(shell,argv);
246
error_exit(badexec);
247
}
248
249
/*
250
* return true of shell ends in sh of ksh
251
*/
252
253
static int endsh(register const char *shell)
254
{
255
while(*shell)
256
shell++;
257
if(*--shell != 'h' || *--shell != 's')
258
return(0);
259
if(*--shell=='/')
260
return(1);
261
if(*shell=='k' && *--shell=='/')
262
return(1);
263
return(0);
264
}
265
266
267
/*
268
* return true of shell is in <dir> directory
269
*/
270
271
static int in_dir(register const char *dir,register const char *shell)
272
{
273
while(*dir)
274
{
275
if(*dir++ != *shell++)
276
return(0);
277
}
278
/* return true if next character is a '/' */
279
return(*shell=='/');
280
}
281
282
static void error_exit(const char *message)
283
{
284
sfprintf(sfstdout,"%s: %s\n",command,message);
285
exit(126);
286
}
287
288
289
/*
290
* This version of access checks against effective uid and effective gid
291
*/
292
293
int eaccess(register const char *name, register int mode)
294
{
295
struct stat statb;
296
if (stat(name, &statb) == 0)
297
{
298
if(euserid == 0)
299
{
300
if(!S_ISREG(statb.st_mode) || mode != 1)
301
return(0);
302
/* root needs execute permission for someone */
303
mode = (S_IXUSR|S_IXGRP|S_IXOTH);
304
}
305
else if(euserid == statb.st_uid)
306
mode <<= 6;
307
else if(egroupid == statb.st_gid)
308
mode <<= 3;
309
#ifdef _lib_getgroups
310
/* on some systems you can be in several groups */
311
else
312
{
313
static int maxgroups;
314
gid_t *groups=0;
315
register int n;
316
if(maxgroups==0)
317
{
318
/* first time */
319
if((maxgroups=getgroups(0,groups)) < 0)
320
{
321
/* pre-POSIX system */
322
maxgroups=NGROUPS_MAX;
323
}
324
}
325
groups = (gid_t*)malloc((maxgroups+1)*sizeof(gid_t));
326
n = getgroups(maxgroups,groups);
327
while(--n >= 0)
328
{
329
if(groups[n] == statb.st_gid)
330
{
331
mode <<= 3;
332
break;
333
}
334
}
335
}
336
#endif /* _lib_getgroups */
337
if(statb.st_mode & mode)
338
return(0);
339
}
340
return(-1);
341
}
342
343
#ifdef _lib_setreuid
344
static void setids(int mode,int owner,int group)
345
{
346
if(mode & S_ISGID)
347
setregid(rgroupid,group);
348
349
/* set effective uid even if S_ISUID is not set. This is because
350
* we are *really* executing EUID root at this point. Even if S_ISUID
351
* is not set, the value for owner that is passsed should be correct.
352
*/
353
setreuid(ruserid,owner);
354
}
355
356
#else
357
/*
358
* This version of setids creats a /tmp file and copies itself into it.
359
* The "clone" file is made executable with appropriate suid/sgid bits.
360
* Finally, the clone is exec'ed. This file is unlinked by a grandchild
361
* of this program, who waits around until the text is free.
362
*/
363
364
static void setids(int mode,uid_t owner,gid_t group)
365
{
366
register int n,m;
367
int pv[2];
368
369
/*
370
* Create a token to pass to the new program for validation.
371
* This token can only be procured by someone running with an
372
* effective userid of root, and hence gives the clone a way to
373
* certify that it was really invoked by THISPROG. Someone who
374
* is already root could spoof us, but why would they want to?
375
*
376
* Since we are root here, we must be careful: What if someone
377
* linked a valuable file to tmpname?
378
*/
379
unlink(tmpname); /* should normally fail */
380
#ifdef O_EXCL
381
if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 ||
382
unlink(tmpname) < 0)
383
#else
384
if((n = open(tmpname, O_WRONLY | O_CREAT ,SPECIAL)) < 0 || unlink(tmpname) < 0)
385
#endif
386
error_exit(badexec);
387
if(n != FDVERIFY)
388
{
389
close(FDVERIFY);
390
if(fcntl(n,F_DUPFD,FDVERIFY) != FDVERIFY)
391
error_exit(badexec);
392
}
393
mode |= S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6);
394
/* create a pipe for synchronization */
395
if(pipe(pv) < 0)
396
error_exit(badexec);
397
if((n=fork()) == 0)
398
{ /* child */
399
close(FDVERIFY);
400
close(pv[1]);
401
if((n=fork()) == 0)
402
{ /* grandchild -- cleans up clone file */
403
signal(SIGHUP, SIG_IGN);
404
signal(SIGINT, SIG_IGN);
405
signal(SIGQUIT, SIG_IGN);
406
signal(SIGTERM, SIG_IGN);
407
read(pv[0],pv,1); /* wait for clone to close pipe */
408
while(unlink(tmpname) < 0 && errno == ETXTBSY)
409
sleep(1);
410
exit(0);
411
}
412
else if(n == -1)
413
exit(1);
414
else
415
{
416
/* Create a set[ug]id file that will become the clone.
417
* To make this atomic, without need for chown(), the
418
* child takes on desired user and group. The only
419
* downsize of this that I can see is that it may
420
* screw up some per- * user accounting.
421
*/
422
if((m = open(THISPROG, O_RDONLY)) < 0)
423
exit(1);
424
if((mode & S_ISGID) && setgid(group) < 0)
425
exit(1);
426
if((mode & S_ISUID) && owner && setuid(owner) < 0)
427
exit(1);
428
#ifdef O_EXCL
429
if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, mode)) < 0)
430
#else
431
unlink(tmpname);
432
if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0)
433
#endif /* O_EXCL */
434
exit(1);
435
/* populate the clone */
436
m = mycopy(m,n);
437
if(chmod(tmpname,mode) <0)
438
exit(1);
439
exit(m);
440
}
441
}
442
else if(n == -1)
443
error_exit(badexec);
444
else
445
{
446
arglist[0] = (char*)tmpname;
447
close(pv[0]);
448
/* move write end of pipe into FDSYNC */
449
if(pv[1] != FDSYNC)
450
{
451
close(FDSYNC);
452
if(fcntl(pv[1],F_DUPFD,FDSYNC) != FDSYNC)
453
error_exit(badexec);
454
}
455
/* wait for child to die */
456
while((m = wait(0)) != n)
457
if(m == -1 && errno != EINTR)
458
break;
459
/* Kill any setuid status at this point. That way, if the
460
* clone is not setuid, we won't exec it as root. Also, don't
461
* neglect to consider that someone could have switched the
462
* clone file on us.
463
*/
464
if(setuid(ruserid) < 0)
465
error_exit(badexec);
466
execv(tmpname,arglist);
467
error_exit(badexec);
468
}
469
}
470
471
/*
472
* create a unique name into the <template>
473
*/
474
475
static void maketemp(char *template)
476
{
477
register char *cp = template;
478
register pid_t n = getpid();
479
/* skip to end of string */
480
while(*++cp);
481
/* convert process id to string */
482
while(n > 0)
483
{
484
*--cp = (n%10) + '0';
485
n /= 10;
486
}
487
488
}
489
490
/*
491
* copy THISPROG into the open file number <fdo> and close <fdo>
492
*/
493
494
static int mycopy(int fdi, int fdo)
495
{
496
char buffer[BLKSIZE];
497
register int n;
498
499
while((n = read(fdi,buffer,BLKSIZE)) > 0)
500
if(write(fdo,buffer,n) != n)
501
break;
502
close(fdi);
503
close(fdo);
504
return n;
505
}
506
507
#endif /* _lib_setreuid */
508
509
510
511