Merge lp:~broder/upstart/drop-privileges into lp:upstart
- drop-privileges
- Merge into trunk
Proposed by
Evan Broder
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 1331 | ||||
Proposed branch: | lp:~broder/upstart/drop-privileges | ||||
Merge into: | lp:upstart | ||||
Diff against target: |
748 lines (+536/-8) 12 files modified
contrib/vim/syntax/upstart.vim (+1/-1) init/errors.h (+4/-0) init/job_class.c (+3/-0) init/job_class.h (+4/-0) init/job_process.c (+82/-0) init/job_process.h (+4/-0) init/man/init.5 (+26/-0) init/parse_job.c (+84/-0) init/tests/test_job_class.c (+4/-0) init/tests/test_job_process.c (+49/-7) init/tests/test_parse_job.c (+234/-0) util/tests/test_user_sessions.sh (+41/-0) |
||||
To merge this branch: | bzr merge lp:~broder/upstart/drop-privileges | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Hunt | Needs Fixing | ||
Review via email: mp+81417@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 1336. By Evan Broder
-
* init/man/init.5: Correct spelling typo and clarify that system jobs
which drop privilege are still system jobs, not user jobs. - 1337. By Evan Broder
-
* contrib/
vim/syntax/ upstart. vim: Add the new setuid and setgid
stanzas - 1338. By Evan Broder
-
* init/tests/
test_job_ process. c, util/tests/ test_user_ sessions. sh:
Test setuid and setgid stanzas as thoroughly as an unprivileged user
can (i.e. make sure dropping to yourself works, and escalating to
anything else doesn't) - 1339. By Evan Broder
-
* init/job_process.c: Cast signed constants to uid_t and gid_t before
comparing them with the same to correct warnings from the
build. uid_t and gid_t are unsigned, but -1 is still their dummy
value
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'contrib/vim/syntax/upstart.vim' | |||
2 | --- contrib/vim/syntax/upstart.vim 2011-06-14 13:53:27 +0000 | |||
3 | +++ contrib/vim/syntax/upstart.vim 2011-12-07 15:52:24 +0000 | |||
4 | @@ -33,7 +33,7 @@ | |||
5 | 33 | " one argument | 33 | " one argument |
6 | 34 | syn keyword upstartStatement description author version instance expect | 34 | syn keyword upstartStatement description author version instance expect |
7 | 35 | syn keyword upstartStatement pid kill normal console env exit export | 35 | syn keyword upstartStatement pid kill normal console env exit export |
9 | 36 | syn keyword upstartStatement umask nice oom chroot chdir exec | 36 | syn keyword upstartStatement umask nice oom chroot chdir exec setiud setgid |
10 | 37 | 37 | ||
11 | 38 | " two arguments | 38 | " two arguments |
12 | 39 | syn keyword upstartStatement limit | 39 | syn keyword upstartStatement limit |
13 | 40 | 40 | ||
14 | === modified file 'init/errors.h' | |||
15 | --- init/errors.h 2011-05-12 20:42:28 +0000 | |||
16 | +++ init/errors.h 2011-12-07 15:52:24 +0000 | |||
17 | @@ -36,6 +36,8 @@ | |||
18 | 36 | 36 | ||
19 | 37 | /* Errors while handling job processes */ | 37 | /* Errors while handling job processes */ |
20 | 38 | JOB_PROCESS_ERROR, | 38 | JOB_PROCESS_ERROR, |
21 | 39 | JOB_PROCESS_INVALID_SETUID, | ||
22 | 40 | JOB_PROCESS_INVALID_SETGID, | ||
23 | 39 | 41 | ||
24 | 40 | /* Errors while parsing configuration files */ | 42 | /* Errors while parsing configuration files */ |
25 | 41 | PARSE_ILLEGAL_INTERVAL, | 43 | PARSE_ILLEGAL_INTERVAL, |
26 | @@ -59,6 +61,8 @@ | |||
27 | 59 | #define ENVIRON_UNKNOWN_PARAM_STR N_("Unknown parameter") | 61 | #define ENVIRON_UNKNOWN_PARAM_STR N_("Unknown parameter") |
28 | 60 | #define ENVIRON_EXPECTED_OPERATOR_STR N_("Expected operator") | 62 | #define ENVIRON_EXPECTED_OPERATOR_STR N_("Expected operator") |
29 | 61 | #define ENVIRON_MISMATCHED_BRACES_STR N_("Mismatched braces") | 63 | #define ENVIRON_MISMATCHED_BRACES_STR N_("Mismatched braces") |
30 | 64 | #define JOB_PROCESS_INVALID_SETUID_STR N_("Invalid setuid user name does not exist") | ||
31 | 65 | #define JOB_PROCESS_INVALID_SETGID_STR N_("Invalid setgid group name does not exist") | ||
32 | 62 | #define PARSE_ILLEGAL_INTERVAL_STR N_("Illegal interval, expected number of seconds") | 66 | #define PARSE_ILLEGAL_INTERVAL_STR N_("Illegal interval, expected number of seconds") |
33 | 63 | #define PARSE_ILLEGAL_EXIT_STR N_("Illegal exit status, expected integer") | 67 | #define PARSE_ILLEGAL_EXIT_STR N_("Illegal exit status, expected integer") |
34 | 64 | #define PARSE_ILLEGAL_SIGNAL_STR N_("Illegal signal status, expected integer") | 68 | #define PARSE_ILLEGAL_SIGNAL_STR N_("Illegal signal status, expected integer") |
35 | 65 | 69 | ||
36 | === modified file 'init/job_class.c' | |||
37 | --- init/job_class.c 2011-08-11 20:47:00 +0000 | |||
38 | +++ init/job_class.c 2011-12-07 15:52:24 +0000 | |||
39 | @@ -216,6 +216,9 @@ | |||
40 | 216 | class->chroot = NULL; | 216 | class->chroot = NULL; |
41 | 217 | class->chdir = NULL; | 217 | class->chdir = NULL; |
42 | 218 | 218 | ||
43 | 219 | class->setuid = NULL; | ||
44 | 220 | class->setgid = NULL; | ||
45 | 221 | |||
46 | 219 | class->deleted = FALSE; | 222 | class->deleted = FALSE; |
47 | 220 | class->debug = FALSE; | 223 | class->debug = FALSE; |
48 | 221 | 224 | ||
49 | 222 | 225 | ||
50 | === modified file 'init/job_class.h' | |||
51 | --- init/job_class.h 2011-08-11 20:47:00 +0000 | |||
52 | +++ init/job_class.h 2011-12-07 15:52:24 +0000 | |||
53 | @@ -156,6 +156,8 @@ | |||
54 | 156 | * @limits: resource limits indexed by resource, | 156 | * @limits: resource limits indexed by resource, |
55 | 157 | * @chroot: root directory of process (implies @chdir if not set), | 157 | * @chroot: root directory of process (implies @chdir if not set), |
56 | 158 | * @chdir: working directory of process, | 158 | * @chdir: working directory of process, |
57 | 159 | * @setuid: user name to drop to before starting process, | ||
58 | 160 | * @setgid: group name to drop to before starting process, | ||
59 | 159 | * @deleted: whether job should be deleted when finished. | 161 | * @deleted: whether job should be deleted when finished. |
60 | 160 | * | 162 | * |
61 | 161 | * This structure holds the configuration of a known task or service that | 163 | * This structure holds the configuration of a known task or service that |
62 | @@ -206,6 +208,8 @@ | |||
63 | 206 | struct rlimit *limits[RLIMIT_NLIMITS]; | 208 | struct rlimit *limits[RLIMIT_NLIMITS]; |
64 | 207 | char *chroot; | 209 | char *chroot; |
65 | 208 | char *chdir; | 210 | char *chdir; |
66 | 211 | char *setuid; | ||
67 | 212 | char *setgid; | ||
68 | 209 | 213 | ||
69 | 210 | int deleted; | 214 | int deleted; |
70 | 211 | int debug; | 215 | int debug; |
71 | 212 | 216 | ||
72 | === modified file 'init/job_process.c' | |||
73 | --- init/job_process.c 2011-08-11 21:08:52 +0000 | |||
74 | +++ init/job_process.c 2011-12-07 15:52:24 +0000 | |||
75 | @@ -41,6 +41,7 @@ | |||
76 | 41 | #include <utmp.h> | 41 | #include <utmp.h> |
77 | 42 | #include <utmpx.h> | 42 | #include <utmpx.h> |
78 | 43 | #include <pwd.h> | 43 | #include <pwd.h> |
79 | 44 | #include <grp.h> | ||
80 | 44 | 45 | ||
81 | 45 | #include <nih/macros.h> | 46 | #include <nih/macros.h> |
82 | 46 | #include <nih/alloc.h> | 47 | #include <nih/alloc.h> |
83 | @@ -380,6 +381,8 @@ | |||
84 | 380 | FILE *fd; | 381 | FILE *fd; |
85 | 381 | int user_job = FALSE; | 382 | int user_job = FALSE; |
86 | 382 | nih_local char *user_dir = NULL; | 383 | nih_local char *user_dir = NULL; |
87 | 384 | uid_t job_setuid = -1; | ||
88 | 385 | gid_t job_setgid = -1; | ||
89 | 383 | 386 | ||
90 | 384 | 387 | ||
91 | 385 | nih_assert (class != NULL); | 388 | nih_assert (class != NULL); |
92 | @@ -654,6 +657,67 @@ | |||
93 | 654 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHDIR, 0); | 657 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHDIR, 0); |
94 | 655 | } | 658 | } |
95 | 656 | 659 | ||
96 | 660 | /* Change the user and group of the process to the one | ||
97 | 661 | * configured in the job. We must wait until now to lookup the | ||
98 | 662 | * UID and GID from the names to accommodate both chroot | ||
99 | 663 | * session jobs and jobs with a chroot stanza. | ||
100 | 664 | */ | ||
101 | 665 | if (class->setuid) { | ||
102 | 666 | /* Without resetting errno, it's impossible to | ||
103 | 667 | * distinguish between a non-existent user and and | ||
104 | 668 | * error during lookup */ | ||
105 | 669 | errno = 0; | ||
106 | 670 | struct passwd *pwd = getpwnam (class->setuid); | ||
107 | 671 | if (! pwd) { | ||
108 | 672 | if (errno != 0) { | ||
109 | 673 | nih_error_raise_system (); | ||
110 | 674 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWNAM, 0); | ||
111 | 675 | } else { | ||
112 | 676 | nih_error_raise (JOB_PROCESS_INVALID_SETUID, | ||
113 | 677 | JOB_PROCESS_INVALID_SETUID_STR); | ||
114 | 678 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_BAD_SETUID, 0); | ||
115 | 679 | } | ||
116 | 680 | } | ||
117 | 681 | |||
118 | 682 | job_setuid = pwd->pw_uid; | ||
119 | 683 | /* This will be overridden if setgid is also set: */ | ||
120 | 684 | job_setgid = pwd->pw_gid; | ||
121 | 685 | } | ||
122 | 686 | |||
123 | 687 | if (class->setgid) { | ||
124 | 688 | errno = 0; | ||
125 | 689 | struct group *grp = getgrnam (class->setgid); | ||
126 | 690 | if (! grp) { | ||
127 | 691 | if (errno != 0) { | ||
128 | 692 | nih_error_raise_system (); | ||
129 | 693 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETGRNAM, 0); | ||
130 | 694 | } else { | ||
131 | 695 | nih_error_raise (JOB_PROCESS_INVALID_SETGID, | ||
132 | 696 | JOB_PROCESS_INVALID_SETGID_STR); | ||
133 | 697 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_BAD_SETGID, 0); | ||
134 | 698 | } | ||
135 | 699 | } | ||
136 | 700 | |||
137 | 701 | job_setgid = grp->gr_gid; | ||
138 | 702 | } | ||
139 | 703 | |||
140 | 704 | if (script_fd != -1 && | ||
141 | 705 | (job_setuid != (uid_t) -1 || job_setgid != (gid_t) -1) && | ||
142 | 706 | fchown (script_fd, job_setuid, job_setgid) < 0) { | ||
143 | 707 | nih_error_raise_system (); | ||
144 | 708 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHOWN, 0); | ||
145 | 709 | } | ||
146 | 710 | |||
147 | 711 | if (job_setgid != (gid_t) -1 && setgid (job_setgid) < 0) { | ||
148 | 712 | nih_error_raise_system (); | ||
149 | 713 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETGID, 0); | ||
150 | 714 | } | ||
151 | 715 | |||
152 | 716 | if (job_setuid != (uid_t)-1 && setuid (job_setuid) < 0) { | ||
153 | 717 | nih_error_raise_system (); | ||
154 | 718 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETUID, 0); | ||
155 | 719 | } | ||
156 | 720 | |||
157 | 657 | /* Reset all the signal handlers back to their default handling so | 721 | /* Reset all the signal handlers back to their default handling so |
158 | 658 | * the child isn't unexpectedly ignoring any, and so we won't | 722 | * the child isn't unexpectedly ignoring any, and so we won't |
159 | 659 | * surprisingly handle them before we've exec()d the new process. | 723 | * surprisingly handle them before we've exec()d the new process. |
160 | @@ -880,6 +944,24 @@ | |||
161 | 880 | err, _("unable to execute: %s"), | 944 | err, _("unable to execute: %s"), |
162 | 881 | strerror (err->errnum))); | 945 | strerror (err->errnum))); |
163 | 882 | break; | 946 | break; |
164 | 947 | case JOB_PROCESS_ERROR_GETPWNAM: | ||
165 | 948 | err->error.message = NIH_MUST (nih_sprintf ( | ||
166 | 949 | err, _("unable to getpwnam: %s"), | ||
167 | 950 | strerror (err->errnum))); | ||
168 | 951 | break; | ||
169 | 952 | case JOB_PROCESS_ERROR_GETGRNAM: | ||
170 | 953 | err->error.message = NIH_MUST (nih_sprintf ( | ||
171 | 954 | err, _("unable to getgrnam: %s"), | ||
172 | 955 | strerror (err->errnum))); | ||
173 | 956 | break; | ||
174 | 957 | case JOB_PROCESS_ERROR_BAD_SETUID: | ||
175 | 958 | err->error.message = NIH_MUST (nih_sprintf ( | ||
176 | 959 | err, _("unable to find setuid user"))); | ||
177 | 960 | break; | ||
178 | 961 | case JOB_PROCESS_ERROR_BAD_SETGID: | ||
179 | 962 | err->error.message = NIH_MUST (nih_sprintf ( | ||
180 | 963 | err, _("unable to find setgid group"))); | ||
181 | 964 | break; | ||
182 | 883 | case JOB_PROCESS_ERROR_SETUID: | 965 | case JOB_PROCESS_ERROR_SETUID: |
183 | 884 | err->error.message = NIH_MUST (nih_sprintf ( | 966 | err->error.message = NIH_MUST (nih_sprintf ( |
184 | 885 | err, _("unable to setuid: %s"), | 967 | err, _("unable to setuid: %s"), |
185 | 886 | 968 | ||
186 | === modified file 'init/job_process.h' | |||
187 | --- init/job_process.h 2011-07-25 14:08:47 +0000 | |||
188 | +++ init/job_process.h 2011-12-07 15:52:24 +0000 | |||
189 | @@ -57,6 +57,10 @@ | |||
190 | 57 | JOB_PROCESS_ERROR_CHDIR, | 57 | JOB_PROCESS_ERROR_CHDIR, |
191 | 58 | JOB_PROCESS_ERROR_PTRACE, | 58 | JOB_PROCESS_ERROR_PTRACE, |
192 | 59 | JOB_PROCESS_ERROR_EXEC, | 59 | JOB_PROCESS_ERROR_EXEC, |
193 | 60 | JOB_PROCESS_ERROR_GETPWNAM, | ||
194 | 61 | JOB_PROCESS_ERROR_GETGRNAM, | ||
195 | 62 | JOB_PROCESS_ERROR_BAD_SETUID, | ||
196 | 63 | JOB_PROCESS_ERROR_BAD_SETGID, | ||
197 | 60 | JOB_PROCESS_ERROR_SETUID, | 64 | JOB_PROCESS_ERROR_SETUID, |
198 | 61 | JOB_PROCESS_ERROR_SETGID, | 65 | JOB_PROCESS_ERROR_SETGID, |
199 | 62 | JOB_PROCESS_ERROR_CHOWN | 66 | JOB_PROCESS_ERROR_CHOWN |
200 | 63 | 67 | ||
201 | === modified file 'init/man/init.5' | |||
202 | --- init/man/init.5 2011-07-25 15:01:24 +0000 | |||
203 | +++ init/man/init.5 2011-12-07 15:52:24 +0000 | |||
204 | @@ -659,6 +659,32 @@ | |||
205 | 659 | .B unlimited | 659 | .B unlimited |
206 | 660 | may be specified for either. | 660 | may be specified for either. |
207 | 661 | .\" | 661 | .\" |
208 | 662 | .TP | ||
209 | 663 | .B setuid \fIUSERNAME | ||
210 | 664 | Changes to the user | ||
211 | 665 | .I USERNAME | ||
212 | 666 | before running the job's process. | ||
213 | 667 | |||
214 | 668 | If this stanza is unspecified, the job will run as root in the case of | ||
215 | 669 | system jobs, and as the user in the case of User Jobs. | ||
216 | 670 | |||
217 | 671 | Note that System jobs using the setuid stanza are still system jobs, | ||
218 | 672 | and can not be controlled by an unprivileged user, even if the setuid | ||
219 | 673 | stanza specifies that user. | ||
220 | 674 | .\" | ||
221 | 675 | .TP | ||
222 | 676 | .B setgid \fIGROUPNAME | ||
223 | 677 | Changes to the group | ||
224 | 678 | .I GROUPNAME | ||
225 | 679 | before running the job's process. | ||
226 | 680 | |||
227 | 681 | If this stanza is unspecified, the primary group of the user specified | ||
228 | 682 | in the | ||
229 | 683 | .B setuid | ||
230 | 684 | block is used. If both stanzas are unspecified, the job will run with | ||
231 | 685 | its group ID set to 0 in the case of system jobs, and as the primary | ||
232 | 686 | group of the user in the case of User Jobs. | ||
233 | 687 | .\" | ||
234 | 662 | .SS Override File Handling | 688 | .SS Override File Handling |
235 | 663 | Override files allow a jobs environment to be changed without modifying | 689 | Override files allow a jobs environment to be changed without modifying |
236 | 664 | the jobs configuration file. Rules governing override files: | 690 | the jobs configuration file. Rules governing override files: |
237 | 665 | 691 | ||
238 | === modified file 'init/parse_job.c' | |||
239 | --- init/parse_job.c 2011-06-06 12:52:08 +0000 | |||
240 | +++ init/parse_job.c 2011-12-07 15:52:24 +0000 | |||
241 | @@ -209,6 +209,14 @@ | |||
242 | 209 | const char *file, size_t len, | 209 | const char *file, size_t len, |
243 | 210 | size_t *pos, size_t *lineno) | 210 | size_t *pos, size_t *lineno) |
244 | 211 | __attribute__ ((warn_unused_result)); | 211 | __attribute__ ((warn_unused_result)); |
245 | 212 | static int stanza_setuid (JobClass *class, NihConfigStanza *stanza, | ||
246 | 213 | const char *file, size_t len, | ||
247 | 214 | size_t *pos, size_t *lineno) | ||
248 | 215 | __attribute__ ((warn_unused_result)); | ||
249 | 216 | static int stanza_setgid (JobClass *class, NihConfigStanza *stanza, | ||
250 | 217 | const char *file, size_t len, | ||
251 | 218 | size_t *pos, size_t *lineno) | ||
252 | 219 | __attribute__ ((warn_unused_result)); | ||
253 | 212 | static int stanza_debug (JobClass *class, NihConfigStanza *stanza, | 220 | static int stanza_debug (JobClass *class, NihConfigStanza *stanza, |
254 | 213 | const char *file, size_t len, | 221 | const char *file, size_t len, |
255 | 214 | size_t *pos, size_t *lineno) | 222 | size_t *pos, size_t *lineno) |
256 | @@ -253,6 +261,8 @@ | |||
257 | 253 | { "limit", (NihConfigHandler)stanza_limit }, | 261 | { "limit", (NihConfigHandler)stanza_limit }, |
258 | 254 | { "chroot", (NihConfigHandler)stanza_chroot }, | 262 | { "chroot", (NihConfigHandler)stanza_chroot }, |
259 | 255 | { "chdir", (NihConfigHandler)stanza_chdir }, | 263 | { "chdir", (NihConfigHandler)stanza_chdir }, |
260 | 264 | { "setuid", (NihConfigHandler)stanza_setuid }, | ||
261 | 265 | { "setgid", (NihConfigHandler)stanza_setgid }, | ||
262 | 256 | { "debug", (NihConfigHandler)stanza_debug }, | 266 | { "debug", (NihConfigHandler)stanza_debug }, |
263 | 257 | { "manual", (NihConfigHandler)stanza_manual }, | 267 | { "manual", (NihConfigHandler)stanza_manual }, |
264 | 258 | 268 | ||
265 | @@ -2538,3 +2548,77 @@ | |||
266 | 2538 | 2548 | ||
267 | 2539 | return nih_config_skip_comment (file, len, pos, lineno); | 2549 | return nih_config_skip_comment (file, len, pos, lineno); |
268 | 2540 | } | 2550 | } |
269 | 2551 | |||
270 | 2552 | /** | ||
271 | 2553 | * stanza_setuid: | ||
272 | 2554 | * @class: job class being parsed, | ||
273 | 2555 | * @stanza: stanza found, | ||
274 | 2556 | * @file: file or string to parse, | ||
275 | 2557 | * @len: length of @file, | ||
276 | 2558 | * @pos: offset within @file, | ||
277 | 2559 | * @lineno: line number. | ||
278 | 2560 | * | ||
279 | 2561 | * Parse a setuid stanza from @file, extracting a single argument | ||
280 | 2562 | * containing a user name. | ||
281 | 2563 | * | ||
282 | 2564 | * Returns: zero on success, negative value on error. | ||
283 | 2565 | **/ | ||
284 | 2566 | static int | ||
285 | 2567 | stanza_setuid (JobClass *class, | ||
286 | 2568 | NihConfigStanza *stanza, | ||
287 | 2569 | const char *file, | ||
288 | 2570 | size_t len, | ||
289 | 2571 | size_t *pos, | ||
290 | 2572 | size_t *lineno) | ||
291 | 2573 | { | ||
292 | 2574 | nih_assert (class != NULL); | ||
293 | 2575 | nih_assert (stanza != NULL); | ||
294 | 2576 | nih_assert (file != NULL); | ||
295 | 2577 | nih_assert (pos != NULL); | ||
296 | 2578 | |||
297 | 2579 | if (class->setuid) | ||
298 | 2580 | nih_unref (class->setuid, class); | ||
299 | 2581 | |||
300 | 2582 | class->setuid = nih_config_next_arg (class, file, len, pos, lineno); | ||
301 | 2583 | if (! class->setuid) | ||
302 | 2584 | return -1; | ||
303 | 2585 | |||
304 | 2586 | return nih_config_skip_comment (file, len, pos, lineno); | ||
305 | 2587 | } | ||
306 | 2588 | |||
307 | 2589 | /** | ||
308 | 2590 | * stanza_setgid: | ||
309 | 2591 | * @class: job class being parsed, | ||
310 | 2592 | * @stanza: stanza found, | ||
311 | 2593 | * @file: file or string to parse, | ||
312 | 2594 | * @len: length of @file, | ||
313 | 2595 | * @pos: offset within @file, | ||
314 | 2596 | * @lineno: line number. | ||
315 | 2597 | * | ||
316 | 2598 | * Parse a setgid stanza from @file, extracting a single argument | ||
317 | 2599 | * containing a group name. | ||
318 | 2600 | * | ||
319 | 2601 | * Returns: zero on success, negative value on error. | ||
320 | 2602 | **/ | ||
321 | 2603 | static int | ||
322 | 2604 | stanza_setgid (JobClass *class, | ||
323 | 2605 | NihConfigStanza *stanza, | ||
324 | 2606 | const char *file, | ||
325 | 2607 | size_t len, | ||
326 | 2608 | size_t *pos, | ||
327 | 2609 | size_t *lineno) | ||
328 | 2610 | { | ||
329 | 2611 | nih_assert (class != NULL); | ||
330 | 2612 | nih_assert (stanza != NULL); | ||
331 | 2613 | nih_assert (file != NULL); | ||
332 | 2614 | nih_assert (pos != NULL); | ||
333 | 2615 | |||
334 | 2616 | if (class->setgid) | ||
335 | 2617 | nih_unref (class->setgid, class); | ||
336 | 2618 | |||
337 | 2619 | class->setgid = nih_config_next_arg (class, file, len, pos, lineno); | ||
338 | 2620 | if (! class->setgid) | ||
339 | 2621 | return -1; | ||
340 | 2622 | |||
341 | 2623 | return nih_config_skip_comment (file, len, pos, lineno); | ||
342 | 2624 | } | ||
343 | 2541 | 2625 | ||
344 | === modified file 'init/tests/test_job_class.c' | |||
345 | --- init/tests/test_job_class.c 2011-06-06 17:05:11 +0000 | |||
346 | +++ init/tests/test_job_class.c 2011-12-07 15:52:24 +0000 | |||
347 | @@ -139,6 +139,10 @@ | |||
348 | 139 | 139 | ||
349 | 140 | TEST_EQ_P (class->chroot, NULL); | 140 | TEST_EQ_P (class->chroot, NULL); |
350 | 141 | TEST_EQ_P (class->chdir, NULL); | 141 | TEST_EQ_P (class->chdir, NULL); |
351 | 142 | |||
352 | 143 | TEST_EQ_P (class->setuid, NULL); | ||
353 | 144 | TEST_EQ_P (class->setgid, NULL); | ||
354 | 145 | |||
355 | 142 | TEST_FALSE (class->deleted); | 146 | TEST_FALSE (class->deleted); |
356 | 143 | 147 | ||
357 | 144 | nih_free (class); | 148 | nih_free (class); |
358 | 145 | 149 | ||
359 | === modified file 'init/tests/test_job_process.c' | |||
360 | --- init/tests/test_job_process.c 2011-06-06 17:05:11 +0000 | |||
361 | +++ init/tests/test_job_process.c 2011-12-07 15:52:24 +0000 | |||
362 | @@ -38,6 +38,8 @@ | |||
363 | 38 | #include <unistd.h> | 38 | #include <unistd.h> |
364 | 39 | #include <utmp.h> | 39 | #include <utmp.h> |
365 | 40 | #include <utmpx.h> | 40 | #include <utmpx.h> |
366 | 41 | #include <pwd.h> | ||
367 | 42 | #include <grp.h> | ||
368 | 41 | 43 | ||
369 | 42 | #include <nih/macros.h> | 44 | #include <nih/macros.h> |
370 | 43 | #include <nih/string.h> | 45 | #include <nih/string.h> |
371 | @@ -124,13 +126,15 @@ | |||
372 | 124 | void | 126 | void |
373 | 125 | test_run (void) | 127 | test_run (void) |
374 | 126 | { | 128 | { |
382 | 127 | JobClass *class = NULL; | 129 | JobClass *class = NULL; |
383 | 128 | Job *job = NULL; | 130 | Job *job = NULL; |
384 | 129 | FILE *output; | 131 | FILE *output; |
385 | 130 | struct stat statbuf; | 132 | struct stat statbuf; |
386 | 131 | char filename[PATH_MAX], buf[80]; | 133 | char filename[PATH_MAX], buf[80]; |
387 | 132 | int ret = -1, status, first; | 134 | int ret = -1, status, first; |
388 | 133 | siginfo_t info; | 135 | siginfo_t info; |
389 | 136 | struct passwd *pwd; | ||
390 | 137 | struct group *grp; | ||
391 | 134 | 138 | ||
392 | 135 | TEST_FUNCTION ("job_process_run"); | 139 | TEST_FUNCTION ("job_process_run"); |
393 | 136 | job_class_init (); | 140 | job_class_init (); |
394 | @@ -787,6 +791,44 @@ | |||
395 | 787 | 791 | ||
396 | 788 | nih_free (class); | 792 | nih_free (class); |
397 | 789 | } | 793 | } |
398 | 794 | |||
399 | 795 | /* Check that we can succesfully setuid and setgid to | ||
400 | 796 | * ourselves. This should always work, privileged or | ||
401 | 797 | * otherwise. | ||
402 | 798 | */ | ||
403 | 799 | TEST_FEATURE ("with setuid me"); | ||
404 | 800 | TEST_ALLOC_FAIL { | ||
405 | 801 | TEST_ALLOC_SAFE { | ||
406 | 802 | class = job_class_new (NULL, "test", NULL); | ||
407 | 803 | class->process[PROCESS_MAIN] = process_new (class); | ||
408 | 804 | class->process[PROCESS_MAIN]->command = nih_sprintf ( | ||
409 | 805 | class->process[PROCESS_MAIN], | ||
410 | 806 | "touch %s", filename); | ||
411 | 807 | |||
412 | 808 | pwd = getpwuid (getuid ()); | ||
413 | 809 | TEST_NE (pwd, NULL); | ||
414 | 810 | class->setuid = nih_strdup (class, pwd->pw_name); | ||
415 | 811 | |||
416 | 812 | grp = getgrgid (getgid ()); | ||
417 | 813 | TEST_NE (grp, NULL); | ||
418 | 814 | class->setuid = nih_strdup (class, grp->gr_name); | ||
419 | 815 | |||
420 | 816 | job = job_new (class, ""); | ||
421 | 817 | job->goal = JOB_START; | ||
422 | 818 | job->state = JOB_SPAWNED; | ||
423 | 819 | } | ||
424 | 820 | |||
425 | 821 | ret = job_process_run (job, PROCESS_MAIN); | ||
426 | 822 | TEST_EQ (ret, 0); | ||
427 | 823 | |||
428 | 824 | TEST_NE (job->pid[PROCESS_MAIN], 0); | ||
429 | 825 | |||
430 | 826 | waitpid (job->pid[PROCESS_MAIN], NULL, 0); | ||
431 | 827 | TEST_EQ (stat (filename, &statbuf), 0); | ||
432 | 828 | |||
433 | 829 | unlink (filename); | ||
434 | 830 | nih_free (class); | ||
435 | 831 | } | ||
436 | 790 | } | 832 | } |
437 | 791 | 833 | ||
438 | 792 | 834 | ||
439 | 793 | 835 | ||
440 | === modified file 'init/tests/test_parse_job.c' | |||
441 | --- init/tests/test_parse_job.c 2011-06-06 12:52:08 +0000 | |||
442 | +++ init/tests/test_parse_job.c 2011-12-07 15:52:24 +0000 | |||
443 | @@ -7933,6 +7933,238 @@ | |||
444 | 7933 | nih_free (err); | 7933 | nih_free (err); |
445 | 7934 | } | 7934 | } |
446 | 7935 | 7935 | ||
447 | 7936 | void | ||
448 | 7937 | test_stanza_setuid (void) | ||
449 | 7938 | { | ||
450 | 7939 | JobClass*job; | ||
451 | 7940 | NihError *err; | ||
452 | 7941 | size_t pos, lineno; | ||
453 | 7942 | char buf[1024]; | ||
454 | 7943 | |||
455 | 7944 | TEST_FUNCTION ("stanza_setuid"); | ||
456 | 7945 | |||
457 | 7946 | /* Check that a setuid stanza with an argument results in it | ||
458 | 7947 | * being stored in the job. | ||
459 | 7948 | */ | ||
460 | 7949 | TEST_FEATURE ("with single argument"); | ||
461 | 7950 | strcpy (buf, "setuid www-data\n"); | ||
462 | 7951 | |||
463 | 7952 | TEST_ALLOC_FAIL { | ||
464 | 7953 | pos = 0; | ||
465 | 7954 | lineno = 1; | ||
466 | 7955 | job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), | ||
467 | 7956 | &pos, &lineno); | ||
468 | 7957 | |||
469 | 7958 | if (test_alloc_failed) { | ||
470 | 7959 | TEST_EQ_P (job, NULL); | ||
471 | 7960 | |||
472 | 7961 | err = nih_error_get (); | ||
473 | 7962 | TEST_EQ (err->number, ENOMEM); | ||
474 | 7963 | nih_free (err); | ||
475 | 7964 | |||
476 | 7965 | continue; | ||
477 | 7966 | } | ||
478 | 7967 | |||
479 | 7968 | TEST_EQ (pos, strlen (buf)); | ||
480 | 7969 | TEST_EQ (lineno, 2); | ||
481 | 7970 | |||
482 | 7971 | TEST_ALLOC_SIZE (job, sizeof (JobClass)); | ||
483 | 7972 | |||
484 | 7973 | TEST_ALLOC_PARENT (job->setuid, job); | ||
485 | 7974 | TEST_EQ_STR (job->setuid, "www-data"); | ||
486 | 7975 | |||
487 | 7976 | nih_free (job); | ||
488 | 7977 | } | ||
489 | 7978 | |||
490 | 7979 | |||
491 | 7980 | /* Check that the last of multiple setuid stanzas is used. | ||
492 | 7981 | */ | ||
493 | 7982 | TEST_FEATURE ("with multiple stanzas"); | ||
494 | 7983 | strcpy (buf, "setuid www-data\n"); | ||
495 | 7984 | strcat (buf, "setuid pulse\n"); | ||
496 | 7985 | |||
497 | 7986 | TEST_ALLOC_FAIL { | ||
498 | 7987 | pos = 0; | ||
499 | 7988 | lineno = 1; | ||
500 | 7989 | job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), | ||
501 | 7990 | &pos, &lineno); | ||
502 | 7991 | |||
503 | 7992 | if (test_alloc_failed) { | ||
504 | 7993 | TEST_EQ_P (job, NULL); | ||
505 | 7994 | |||
506 | 7995 | err = nih_error_get (); | ||
507 | 7996 | TEST_EQ (err->number, ENOMEM); | ||
508 | 7997 | nih_free (err); | ||
509 | 7998 | |||
510 | 7999 | continue; | ||
511 | 8000 | } | ||
512 | 8001 | |||
513 | 8002 | TEST_EQ (pos, strlen (buf)); | ||
514 | 8003 | TEST_EQ (lineno, 3); | ||
515 | 8004 | |||
516 | 8005 | TEST_ALLOC_SIZE (job, sizeof (JobClass)); | ||
517 | 8006 | |||
518 | 8007 | TEST_ALLOC_PARENT (job->setuid, job); | ||
519 | 8008 | TEST_EQ_STR (job->setuid, "pulse"); | ||
520 | 8009 | |||
521 | 8010 | nih_free (job); | ||
522 | 8011 | } | ||
523 | 8012 | |||
524 | 8013 | |||
525 | 8014 | /* Check that a setuid stanza without an argument results in | ||
526 | 8015 | * a syntax error. | ||
527 | 8016 | */ | ||
528 | 8017 | TEST_FEATURE ("with missing argument"); | ||
529 | 8018 | strcpy (buf, "setuid\n"); | ||
530 | 8019 | |||
531 | 8020 | pos = 0; | ||
532 | 8021 | lineno = 1; | ||
533 | 8022 | job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); | ||
534 | 8023 | |||
535 | 8024 | TEST_EQ_P (job, NULL); | ||
536 | 8025 | |||
537 | 8026 | err = nih_error_get (); | ||
538 | 8027 | TEST_EQ (err->number, NIH_CONFIG_EXPECTED_TOKEN); | ||
539 | 8028 | TEST_EQ (pos, 6); | ||
540 | 8029 | TEST_EQ (lineno, 1); | ||
541 | 8030 | nih_free (err); | ||
542 | 8031 | |||
543 | 8032 | |||
544 | 8033 | /* Check that a setuid stanza with an extra second argument | ||
545 | 8034 | * results in a syntax error. | ||
546 | 8035 | */ | ||
547 | 8036 | TEST_FEATURE ("with extra argument"); | ||
548 | 8037 | strcpy (buf, "setuid www-data foo\n"); | ||
549 | 8038 | |||
550 | 8039 | pos = 0; | ||
551 | 8040 | lineno = 1; | ||
552 | 8041 | job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); | ||
553 | 8042 | |||
554 | 8043 | TEST_EQ_P (job, NULL); | ||
555 | 8044 | |||
556 | 8045 | err = nih_error_get (); | ||
557 | 8046 | TEST_EQ (err->number, NIH_CONFIG_UNEXPECTED_TOKEN); | ||
558 | 8047 | TEST_EQ (pos, 16); | ||
559 | 8048 | TEST_EQ (lineno, 1); | ||
560 | 8049 | nih_free (err); | ||
561 | 8050 | } | ||
562 | 8051 | |||
563 | 8052 | void | ||
564 | 8053 | test_stanza_setgid (void) | ||
565 | 8054 | { | ||
566 | 8055 | JobClass*job; | ||
567 | 8056 | NihError *err; | ||
568 | 8057 | size_t pos, lineno; | ||
569 | 8058 | char buf[1024]; | ||
570 | 8059 | |||
571 | 8060 | TEST_FUNCTION ("stanza_setgid"); | ||
572 | 8061 | |||
573 | 8062 | /* Check that a setgid stanza with an argument results in it | ||
574 | 8063 | * being stored in the job. | ||
575 | 8064 | */ | ||
576 | 8065 | TEST_FEATURE ("with single argument"); | ||
577 | 8066 | strcpy (buf, "setgid kvm\n"); | ||
578 | 8067 | |||
579 | 8068 | TEST_ALLOC_FAIL { | ||
580 | 8069 | pos = 0; | ||
581 | 8070 | lineno = 1; | ||
582 | 8071 | job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), | ||
583 | 8072 | &pos, &lineno); | ||
584 | 8073 | |||
585 | 8074 | if (test_alloc_failed) { | ||
586 | 8075 | TEST_EQ_P (job, NULL); | ||
587 | 8076 | |||
588 | 8077 | err = nih_error_get (); | ||
589 | 8078 | TEST_EQ (err->number, ENOMEM); | ||
590 | 8079 | nih_free (err); | ||
591 | 8080 | |||
592 | 8081 | continue; | ||
593 | 8082 | } | ||
594 | 8083 | |||
595 | 8084 | TEST_EQ (pos, strlen (buf)); | ||
596 | 8085 | TEST_EQ (lineno, 2); | ||
597 | 8086 | |||
598 | 8087 | TEST_ALLOC_SIZE (job, sizeof (JobClass)); | ||
599 | 8088 | |||
600 | 8089 | TEST_ALLOC_PARENT (job->setgid, job); | ||
601 | 8090 | TEST_EQ_STR (job->setgid, "kvm"); | ||
602 | 8091 | |||
603 | 8092 | nih_free (job); | ||
604 | 8093 | } | ||
605 | 8094 | |||
606 | 8095 | |||
607 | 8096 | /* Check that the last of multiple setgid stanzas is used. | ||
608 | 8097 | */ | ||
609 | 8098 | TEST_FEATURE ("with multiple stanzas"); | ||
610 | 8099 | strcpy (buf, "setgid kvm\n"); | ||
611 | 8100 | strcat (buf, "setgid fuse\n"); | ||
612 | 8101 | |||
613 | 8102 | TEST_ALLOC_FAIL { | ||
614 | 8103 | pos = 0; | ||
615 | 8104 | lineno = 1; | ||
616 | 8105 | job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), | ||
617 | 8106 | &pos, &lineno); | ||
618 | 8107 | |||
619 | 8108 | if (test_alloc_failed) { | ||
620 | 8109 | TEST_EQ_P (job, NULL); | ||
621 | 8110 | |||
622 | 8111 | err = nih_error_get (); | ||
623 | 8112 | TEST_EQ (err->number, ENOMEM); | ||
624 | 8113 | nih_free (err); | ||
625 | 8114 | |||
626 | 8115 | continue; | ||
627 | 8116 | } | ||
628 | 8117 | |||
629 | 8118 | TEST_EQ (pos, strlen (buf)); | ||
630 | 8119 | TEST_EQ (lineno, 3); | ||
631 | 8120 | |||
632 | 8121 | TEST_ALLOC_SIZE (job, sizeof (JobClass)); | ||
633 | 8122 | |||
634 | 8123 | TEST_ALLOC_PARENT (job->setgid, job); | ||
635 | 8124 | TEST_EQ_STR (job->setgid, "fuse"); | ||
636 | 8125 | |||
637 | 8126 | nih_free (job); | ||
638 | 8127 | } | ||
639 | 8128 | |||
640 | 8129 | |||
641 | 8130 | /* Check that a setgid stanza without an argument results in | ||
642 | 8131 | * a syntax error. | ||
643 | 8132 | */ | ||
644 | 8133 | TEST_FEATURE ("with missing argument"); | ||
645 | 8134 | strcpy (buf, "setgid\n"); | ||
646 | 8135 | |||
647 | 8136 | pos = 0; | ||
648 | 8137 | lineno = 1; | ||
649 | 8138 | job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); | ||
650 | 8139 | |||
651 | 8140 | TEST_EQ_P (job, NULL); | ||
652 | 8141 | |||
653 | 8142 | err = nih_error_get (); | ||
654 | 8143 | TEST_EQ (err->number, NIH_CONFIG_EXPECTED_TOKEN); | ||
655 | 8144 | TEST_EQ (pos, 6); | ||
656 | 8145 | TEST_EQ (lineno, 1); | ||
657 | 8146 | nih_free (err); | ||
658 | 8147 | |||
659 | 8148 | |||
660 | 8149 | /* Check that a setgid stanza with an extra second argument | ||
661 | 8150 | * results in a syntax error. | ||
662 | 8151 | */ | ||
663 | 8152 | TEST_FEATURE ("with extra argument"); | ||
664 | 8153 | strcpy (buf, "setgid kvm foo\n"); | ||
665 | 8154 | |||
666 | 8155 | pos = 0; | ||
667 | 8156 | lineno = 1; | ||
668 | 8157 | job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); | ||
669 | 8158 | |||
670 | 8159 | TEST_EQ_P (job, NULL); | ||
671 | 8160 | |||
672 | 8161 | err = nih_error_get (); | ||
673 | 8162 | TEST_EQ (err->number, NIH_CONFIG_UNEXPECTED_TOKEN); | ||
674 | 8163 | TEST_EQ (pos, 11); | ||
675 | 8164 | TEST_EQ (lineno, 1); | ||
676 | 8165 | nih_free (err); | ||
677 | 8166 | } | ||
678 | 8167 | |||
679 | 7936 | int | 8168 | int |
680 | 7937 | main (int argc, | 8169 | main (int argc, |
681 | 7938 | char *argv[]) | 8170 | char *argv[]) |
682 | @@ -7979,6 +8211,8 @@ | |||
683 | 7979 | test_stanza_limit (); | 8211 | test_stanza_limit (); |
684 | 7980 | test_stanza_chroot (); | 8212 | test_stanza_chroot (); |
685 | 7981 | test_stanza_chdir (); | 8213 | test_stanza_chdir (); |
686 | 8214 | test_stanza_setuid (); | ||
687 | 8215 | test_stanza_setgid (); | ||
688 | 7982 | 8216 | ||
689 | 7983 | return 0; | 8217 | return 0; |
690 | 7984 | } | 8218 | } |
691 | 7985 | 8219 | ||
692 | === modified file 'util/tests/test_user_sessions.sh' | |||
693 | --- util/tests/test_user_sessions.sh 2011-07-25 14:49:17 +0000 | |||
694 | +++ util/tests/test_user_sessions.sh 2011-12-07 15:52:24 +0000 | |||
695 | @@ -470,6 +470,45 @@ | |||
696 | 470 | rm -f "$job_file" | 470 | rm -f "$job_file" |
697 | 471 | } | 471 | } |
698 | 472 | 472 | ||
699 | 473 | test_user_job_setuid_setgid() | ||
700 | 474 | { | ||
701 | 475 | group="user job with setuid and setgid me" | ||
702 | 476 | job_name="setuid_setgid_me_test" | ||
703 | 477 | script="\ | ||
704 | 478 | setuid $(id -un) | ||
705 | 479 | setgid $(id -gn) | ||
706 | 480 | exec true" | ||
707 | 481 | test_user_job "$group" "$job_name" "$script" no "" | ||
708 | 482 | |||
709 | 483 | TEST_GROUP "user job with setuid and setgid root" | ||
710 | 484 | script="\ | ||
711 | 485 | setuid root | ||
712 | 486 | setgid root | ||
713 | 487 | exec true" | ||
714 | 488 | |||
715 | 489 | job_name="setuid_setgid_root_test" | ||
716 | 490 | job_file="${test_dir}/${job_name}.conf" | ||
717 | 491 | job="${test_dir_suffix}/${job_name}" | ||
718 | 492 | |||
719 | 493 | echo "$script" > $job_file | ||
720 | 494 | |||
721 | 495 | ensure_job_known "$job" "$job_name" | ||
722 | 496 | |||
723 | 497 | TEST_FEATURE "ensure job fails to start as root" | ||
724 | 498 | cmd="start ${job}" | ||
725 | 499 | output=$(eval "$cmd") | ||
726 | 500 | rc=$? | ||
727 | 501 | TEST_EQ "$cmd" $rc 1 | ||
728 | 502 | |||
729 | 503 | TEST_FEATURE "ensure 'start' indicates job failure" | ||
730 | 504 | error=$(echo "$output"|grep failed) | ||
731 | 505 | TEST_NE "error" "$error" "" | ||
732 | 506 | |||
733 | 507 | TEST_FEATURE "ensure 'initctl' does not list job" | ||
734 | 508 | initctl list|grep -q "^$job stop/waiting" || \ | ||
735 | 509 | TEST_FAILED "job $job_name not listed as stopped" | ||
736 | 510 | } | ||
737 | 511 | |||
738 | 473 | test_user_jobs() | 512 | test_user_jobs() |
739 | 474 | { | 513 | { |
740 | 475 | test_user_job_binary | 514 | test_user_job_binary |
741 | @@ -480,6 +519,8 @@ | |||
742 | 480 | test_user_job_single_line_script_task | 519 | test_user_job_single_line_script_task |
743 | 481 | test_user_job_multi_line_script_task | 520 | test_user_job_multi_line_script_task |
744 | 482 | 521 | ||
745 | 522 | test_user_job_setuid_setgid | ||
746 | 523 | |||
747 | 483 | test_user_emit_events | 524 | test_user_emit_events |
748 | 484 | } | 525 | } |
749 | 485 | 526 |
* init/man/init.5:
- Typo 'unspceified'.
- To avoid any confusion, I think it's worth explaining that system jobs which specify "setuid X"
cannot be controlled by user X since they are not user jobs: they are simply jobs running as the
user in question [*].
* init/job_ process. c:job_process_ spawn() :
- That fchown() looks potentially unsafe:
if job_setgid != -1, but job_setuid == -1, we get:
fchown (script_fd, -1, job_setgid);
However, something like the following is also too simplistic...
fchown (script_fd,
job_setuid == -1 ? 0 : job_setuid,
job_setgid == -1 ? 0 : job_setgid);
... since it doesn't take account of user jobs (and in my snippet above could allow privilege
escalation).
- Resource Limits/OOM/Priority
The fact that the setgid+setuid calls come *after* the chroot+user session code means we're
effectively allowing non-privileged (system) jobs to elevate their resource limits, OOM score and
priority. It could be argued that these are after all system jobs, so why not allow such behaviour?
But until compelling examples are given, I'd prefer we take the cautious approach and disallowed
this behaviour. Again, to avoid confusion we should document in init.5 that system jobs running as
non-privileged users cannot elevate their resource limits beyond that users limits.
* Testing
Yes, I appreciate the issues testing some of these scenarios. It will admittedly complicated by
adding setuid+setgid support to the already interesting combination of user jobs and chroot support
What we need is a fully automated set of tests for these features. Effort is being put into this
for the current Ubuntu cycle.
What I would recommend is adding some tests to util/tests/ test_user_ sessions. sh that...
- essentially duplicate the tests you've created in test_parse_job.c to ensure that say specifying
multiple values after the setuid stanza fails as expected.
- ensure 'setuid root' fails as expected
- ensure 'setdid root' fails as expected
- ensure 'setgid <group>' works (assuming <group> is the users primary group).
- ensure 'setgid <group>' works (assuming <group> is a valid supplementary group).
Also, could you add a test to init/tests/ test_job_ process. c that ensures it is possible to run a job
which specifies the *current* user and group of the user running the tests (essentially specifying
"setuid `id -u`" and "setgid `id -g`").