Merge lp:~mdeslaur/upstart/apparmor-support into lp:upstart
- apparmor-support
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1476 |
Proposed branch: | lp:~mdeslaur/upstart/apparmor-support |
Merge into: | lp:upstart |
Diff against target: |
1899 lines (+1133/-199) 21 files modified
ChangeLog (+52/-0) init/Makefile.am (+16/-15) init/apparmor.c (+116/-0) init/apparmor.h (+52/-0) init/errors.h (+3/-0) init/job.c (+64/-8) init/job.h (+1/-0) init/job_class.c (+13/-0) init/job_class.h (+3/-0) init/job_process.c (+212/-170) init/job_process.h (+2/-1) init/man/init.5 (+32/-0) init/parse_job.c (+109/-0) init/process.c (+6/-0) init/process.h (+1/-0) init/tests/data/upstart-pre-security.json (+1/-0) init/tests/test_job.c (+36/-5) init/tests/test_job_class.c (+2/-0) init/tests/test_parse_job.c (+263/-0) init/tests/test_process.c (+14/-0) init/tests/test_state.c (+135/-0) |
To merge this branch: | bzr merge lp:~mdeslaur/upstart/apparmor-support |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Hunt | Approve | ||
Stéphane Graber (community) | Approve | ||
Marc Deslauriers (community) | Needs Resubmitting | ||
Review via email: mp+164169@code.launchpad.net |
Commit message
Description of the change
This merge request adds native AppArmor support in Upstart by introducing a new process type that can be shared with other Mandatory Access Control frameworks.
The new AppArmor stanzas allow specifying an AppArmor profile to load at job start, and a profile to switch to before running the main process. One interesting use case for switching AppArmor profiles is to confine applications started with jobs in User Session mode without relying on automatic path attachment.
If the running kernel doesn't have AppArmor enabled, or if the AppArmor tools aren't installed, the stanzas are simply ignored, allowing package maintainers to include AppArmor profiles and stanzas in their packages that will work on both distros that enable AppArmor by default, and distros that don't.
James Hunt (jamesodhunt) wrote : | # |
- 1478. By Marc Deslauriers
-
init/process.c: Fix deserialising with PROCESS_SECURITY.
- 1479. By Marc Deslauriers
-
Added test for serialisation data with new security elements.
Marc Deslauriers (mdeslaur) wrote : | # |
Excellent comment. I've added the requested test, and it uncovered a bug with deserialisation, which I've fixed also. Thanks!
- 1480. By Marc Deslauriers
-
An unused process is supposed to be NULL. Fix deserialiser and test.
Marc Deslauriers (mdeslaur) wrote : | # |
Testing uncovered an issue when deserialising state from an older version. Fixed.
- 1481. By Marc Deslauriers
-
Allow environment variables in apparmor switch stanzas.
Stéphane Graber (stgraber) wrote : | # |
Hi,
Thanks for this work. I had a quick look through the merge proposal and it looks pretty good.
I just have a question regarding "apparmor load". It's currently restricted to non-usermode presumably because you want to ensure that the user is indeed allowed to load a new profile into apparmor.
So what happens when you run upstart without the usermode but as a user (init --session)?
Also, is there any reason not to allow loading extra apparmor profiles when running in user mode with the user being root?
Marc Deslauriers (mdeslaur) wrote : | # |
> So what happens when you run upstart without the usermode but as a user (init --session)?
> Also, is there any reason not to allow loading extra apparmor profiles when running in user mode with the user being root?
I didn't realize these were common scenarios.
Perhaps it should simply fail a job if it contains an "apparmor load" stanza and the user doesn't have appropriate permissions?
Stéphane Graber (stgraber) wrote : | # |
I think that'd make sense, that's how setuid/setgid and other privileged stanzas behave currently so it'd be consistent.
Running init against the session bus and without user mode isn't very common, the biggest use case for this is development.
The use case for running a session init as root isn't yet present in Ubuntu but may be in the relatively near future where we want to have upstart spawned by a PAM plugin even for text consoles (exact details are yet to be specced) so in that case you'd get a Session Init for the root user.
- 1482. By Marc Deslauriers
-
Don't check for user mode when trying to load an AppArmor profile.
User mode is supported as root, and upstart can be run as a user
without being in user mode.
Marc Deslauriers (mdeslaur) wrote : | # |
OK, that makes sense. I've removed the check for user mode. Thanks.
Stéphane Graber (stgraber) wrote : | # |
Shouldn't the manpage explicitly say that "apparmor load" requires root privileges (or at least cap_mac_admin?) while "apparmor switch" will work even when upstart runs as an unprivileged user as long as the profile is already loaded?
Marc Deslauriers (mdeslaur) wrote : | # |
I'm not sure. Future versions of AppArmor may allow unprivileged users to load a new profile, and switching to a different profile may fail if the user is already confined by a restrictive AppArmor profile (with pam_apparmor for example). Also, I don't see any other mention of privileges in the man page, such as the setuid/setgid stanzas. While I could potentially add a "requires appropriate privileges" blurb, I believe it's assumed everywhere else.
James Hunt (jamesodhunt) wrote : | # |
Thanks Marc. Merged.
Preview Diff
1 | === modified file 'ChangeLog' |
2 | --- ChangeLog 2013-05-07 08:59:05 +0000 |
3 | +++ ChangeLog 2013-05-27 14:31:27 +0000 |
4 | @@ -1,3 +1,55 @@ |
5 | +2013-05-27 Marc Deslauriers <marc.deslauriers@ubuntu.com> |
6 | + |
7 | + * init/job.c: Don't check for user mode when trying to load an |
8 | + AppArmor profile. User mode is supported as root, and upstart |
9 | + can be run as a user without being in user mode. |
10 | + * init/man/init.5: Adjust man page. |
11 | + |
12 | +2013-05-24 Marc Deslauriers <marc.deslauriers@ubuntu.com> |
13 | + |
14 | + * init/job_process.c: Allow environment variables in apparmor |
15 | + switch stanzas. |
16 | + * init/apparmor.[ch]: Use a profile name instead of a Job so we |
17 | + can use environment variables. |
18 | + |
19 | +2013-05-23 Marc Deslauriers <marc.deslauriers@ubuntu.com> |
20 | + |
21 | + * init/tests/test_state.c: An unused process is actually supposed |
22 | + to be NULL. Fix test. |
23 | + * init/process.c: Adjust to leave unused process as NULL. |
24 | + |
25 | +2013-05-17 Marc Deslauriers <marc.deslauriers@ubuntu.com> |
26 | + |
27 | + * init/process.c: Fix deserialising with PROCESS_SECURITY. |
28 | + * init/tests/data/upstart-pre-security.json: Added new file to |
29 | + test importing serialisation data without security elements. |
30 | + * init/tests/test_state.c: Added new data format test. |
31 | + |
32 | +2013-05-15 Marc Deslauriers <marc.deslauriers@ubuntu.com> |
33 | + |
34 | + * init/apparmor.[ch]: AppArmor profile helper. |
35 | + * init/Makefile.am: Added AppArmor profile helper. |
36 | + * init/errors.h: Added SECURITY_ERROR. |
37 | + * init/job.c: |
38 | + - Added new JOB_SECURITY state and PROCESS_SECURITY process. |
39 | + - Fix job_deserialise() for new PROCESS_SECURITY process. |
40 | + * init/job.h: Added new JOB_SECURITY state. |
41 | + * init/job_class.[ch]: Added apparmor_switch to hold the new |
42 | + "apparmor switch" stanza. |
43 | + * init/job_process.c: |
44 | + - Switch to new AppArmor profile. |
45 | + - Handle PROCESS_SECURITY process. |
46 | + * init/job_process.h: Added JOB_PROCESS_ERROR_SECURITY. |
47 | + * init/man/init.5: Document new AppArmor stanzas. |
48 | + * init/parse_job.c: Parse new "apparmor" stanzas. |
49 | + * init/process.[ch]: Add PROCESS_SECURITY. |
50 | + * init/tests/test_job.c: Add new tests, and adjust existing ones. |
51 | + * init/tests/test_job_class.c: Added apparmor_switch. |
52 | + * init/tests/test_parse_job.c: Test new AppArmor stanza parsing. |
53 | + * init/tests/test_process.c: Added PROCESS_SECURITY tests. |
54 | + * init/tests/test_state.c: Test apparmor_switch and |
55 | + PROCESS_SECURITY. |
56 | + |
57 | 2013-05-07 James Hunt <james.hunt@ubuntu.com> |
58 | |
59 | * util/man/shutdown.8: Specify default action is to bring system |
60 | |
61 | === modified file 'init/Makefile.am' |
62 | --- init/Makefile.am 2013-04-17 09:41:42 +0000 |
63 | +++ init/Makefile.am 2013-05-27 14:31:27 +0000 |
64 | @@ -59,7 +59,8 @@ |
65 | control.c control.h \ |
66 | xdg.c xdg.h \ |
67 | quiesce.c quiesce.h \ |
68 | - errors.h |
69 | + errors.h \ |
70 | + apparmor.c apparmor.h |
71 | nodist_init_SOURCES = \ |
72 | $(com_ubuntu_Upstart_OUTPUTS) \ |
73 | $(com_ubuntu_Upstart_Job_OUTPUTS) \ |
74 | @@ -186,7 +187,7 @@ |
75 | system.o environ.o process.o \ |
76 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
77 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
78 | - session.o log.o state.o xdg.o \ |
79 | + session.o log.o state.o xdg.o apparmor.o \ |
80 | com.ubuntu.Upstart.o \ |
81 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
82 | $(NIH_LIBS) \ |
83 | @@ -200,7 +201,7 @@ |
84 | system.o environ.o process.o \ |
85 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
86 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
87 | - session.o log.o state.o xdg.o \ |
88 | + session.o log.o state.o xdg.o apparmor.o \ |
89 | com.ubuntu.Upstart.o \ |
90 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
91 | $(NIH_LIBS) \ |
92 | @@ -214,7 +215,7 @@ |
93 | system.o environ.o process.o \ |
94 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
95 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
96 | - session.o log.o state.o xdg.o \ |
97 | + session.o log.o state.o xdg.o apparmor.o \ |
98 | com.ubuntu.Upstart.o \ |
99 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
100 | $(NIH_LIBS) \ |
101 | @@ -228,7 +229,7 @@ |
102 | system.o environ.o process.o \ |
103 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
104 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
105 | - session.o log.o state.o xdg.o \ |
106 | + session.o log.o state.o xdg.o apparmor.o \ |
107 | com.ubuntu.Upstart.o \ |
108 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
109 | $(NIH_LIBS) \ |
110 | @@ -242,7 +243,7 @@ |
111 | system.o environ.o process.o \ |
112 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
113 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
114 | - session.o log.o state.o xdg.o \ |
115 | + session.o log.o state.o xdg.o apparmor.o \ |
116 | com.ubuntu.Upstart.o \ |
117 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
118 | $(NIH_LIBS) \ |
119 | @@ -256,7 +257,7 @@ |
120 | system.o environ.o process.o \ |
121 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
122 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
123 | - session.o log.o state.o xdg.o \ |
124 | + session.o log.o state.o xdg.o apparmor.o \ |
125 | com.ubuntu.Upstart.o \ |
126 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
127 | $(NIH_LIBS) \ |
128 | @@ -270,7 +271,7 @@ |
129 | system.o environ.o process.o \ |
130 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
131 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
132 | - session.o log.o state.o xdg.o \ |
133 | + session.o log.o state.o xdg.o apparmor.o \ |
134 | com.ubuntu.Upstart.o \ |
135 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
136 | $(NIH_LIBS) \ |
137 | @@ -284,7 +285,7 @@ |
138 | system.o environ.o process.o \ |
139 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
140 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
141 | - session.o log.o state.o xdg.o \ |
142 | + session.o log.o state.o xdg.o apparmor.o \ |
143 | com.ubuntu.Upstart.o \ |
144 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
145 | $(NIH_LIBS) \ |
146 | @@ -298,7 +299,7 @@ |
147 | system.o environ.o process.o \ |
148 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
149 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
150 | - session.o log.o state.o xdg.o \ |
151 | + session.o log.o state.o xdg.o apparmor.o \ |
152 | com.ubuntu.Upstart.o \ |
153 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
154 | $(NIH_LIBS) \ |
155 | @@ -312,7 +313,7 @@ |
156 | system.o environ.o process.o \ |
157 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
158 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
159 | - session.o log.o state.o xdg.o \ |
160 | + session.o log.o state.o xdg.o apparmor.o \ |
161 | com.ubuntu.Upstart.o \ |
162 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
163 | $(NIH_LIBS) \ |
164 | @@ -326,7 +327,7 @@ |
165 | system.o environ.o process.o \ |
166 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
167 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
168 | - session.o log.o state.o xdg.o \ |
169 | + session.o log.o state.o xdg.o apparmor.o \ |
170 | com.ubuntu.Upstart.o \ |
171 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
172 | $(NIH_LIBS) \ |
173 | @@ -345,7 +346,7 @@ |
174 | system.o environ.o process.o \ |
175 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
176 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
177 | - session.o log.o state.o xdg.o \ |
178 | + session.o log.o state.o xdg.o apparmor.o \ |
179 | com.ubuntu.Upstart.o \ |
180 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
181 | $(NIH_LIBS) \ |
182 | @@ -359,7 +360,7 @@ |
183 | system.o environ.o process.o \ |
184 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
185 | parse_job.o parse_conf.o control.o quiesce.o \ |
186 | - session.o log.o state.o xdg.o \ |
187 | + session.o log.o state.o xdg.o apparmor.o \ |
188 | com.ubuntu.Upstart.o \ |
189 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
190 | $(NIH_LIBS) \ |
191 | @@ -380,7 +381,7 @@ |
192 | system.o environ.o process.o \ |
193 | job_class.o job_process.o job.o event.o event_operator.o blocked.o \ |
194 | parse_job.o parse_conf.o conf.o control.o quiesce.o \ |
195 | - session.o log.o state.o xdg.o \ |
196 | + session.o log.o state.o xdg.o apparmor.o \ |
197 | com.ubuntu.Upstart.o \ |
198 | com.ubuntu.Upstart.Job.o com.ubuntu.Upstart.Instance.o \ |
199 | $(NIH_LIBS) \ |
200 | |
201 | === added file 'init/apparmor.c' |
202 | --- init/apparmor.c 1970-01-01 00:00:00 +0000 |
203 | +++ init/apparmor.c 2013-05-27 14:31:27 +0000 |
204 | @@ -0,0 +1,116 @@ |
205 | +/* upstart |
206 | + * |
207 | + * apparmor.c - handle AppArmor profiles |
208 | + * |
209 | + * Copyright © 2013 Canonical Ltd. |
210 | + * Author: Marc Deslauriers <marc.deslauriers@canonical.com>. |
211 | + * |
212 | + * This program is free software; you can redistribute it and/or modify |
213 | + * it under the terms of the GNU General Public License version 2, as |
214 | + * published by the Free Software Foundation. |
215 | + * |
216 | + * This program is distributed in the hope that it will be useful, |
217 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
218 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
219 | + * GNU General Public License for more details. |
220 | + * |
221 | + * You should have received a copy of the GNU General Public License along |
222 | + * with this program; if not, write to the Free Software Foundation, Inc., |
223 | + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
224 | + */ |
225 | + |
226 | +#ifdef HAVE_CONFIG_H |
227 | +# include <config.h> |
228 | +#endif /* HAVE_CONFIG_H */ |
229 | + |
230 | +#include <sys/types.h> |
231 | +#include <sys/stat.h> |
232 | + |
233 | +#include <stdio.h> |
234 | +#include <unistd.h> |
235 | +#include <limits.h> |
236 | + |
237 | +#include <nih/signal.h> |
238 | +#include <nih/string.h> |
239 | + |
240 | +#include "apparmor.h" |
241 | + |
242 | +/** |
243 | + * apparmor_switch: |
244 | + * @profile: AppArmor profile to switch to |
245 | + * |
246 | + * This function switches to a new AppArmor profile on exec |
247 | + * |
248 | + * Returns: zero on success, -1 on error |
249 | + **/ |
250 | +int |
251 | +apparmor_switch (char *profile) |
252 | +{ |
253 | + nih_local char *filename = NULL; |
254 | + FILE *f; |
255 | + |
256 | + nih_assert (profile != NULL); |
257 | + |
258 | + /* Silently fail if AppArmor isn't enabled. */ |
259 | + if (! apparmor_available()) |
260 | + return 0; |
261 | + |
262 | + filename = nih_sprintf (NULL, "/proc/%d/attr/exec", getpid()); |
263 | + |
264 | + if (! filename) |
265 | + return -1; |
266 | + |
267 | + f = fopen (filename, "w"); |
268 | + |
269 | + if (! f) |
270 | + return -1; |
271 | + |
272 | + fprintf (f, "exec %s\n", profile); |
273 | + |
274 | + if (fclose (f)) |
275 | + return -1; |
276 | + |
277 | + return 0; |
278 | +} |
279 | + |
280 | +/** |
281 | + * apparmor_available: |
282 | + * |
283 | + * This function checks to see if AppArmor is available and enabled |
284 | + * |
285 | + * Returns: TRUE if AppArmor is available, FALSE if it isn't |
286 | + **/ |
287 | +int |
288 | +apparmor_available (void) |
289 | +{ |
290 | + struct stat statbuf; |
291 | + FILE *f; |
292 | + int value = 0; |
293 | + |
294 | + /* Do not load if AppArmor is disabled. |
295 | + */ |
296 | + f = fopen ("/sys/module/apparmor/parameters/enabled", "r"); |
297 | + |
298 | + if (! f) |
299 | + return FALSE; |
300 | + |
301 | + value = fgetc (f); |
302 | + |
303 | + if (fclose (f)) |
304 | + return FALSE; |
305 | + |
306 | + if (value != 'Y') |
307 | + return FALSE; |
308 | + |
309 | + /* Do not load if AppArmor parser isn't available. |
310 | + */ |
311 | + if (stat (APPARMOR_PARSER, &statbuf) == 0) { |
312 | + if(! (S_ISREG(statbuf.st_mode) && statbuf.st_mode & S_IXUSR)) |
313 | + return FALSE; |
314 | + } else { |
315 | + return FALSE; |
316 | + } |
317 | + |
318 | + return TRUE; |
319 | +} |
320 | + |
321 | |
322 | === added file 'init/apparmor.h' |
323 | --- init/apparmor.h 1970-01-01 00:00:00 +0000 |
324 | +++ init/apparmor.h 2013-05-27 14:31:27 +0000 |
325 | @@ -0,0 +1,52 @@ |
326 | +/* upstart |
327 | + * |
328 | + * Copyright © 2013 Canonical Ltd. |
329 | + * Author: Marc Deslauriers <marc.deslauriers@canonical.com>. |
330 | + * |
331 | + * This program is free software; you can redistribute it and/or modify |
332 | + * it under the terms of the GNU General Public License version 2, as |
333 | + * published by the Free Software Foundation. |
334 | + * |
335 | + * This program is distributed in the hope that it will be useful, |
336 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
337 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
338 | + * GNU General Public License for more details. |
339 | + * |
340 | + * You should have received a copy of the GNU General Public License along |
341 | + * with this program; if not, write to the Free Software Foundation, Inc., |
342 | + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
343 | + */ |
344 | + |
345 | +#ifndef INIT_APPARMOR_H |
346 | +#define INIT_APPARMOR_H |
347 | + |
348 | +#include "job.h" |
349 | + |
350 | +/** |
351 | + * APPARMOR_PARSER: |
352 | + * |
353 | + * Location of apparmor_parser binary |
354 | + * |
355 | + **/ |
356 | +#define APPARMOR_PARSER "/sbin/apparmor_parser" |
357 | + |
358 | +/** |
359 | + * APPARMOR_PARSER_OPTS: |
360 | + * |
361 | + * apparmor_parser options |
362 | + * |
363 | + **/ |
364 | +#define APPARMOR_PARSER_OPTS "-r -W" |
365 | + |
366 | + |
367 | +NIH_BEGIN_EXTERN |
368 | + |
369 | +int apparmor_switch (char *profile) |
370 | + __attribute__ ((warn_unused_result)); |
371 | + |
372 | +int apparmor_available (void) |
373 | + __attribute__ ((warn_unused_result)); |
374 | + |
375 | +NIH_END_EXTERN |
376 | + |
377 | +#endif /* INIT_APPARMOR_H */ |
378 | |
379 | === modified file 'init/errors.h' |
380 | --- init/errors.h 2012-09-09 21:38:59 +0000 |
381 | +++ init/errors.h 2013-05-27 14:31:27 +0000 |
382 | @@ -54,6 +54,9 @@ |
383 | |
384 | /* Errors while handling control requests */ |
385 | CONTROL_NAME_TAKEN, |
386 | + |
387 | + /* Errors while handling security profiles */ |
388 | + SECURITY_ERROR, |
389 | }; |
390 | |
391 | /* Error strings for defined messages */ |
392 | |
393 | === modified file 'init/job.c' |
394 | --- init/job.c 2013-02-27 11:46:04 +0000 |
395 | +++ init/job.c 2013-05-27 14:31:27 +0000 |
396 | @@ -56,6 +56,7 @@ |
397 | #include "control.h" |
398 | #include "parse_job.h" |
399 | #include "state.h" |
400 | +#include "apparmor.h" |
401 | |
402 | #include "com.ubuntu.Upstart.Job.h" |
403 | #include "com.ubuntu.Upstart.Instance.h" |
404 | @@ -397,9 +398,25 @@ |
405 | job->blocker = job_emit_event (job); |
406 | |
407 | break; |
408 | + case JOB_SECURITY: |
409 | + nih_assert (job->goal == JOB_START); |
410 | + nih_assert (old_state == JOB_STARTING); |
411 | + |
412 | + if (job->class->process[PROCESS_SECURITY] |
413 | + && apparmor_available()) { |
414 | + if (job_process_run (job, PROCESS_SECURITY) < 0) { |
415 | + job_failed (job, PROCESS_SECURITY, -1); |
416 | + job_change_goal (job, JOB_STOP); |
417 | + state = job_next_state (job); |
418 | + } |
419 | + } else { |
420 | + state = job_next_state (job); |
421 | + } |
422 | + |
423 | + break; |
424 | case JOB_PRE_START: |
425 | nih_assert (job->goal == JOB_START); |
426 | - nih_assert (old_state == JOB_STARTING); |
427 | + nih_assert (old_state == JOB_SECURITY); |
428 | |
429 | if (job->class->process[PROCESS_PRE_START]) { |
430 | if (job_process_run (job, PROCESS_PRE_START) < 0) { |
431 | @@ -480,6 +497,7 @@ |
432 | case JOB_STOPPING: |
433 | nih_assert ((old_state == JOB_STARTING) |
434 | || (old_state == JOB_PRE_START) |
435 | + || (old_state == JOB_SECURITY) |
436 | || (old_state == JOB_SPAWNED) |
437 | || (old_state == JOB_POST_START) |
438 | || (old_state == JOB_RUNNING) |
439 | @@ -598,6 +616,15 @@ |
440 | case JOB_STOP: |
441 | return JOB_STOPPING; |
442 | case JOB_START: |
443 | + return JOB_SECURITY; |
444 | + default: |
445 | + nih_assert_not_reached (); |
446 | + } |
447 | + case JOB_SECURITY: |
448 | + switch (job->goal) { |
449 | + case JOB_STOP: |
450 | + return JOB_STOPPING; |
451 | + case JOB_START: |
452 | return JOB_PRE_START; |
453 | default: |
454 | nih_assert_not_reached (); |
455 | @@ -1072,6 +1099,8 @@ |
456 | return N_("waiting"); |
457 | case JOB_STARTING: |
458 | return N_("starting"); |
459 | + case JOB_SECURITY: |
460 | + return N_("security"); |
461 | case JOB_PRE_START: |
462 | return N_("pre-start"); |
463 | case JOB_SPAWNED: |
464 | @@ -1110,6 +1139,8 @@ |
465 | return JOB_WAITING; |
466 | } else if (! strcmp (state, "starting")) { |
467 | return JOB_STARTING; |
468 | + } else if (! strcmp (state, "security")) { |
469 | + return JOB_SECURITY; |
470 | } else if (! strcmp (state, "pre-start")) { |
471 | return JOB_PRE_START; |
472 | } else if (! strcmp (state, "spawned")) { |
473 | @@ -1926,8 +1957,21 @@ |
474 | if (ret < 0) |
475 | goto error; |
476 | |
477 | - if (len != PROCESS_LAST) |
478 | + /* If we are missing one, we're probably importing from a |
479 | + * previous version that didn't include PROCESS_SECURITY. |
480 | + * Simply add the missing one. |
481 | + */ |
482 | + if (len == PROCESS_LAST - 1) { |
483 | + job->pid = nih_realloc (job->pid, job, sizeof (pid_t) * PROCESS_LAST); |
484 | + |
485 | + if (! job->pid) |
486 | + goto error; |
487 | + |
488 | + job->pid[PROCESS_LAST - 1] = 0; |
489 | + |
490 | + } else if (len != PROCESS_LAST) { |
491 | goto error; |
492 | + } |
493 | |
494 | if (! state_get_json_int_var_to_obj (json, job, trace_forks)) |
495 | goto error; |
496 | @@ -1949,13 +1993,23 @@ |
497 | json_object *json_log; |
498 | |
499 | json_log = json_object_array_get_idx (json_logs, process); |
500 | - if (! json_log) |
501 | - goto error; |
502 | |
503 | - /* NULL if there was no log configured, or we failed to |
504 | - * deserialise it; either way, this should be non-fatal. |
505 | - */ |
506 | - job->log[process] = log_deserialise (job->log, json_log); |
507 | + if (json_log) { |
508 | + /* NULL if there was no log configured, or we failed to |
509 | + * deserialise it; either way, this should be non-fatal. |
510 | + */ |
511 | + job->log[process] = log_deserialise (job->log, json_log); |
512 | + } else { |
513 | + /* If we are missing one, we're probably importing from a |
514 | + * previous version that didn't include PROCESS_SECURITY. |
515 | + * Simply ignore the missing one. |
516 | + */ |
517 | + if (process == PROCESS_LAST - 1) { |
518 | + job->log[process] = NULL; |
519 | + } else { |
520 | + goto error; |
521 | + } |
522 | + } |
523 | } |
524 | |
525 | return job; |
526 | @@ -2066,6 +2120,7 @@ |
527 | { |
528 | state_enum_to_str (JOB_WAITING, state); |
529 | state_enum_to_str (JOB_STARTING, state); |
530 | + state_enum_to_str (JOB_SECURITY, state); |
531 | state_enum_to_str (JOB_PRE_START, state); |
532 | state_enum_to_str (JOB_SPAWNED, state); |
533 | state_enum_to_str (JOB_POST_START, state); |
534 | @@ -2092,6 +2147,7 @@ |
535 | { |
536 | state_str_to_enum (JOB_WAITING, state); |
537 | state_str_to_enum (JOB_STARTING, state); |
538 | + state_str_to_enum (JOB_SECURITY, state); |
539 | state_str_to_enum (JOB_PRE_START, state); |
540 | state_str_to_enum (JOB_SPAWNED, state); |
541 | state_str_to_enum (JOB_POST_START, state); |
542 | |
543 | === modified file 'init/job.h' |
544 | --- init/job.h 2013-02-27 11:46:04 +0000 |
545 | +++ init/job.h 2013-05-27 14:31:27 +0000 |
546 | @@ -66,6 +66,7 @@ |
547 | typedef enum job_state { |
548 | JOB_WAITING, |
549 | JOB_STARTING, |
550 | + JOB_SECURITY, |
551 | JOB_PRE_START, |
552 | JOB_SPAWNED, |
553 | JOB_POST_START, |
554 | |
555 | === modified file 'init/job_class.c' |
556 | --- init/job_class.c 2013-02-25 09:38:55 +0000 |
557 | +++ init/job_class.c 2013-05-27 14:31:27 +0000 |
558 | @@ -362,6 +362,8 @@ |
559 | |
560 | class->usage = NULL; |
561 | |
562 | + class->apparmor_switch = NULL; |
563 | + |
564 | return class; |
565 | |
566 | error: |
567 | @@ -1873,6 +1875,9 @@ |
568 | if (! state_set_json_string_var_from_obj (json, class, usage)) |
569 | goto error; |
570 | |
571 | + if (! state_set_json_string_var_from_obj (json, class, apparmor_switch)) |
572 | + goto error; |
573 | + |
574 | return json; |
575 | |
576 | error: |
577 | @@ -2109,6 +2114,14 @@ |
578 | if (! state_get_json_string_var_to_obj (json, class, usage)) |
579 | goto error; |
580 | |
581 | + /* If we are missing this, we're probably importing from a |
582 | + * previous version that didn't include PROCESS_SECURITY. |
583 | + */ |
584 | + if (json_object_object_get (json, "apparmor_switch")) { |
585 | + if (! state_get_json_string_var_to_obj (json, class, apparmor_switch)) |
586 | + goto error; |
587 | + } |
588 | + |
589 | json_normalexit = json_object_object_get (json, "normalexit"); |
590 | if (! json_normalexit) |
591 | goto error; |
592 | |
593 | === modified file 'init/job_class.h' |
594 | --- init/job_class.h 2013-02-27 11:46:04 +0000 |
595 | +++ init/job_class.h 2013-05-27 14:31:27 +0000 |
596 | @@ -164,6 +164,7 @@ |
597 | * @setgid: group name to drop to before starting process, |
598 | * @deleted: whether job should be deleted when finished. |
599 | * @usage: usage text - how to control job |
600 | + * @apparmor_switch: AppArmor profile to switch to before starting job |
601 | * |
602 | * This structure holds the configuration of a known task or service that |
603 | * should be tracked by the init daemon; as tasks and services are |
604 | @@ -220,6 +221,8 @@ |
605 | int debug; |
606 | |
607 | char *usage; |
608 | + |
609 | + char *apparmor_switch; |
610 | } JobClass; |
611 | |
612 | |
613 | |
614 | === modified file 'init/job_process.c' |
615 | --- init/job_process.c 2013-03-28 17:07:54 +0000 |
616 | +++ init/job_process.c 2013-05-27 14:31:27 +0000 |
617 | @@ -67,6 +67,7 @@ |
618 | #include "errors.h" |
619 | #include "control.h" |
620 | #include "xdg.h" |
621 | +#include "apparmor.h" |
622 | |
623 | |
624 | /** |
625 | @@ -701,184 +702,208 @@ |
626 | close (pty_slave); |
627 | } |
628 | |
629 | - /* Set resource limits for the process, skipping over any that |
630 | - * aren't set in the job class such that they inherit from |
631 | - * ourselves (and we inherit from kernel defaults). |
632 | + /* Switch to the specified AppArmor profile, but only for the main |
633 | + process, so we don't confine the pre- and post- processes. |
634 | */ |
635 | - for (i = 0; i < RLIMIT_NLIMITS; i++) { |
636 | - if (! class->limits[i]) |
637 | - continue; |
638 | - |
639 | - if (setrlimit (i, class->limits[i]) < 0) { |
640 | + if ((class->apparmor_switch) && (process == PROCESS_MAIN)) { |
641 | + nih_local char *profile = NULL; |
642 | + |
643 | + /* Use the environment to expand the AppArmor profile name |
644 | + */ |
645 | + profile = NIH_SHOULD (environ_expand (NULL, |
646 | + class->apparmor_switch, |
647 | + environ)); |
648 | + |
649 | + if (! profile) { |
650 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SECURITY, 0); |
651 | + } |
652 | + |
653 | + if (apparmor_switch (profile) < 0) { |
654 | + nih_error_raise_system (); |
655 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SECURITY, 0); |
656 | + } |
657 | + } |
658 | + |
659 | + if (process != PROCESS_SECURITY) { |
660 | + /* Set resource limits for the process, skipping over any that |
661 | + * aren't set in the job class such that they inherit from |
662 | + * ourselves (and we inherit from kernel defaults). |
663 | + */ |
664 | + for (i = 0; i < RLIMIT_NLIMITS; i++) { |
665 | + if (! class->limits[i]) |
666 | + continue; |
667 | + |
668 | + if (setrlimit (i, class->limits[i]) < 0) { |
669 | + nih_error_raise_system (); |
670 | + job_process_error_abort (fds[1], |
671 | + JOB_PROCESS_ERROR_RLIMIT, i); |
672 | + } |
673 | + } |
674 | + |
675 | + /* Set the file mode creation mask; this is one of the few operations |
676 | + * that can never fail. |
677 | + */ |
678 | + umask (class->umask); |
679 | + |
680 | + /* Adjust the process priority ("nice level"). |
681 | + */ |
682 | + if (class->nice != JOB_NICE_INVALID && |
683 | + setpriority (PRIO_PROCESS, 0, class->nice) < 0) { |
684 | nih_error_raise_system (); |
685 | job_process_error_abort (fds[1], |
686 | - JOB_PROCESS_ERROR_RLIMIT, i); |
687 | + JOB_PROCESS_ERROR_PRIORITY, 0); |
688 | } |
689 | - } |
690 | - |
691 | - /* Set the file mode creation mask; this is one of the few operations |
692 | - * that can never fail. |
693 | - */ |
694 | - umask (class->umask); |
695 | - |
696 | - /* Adjust the process priority ("nice level"). |
697 | - */ |
698 | - if (class->nice != JOB_NICE_INVALID && |
699 | - setpriority (PRIO_PROCESS, 0, class->nice) < 0) { |
700 | - nih_error_raise_system (); |
701 | - job_process_error_abort (fds[1], |
702 | - JOB_PROCESS_ERROR_PRIORITY, 0); |
703 | - } |
704 | - |
705 | - /* Adjust the process OOM killer priority. |
706 | - */ |
707 | - if (class->oom_score_adj != JOB_DEFAULT_OOM_SCORE_ADJ) { |
708 | - int oom_value; |
709 | - snprintf (filename, sizeof (filename), |
710 | - "/proc/%d/oom_score_adj", getpid ()); |
711 | - oom_value = class->oom_score_adj; |
712 | - fd = fopen (filename, "w"); |
713 | - if ((! fd) && (errno == ENOENT)) { |
714 | + |
715 | + /* Adjust the process OOM killer priority. |
716 | + */ |
717 | + if (class->oom_score_adj != JOB_DEFAULT_OOM_SCORE_ADJ) { |
718 | + int oom_value; |
719 | snprintf (filename, sizeof (filename), |
720 | - "/proc/%d/oom_adj", getpid ()); |
721 | - oom_value = (class->oom_score_adj |
722 | - * ((class->oom_score_adj < 0) ? 17 : 15)) / 1000; |
723 | + "/proc/%d/oom_score_adj", getpid ()); |
724 | + oom_value = class->oom_score_adj; |
725 | fd = fopen (filename, "w"); |
726 | - } |
727 | - if (! fd) { |
728 | - nih_error_raise_system (); |
729 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_OOM_ADJ, 0); |
730 | - } else { |
731 | - fprintf (fd, "%d\n", oom_value); |
732 | - |
733 | - if (fclose (fd)) { |
734 | + if ((! fd) && (errno == ENOENT)) { |
735 | + snprintf (filename, sizeof (filename), |
736 | + "/proc/%d/oom_adj", getpid ()); |
737 | + oom_value = (class->oom_score_adj |
738 | + * ((class->oom_score_adj < 0) ? 17 : 15)) / 1000; |
739 | + fd = fopen (filename, "w"); |
740 | + } |
741 | + if (! fd) { |
742 | nih_error_raise_system (); |
743 | job_process_error_abort (fds[1], JOB_PROCESS_ERROR_OOM_ADJ, 0); |
744 | - } |
745 | - } |
746 | - } |
747 | - |
748 | - /* Handle changing a chroot session job prior to dealing with |
749 | - * the 'chroot' stanza. |
750 | - */ |
751 | - if (class->session && class->session->chroot) { |
752 | - if (chroot (class->session->chroot) < 0) { |
753 | - nih_error_raise_system (); |
754 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHROOT, 0); |
755 | - } |
756 | - } |
757 | - |
758 | - /* Change the root directory, confining path resolution within it; |
759 | - * we do this before the working directory call so that is always |
760 | - * relative to the new root. |
761 | - */ |
762 | - if (class->chroot) { |
763 | - if (chroot (class->chroot) < 0) { |
764 | - nih_error_raise_system (); |
765 | - job_process_error_abort (fds[1], |
766 | - JOB_PROCESS_ERROR_CHROOT, 0); |
767 | - } |
768 | - } |
769 | - |
770 | - /* Change the working directory of the process, either to the one |
771 | - * configured in the job, or to the root directory of the filesystem |
772 | - * (or at least relative to the chroot). |
773 | - */ |
774 | - if (class->chdir || user_mode == FALSE) { |
775 | - if (chdir (class->chdir ? class->chdir : "/") < 0) { |
776 | - nih_error_raise_system (); |
777 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHDIR, 0); |
778 | - } |
779 | - } |
780 | - |
781 | - /* Change the user and group of the process to the one |
782 | - * configured in the job. We must wait until now to lookup the |
783 | - * UID and GID from the names to accommodate both chroot |
784 | - * session jobs and jobs with a chroot stanza. |
785 | - */ |
786 | - if (class->setuid) { |
787 | - /* Without resetting errno, it's impossible to |
788 | - * distinguish between a non-existent user and and |
789 | - * error during lookup */ |
790 | - errno = 0; |
791 | - pwd = getpwnam (class->setuid); |
792 | - if (! pwd) { |
793 | - if (errno != 0) { |
794 | - nih_error_raise_system (); |
795 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWNAM, 0); |
796 | - } else { |
797 | - nih_error_raise (JOB_PROCESS_INVALID_SETUID, |
798 | - JOB_PROCESS_INVALID_SETUID_STR); |
799 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_BAD_SETUID, 0); |
800 | - } |
801 | - } |
802 | - |
803 | - job_setuid = pwd->pw_uid; |
804 | - /* This will be overridden if setgid is also set: */ |
805 | - job_setgid = pwd->pw_gid; |
806 | - } |
807 | - |
808 | - if (class->setgid) { |
809 | - errno = 0; |
810 | - grp = getgrnam (class->setgid); |
811 | - if (! grp) { |
812 | - if (errno != 0) { |
813 | - nih_error_raise_system (); |
814 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETGRNAM, 0); |
815 | - } else { |
816 | - nih_error_raise (JOB_PROCESS_INVALID_SETGID, |
817 | - JOB_PROCESS_INVALID_SETGID_STR); |
818 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_BAD_SETGID, 0); |
819 | - } |
820 | - } |
821 | - |
822 | - job_setgid = grp->gr_gid; |
823 | - } |
824 | - |
825 | - if (script_fd != -1 && |
826 | - (job_setuid != (uid_t) -1 || job_setgid != (gid_t) -1) && |
827 | - fchown (script_fd, job_setuid, job_setgid) < 0) { |
828 | - nih_error_raise_system (); |
829 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHOWN, 0); |
830 | - } |
831 | - |
832 | - /* Make sure we always have the needed pwd and grp structs. |
833 | - * Then pass those to initgroups() to setup the user's group list. |
834 | - * Only do that if we're root as initgroups() won't work when non-root. */ |
835 | - if (geteuid () == 0) { |
836 | - if (! pwd) { |
837 | - pwd = getpwuid (geteuid ()); |
838 | - if (! pwd) { |
839 | - nih_error_raise_system (); |
840 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWUID, 0); |
841 | - } |
842 | - } |
843 | - |
844 | - if (! grp) { |
845 | - grp = getgrgid (getegid ()); |
846 | - if (! grp) { |
847 | - nih_error_raise_system (); |
848 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETGRGID, 0); |
849 | - } |
850 | - } |
851 | - |
852 | - if (pwd && grp) { |
853 | - if (initgroups (pwd->pw_name, grp->gr_gid) < 0) { |
854 | - nih_error_raise_system (); |
855 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_INITGROUPS, 0); |
856 | - } |
857 | - } |
858 | - } |
859 | - |
860 | - /* Start dropping privileges */ |
861 | - if (job_setgid != (gid_t) -1 && setgid (job_setgid) < 0) { |
862 | - nih_error_raise_system (); |
863 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETGID, 0); |
864 | - } |
865 | - |
866 | - if (job_setuid != (uid_t)-1 && setuid (job_setuid) < 0) { |
867 | - nih_error_raise_system (); |
868 | - job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETUID, 0); |
869 | + } else { |
870 | + fprintf (fd, "%d\n", oom_value); |
871 | + |
872 | + if (fclose (fd)) { |
873 | + nih_error_raise_system (); |
874 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_OOM_ADJ, 0); |
875 | + } |
876 | + } |
877 | + } |
878 | + |
879 | + /* Handle changing a chroot session job prior to dealing with |
880 | + * the 'chroot' stanza. |
881 | + */ |
882 | + if (class->session && class->session->chroot) { |
883 | + if (chroot (class->session->chroot) < 0) { |
884 | + nih_error_raise_system (); |
885 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHROOT, 0); |
886 | + } |
887 | + } |
888 | + |
889 | + /* Change the root directory, confining path resolution within it; |
890 | + * we do this before the working directory call so that is always |
891 | + * relative to the new root. |
892 | + */ |
893 | + if (class->chroot) { |
894 | + if (chroot (class->chroot) < 0) { |
895 | + nih_error_raise_system (); |
896 | + job_process_error_abort (fds[1], |
897 | + JOB_PROCESS_ERROR_CHROOT, 0); |
898 | + } |
899 | + } |
900 | + |
901 | + /* Change the working directory of the process, either to the one |
902 | + * configured in the job, or to the root directory of the filesystem |
903 | + * (or at least relative to the chroot). |
904 | + */ |
905 | + if (class->chdir || user_mode == FALSE) { |
906 | + if (chdir (class->chdir ? class->chdir : "/") < 0) { |
907 | + nih_error_raise_system (); |
908 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHDIR, 0); |
909 | + } |
910 | + } |
911 | + |
912 | + /* Change the user and group of the process to the one |
913 | + * configured in the job. We must wait until now to lookup the |
914 | + * UID and GID from the names to accommodate both chroot |
915 | + * session jobs and jobs with a chroot stanza. |
916 | + */ |
917 | + if (class->setuid) { |
918 | + /* Without resetting errno, it's impossible to |
919 | + * distinguish between a non-existent user and and |
920 | + * error during lookup */ |
921 | + errno = 0; |
922 | + pwd = getpwnam (class->setuid); |
923 | + if (! pwd) { |
924 | + if (errno != 0) { |
925 | + nih_error_raise_system (); |
926 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWNAM, 0); |
927 | + } else { |
928 | + nih_error_raise (JOB_PROCESS_INVALID_SETUID, |
929 | + JOB_PROCESS_INVALID_SETUID_STR); |
930 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_BAD_SETUID, 0); |
931 | + } |
932 | + } |
933 | + |
934 | + job_setuid = pwd->pw_uid; |
935 | + /* This will be overridden if setgid is also set: */ |
936 | + job_setgid = pwd->pw_gid; |
937 | + } |
938 | + |
939 | + if (class->setgid) { |
940 | + errno = 0; |
941 | + grp = getgrnam (class->setgid); |
942 | + if (! grp) { |
943 | + if (errno != 0) { |
944 | + nih_error_raise_system (); |
945 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETGRNAM, 0); |
946 | + } else { |
947 | + nih_error_raise (JOB_PROCESS_INVALID_SETGID, |
948 | + JOB_PROCESS_INVALID_SETGID_STR); |
949 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_BAD_SETGID, 0); |
950 | + } |
951 | + } |
952 | + |
953 | + job_setgid = grp->gr_gid; |
954 | + } |
955 | + |
956 | + if (script_fd != -1 && |
957 | + (job_setuid != (uid_t) -1 || job_setgid != (gid_t) -1) && |
958 | + fchown (script_fd, job_setuid, job_setgid) < 0) { |
959 | + nih_error_raise_system (); |
960 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_CHOWN, 0); |
961 | + } |
962 | + |
963 | + /* Make sure we always have the needed pwd and grp structs. |
964 | + * Then pass those to initgroups() to setup the user's group list. |
965 | + * Only do that if we're root as initgroups() won't work when non-root. */ |
966 | + if (geteuid () == 0) { |
967 | + if (! pwd) { |
968 | + pwd = getpwuid (geteuid ()); |
969 | + if (! pwd) { |
970 | + nih_error_raise_system (); |
971 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETPWUID, 0); |
972 | + } |
973 | + } |
974 | + |
975 | + if (! grp) { |
976 | + grp = getgrgid (getegid ()); |
977 | + if (! grp) { |
978 | + nih_error_raise_system (); |
979 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_GETGRGID, 0); |
980 | + } |
981 | + } |
982 | + |
983 | + if (pwd && grp) { |
984 | + if (initgroups (pwd->pw_name, grp->gr_gid) < 0) { |
985 | + nih_error_raise_system (); |
986 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_INITGROUPS, 0); |
987 | + } |
988 | + } |
989 | + } |
990 | + |
991 | + /* Start dropping privileges */ |
992 | + if (job_setgid != (gid_t) -1 && setgid (job_setgid) < 0) { |
993 | + nih_error_raise_system (); |
994 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETGID, 0); |
995 | + } |
996 | + |
997 | + if (job_setuid != (uid_t)-1 && setuid (job_setuid) < 0) { |
998 | + nih_error_raise_system (); |
999 | + job_process_error_abort (fds[1], JOB_PROCESS_ERROR_SETUID, 0); |
1000 | + } |
1001 | } |
1002 | |
1003 | /* Reset all the signal handlers back to their default handling so |
1004 | @@ -923,6 +948,7 @@ |
1005 | nih_assert_not_reached (); |
1006 | } |
1007 | |
1008 | + |
1009 | /** |
1010 | * job_process_error_abort: |
1011 | * @fd: writing end of pipe, |
1012 | @@ -1185,6 +1211,11 @@ |
1013 | err, _("unable to initgroups: %s"), |
1014 | strerror (err->errnum))); |
1015 | break; |
1016 | + case JOB_PROCESS_ERROR_SECURITY: |
1017 | + err->error.message = NIH_MUST (nih_sprintf ( |
1018 | + err, _("unable to switch security profile: %s"), |
1019 | + strerror (err->errnum))); |
1020 | + break; |
1021 | default: |
1022 | nih_assert_not_reached (); |
1023 | } |
1024 | @@ -1640,6 +1671,17 @@ |
1025 | */ |
1026 | stop = TRUE; |
1027 | break; |
1028 | + case PROCESS_SECURITY: |
1029 | + nih_assert (job->state == JOB_SECURITY); |
1030 | + |
1031 | + /* We should always fail the job if the security profile |
1032 | + * failed to load |
1033 | + */ |
1034 | + if (status) { |
1035 | + failed = TRUE; |
1036 | + stop = TRUE; |
1037 | + } |
1038 | + break; |
1039 | case PROCESS_PRE_START: |
1040 | nih_assert (job->state == JOB_PRE_START); |
1041 | |
1042 | |
1043 | === modified file 'init/job_process.h' |
1044 | --- init/job_process.h 2013-03-28 17:07:54 +0000 |
1045 | +++ init/job_process.h 2013-05-27 14:31:27 +0000 |
1046 | @@ -94,7 +94,8 @@ |
1047 | JOB_PROCESS_ERROR_SIGNAL, |
1048 | JOB_PROCESS_ERROR_ALLOC, |
1049 | JOB_PROCESS_ERROR_INITGROUPS, |
1050 | - JOB_PROCESS_ERROR_GETGRGID |
1051 | + JOB_PROCESS_ERROR_GETGRGID, |
1052 | + JOB_PROCESS_ERROR_SECURITY |
1053 | } JobProcessErrorType; |
1054 | |
1055 | /** |
1056 | |
1057 | === modified file 'init/man/init.5' |
1058 | --- init/man/init.5 2013-03-25 12:55:08 +0000 |
1059 | +++ init/man/init.5 2013-05-27 14:31:27 +0000 |
1060 | @@ -916,6 +916,37 @@ |
1061 | .P |
1062 | |
1063 | .\" |
1064 | +.SS AppArmor support |
1065 | +Upstart provides several stanzas for loading and switching to different |
1066 | +AppArmor profiles. If AppArmor isn't enabled in the currently running |
1067 | +kernel, the stanzas will be silently ignored. |
1068 | + |
1069 | +.TP |
1070 | +.B apparmor load \fIPROFILE |
1071 | +This stanza specifies an AppArmor profile to load into the Linux kernel at |
1072 | +job start. The AppArmor profile will confine a main process automatically |
1073 | +using path attachment, or manually by using the \fBapparmor switch\fP |
1074 | +stanza. |
1075 | +.I PROFILE |
1076 | +must be an absolute path to a profile and a failure will occur if the file |
1077 | +doesn't exist. |
1078 | + |
1079 | +.nf |
1080 | +apparmor load /etc/apparmor.d/usr.sbin.cupsd |
1081 | +.fi |
1082 | +.\" |
1083 | +.TP |
1084 | +.B apparmor switch \fINAME |
1085 | +This stanza specifies the name of an AppArmor profile name to switch to |
1086 | +before running the main process. |
1087 | +.I NAME |
1088 | +must be the name of a profile already loaded into the running Linux kernel, |
1089 | +and will result in a failure if not available. |
1090 | + |
1091 | +.nf |
1092 | +apparmor switch /usr/sbin/cupsd |
1093 | +.fi |
1094 | +.\" |
1095 | .SS Miscellaneous |
1096 | .TP |
1097 | .B kill signal \fISIGNAL |
1098 | @@ -1073,3 +1104,4 @@ |
1099 | .BR prctl (2) |
1100 | .BR pty (7) |
1101 | .BR sh (1) |
1102 | +.BR apparmor (7) |
1103 | |
1104 | === modified file 'init/parse_job.c' |
1105 | --- init/parse_job.c 2013-02-27 11:46:04 +0000 |
1106 | +++ init/parse_job.c 2013-05-27 14:31:27 +0000 |
1107 | @@ -45,6 +45,7 @@ |
1108 | #include "event.h" |
1109 | #include "parse_job.h" |
1110 | #include "errors.h" |
1111 | +#include "apparmor.h" |
1112 | |
1113 | |
1114 | /* Prototypes for static functions */ |
1115 | @@ -170,6 +171,11 @@ |
1116 | size_t *pos, size_t *lineno) |
1117 | __attribute__ ((warn_unused_result)); |
1118 | |
1119 | +static int stanza_apparmor (JobClass *class, NihConfigStanza *stanza, |
1120 | + const char *file, size_t len, |
1121 | + size_t *pos, size_t *lineno) |
1122 | + __attribute__ ((warn_unused_result)); |
1123 | + |
1124 | static int stanza_respawn (JobClass *class, NihConfigStanza *stanza, |
1125 | const char *file, size_t len, |
1126 | size_t *pos, size_t *lineno) |
1127 | @@ -270,6 +276,7 @@ |
1128 | { "debug", (NihConfigHandler)stanza_debug }, |
1129 | { "manual", (NihConfigHandler)stanza_manual }, |
1130 | { "usage", (NihConfigHandler)stanza_usage }, |
1131 | + { "apparmor", (NihConfigHandler)stanza_apparmor }, |
1132 | |
1133 | NIH_CONFIG_LAST |
1134 | }; |
1135 | @@ -1931,6 +1938,108 @@ |
1136 | return ret; |
1137 | } |
1138 | |
1139 | +/** |
1140 | + * stanza_apparmor: |
1141 | + * @class: job class being parsed, |
1142 | + * @stanza: stanza found, |
1143 | + * @file: file or string to parse, |
1144 | + * @len: length of @file, |
1145 | + * @pos: offset within @file, |
1146 | + * @lineno: line number. |
1147 | + * |
1148 | + * Parse an apparmor stanza from @file, extracting a second-level stanza that |
1149 | + * states which value to set from its argument. |
1150 | + * |
1151 | + * Returns: zero on success, negative value on error. |
1152 | + **/ |
1153 | +static int |
1154 | +stanza_apparmor (JobClass *class, |
1155 | + NihConfigStanza *stanza, |
1156 | + const char *file, |
1157 | + size_t len, |
1158 | + size_t *pos, |
1159 | + size_t *lineno) |
1160 | +{ |
1161 | + size_t a_pos, a_lineno; |
1162 | + int ret = -1; |
1163 | + nih_local char *arg = NULL; |
1164 | + Process *process; |
1165 | + |
1166 | + nih_assert (class != NULL); |
1167 | + nih_assert (stanza != NULL); |
1168 | + nih_assert (file != NULL); |
1169 | + nih_assert (pos != NULL); |
1170 | + |
1171 | + a_pos = *pos; |
1172 | + a_lineno = (lineno ? *lineno : 1); |
1173 | + |
1174 | + arg = nih_config_next_token (NULL, file, len, &a_pos, &a_lineno, |
1175 | + NIH_CONFIG_CNLWS, FALSE); |
1176 | + if (! arg) |
1177 | + goto finish; |
1178 | + |
1179 | + if (! strcmp (arg, "load")) { |
1180 | + nih_local char *aaarg = NULL; |
1181 | + |
1182 | + /* Update error position to the load value */ |
1183 | + *pos = a_pos; |
1184 | + if (lineno) |
1185 | + *lineno = a_lineno; |
1186 | + |
1187 | + aaarg = nih_config_next_arg (NULL, file, len, |
1188 | + &a_pos, &a_lineno); |
1189 | + |
1190 | + if (! aaarg) |
1191 | + goto finish; |
1192 | + |
1193 | + /* Allocate a new Process structure if we need to */ |
1194 | + if (! class->process[PROCESS_SECURITY]) { |
1195 | + class->process[PROCESS_SECURITY] = process_new (class->process); |
1196 | + if (! class->process[PROCESS_SECURITY]) |
1197 | + nih_return_system_error (-1); |
1198 | + } |
1199 | + |
1200 | + process = class->process[PROCESS_SECURITY]; |
1201 | + |
1202 | + if (process->command) |
1203 | + nih_unref (process->command, process); |
1204 | + |
1205 | + process->script = FALSE; |
1206 | + process->command = nih_sprintf (process, "%s %s %s", |
1207 | + APPARMOR_PARSER, |
1208 | + APPARMOR_PARSER_OPTS, |
1209 | + aaarg); |
1210 | + |
1211 | + if (! process->command) |
1212 | + nih_return_system_error (-1); |
1213 | + |
1214 | + } else if (! strcmp (arg, "switch")) { |
1215 | + /* Update error position to the switch value */ |
1216 | + *pos = a_pos; |
1217 | + if (lineno) |
1218 | + *lineno = a_lineno; |
1219 | + |
1220 | + class->apparmor_switch = nih_config_next_arg (class, file, |
1221 | + len, &a_pos, |
1222 | + &a_lineno); |
1223 | + |
1224 | + if (! class->apparmor_switch) |
1225 | + goto finish; |
1226 | + |
1227 | + } else { |
1228 | + nih_return_error (-1, NIH_CONFIG_UNKNOWN_STANZA, |
1229 | + _(NIH_CONFIG_UNKNOWN_STANZA_STR)); |
1230 | + } |
1231 | + |
1232 | + ret = nih_config_skip_comment (file, len, &a_pos, &a_lineno); |
1233 | + |
1234 | +finish: |
1235 | + *pos = a_pos; |
1236 | + if (lineno) |
1237 | + *lineno = a_lineno; |
1238 | + |
1239 | + return ret; |
1240 | +} |
1241 | |
1242 | /** |
1243 | * stanza_respawn: |
1244 | |
1245 | === modified file 'init/process.c' |
1246 | --- init/process.c 2012-11-14 14:47:19 +0000 |
1247 | +++ init/process.c 2013-05-27 14:31:27 +0000 |
1248 | @@ -86,6 +86,8 @@ |
1249 | return N_("pre-stop"); |
1250 | case PROCESS_POST_STOP: |
1251 | return N_("post-stop"); |
1252 | + case PROCESS_SECURITY: |
1253 | + return N_("security"); |
1254 | default: |
1255 | return NULL; |
1256 | } |
1257 | @@ -114,6 +116,8 @@ |
1258 | return PROCESS_PRE_STOP; |
1259 | } else if (! strcmp (process, "post-stop")) { |
1260 | return PROCESS_POST_STOP; |
1261 | + } else if (! strcmp (process, "security")) { |
1262 | + return PROCESS_SECURITY; |
1263 | } else { |
1264 | return -1; |
1265 | } |
1266 | @@ -311,6 +315,7 @@ |
1267 | state_enum_to_str (PROCESS_POST_START, type); |
1268 | state_enum_to_str (PROCESS_PRE_STOP, type); |
1269 | state_enum_to_str (PROCESS_POST_STOP, type); |
1270 | + state_enum_to_str (PROCESS_SECURITY, type); |
1271 | |
1272 | return NULL; |
1273 | } |
1274 | @@ -335,6 +340,7 @@ |
1275 | state_str_to_enum (PROCESS_POST_START, type); |
1276 | state_str_to_enum (PROCESS_PRE_STOP, type); |
1277 | state_str_to_enum (PROCESS_POST_STOP, type); |
1278 | + state_str_to_enum (PROCESS_SECURITY, type); |
1279 | |
1280 | return -1; |
1281 | } |
1282 | |
1283 | === modified file 'init/process.h' |
1284 | --- init/process.h 2013-02-27 11:46:04 +0000 |
1285 | +++ init/process.h 2013-05-27 14:31:27 +0000 |
1286 | @@ -45,6 +45,7 @@ |
1287 | PROCESS_POST_START, |
1288 | PROCESS_PRE_STOP, |
1289 | PROCESS_POST_STOP, |
1290 | + PROCESS_SECURITY, |
1291 | PROCESS_LAST, |
1292 | } ProcessType; |
1293 | |
1294 | |
1295 | === added file 'init/tests/data/upstart-pre-security.json' |
1296 | --- init/tests/data/upstart-pre-security.json 1970-01-01 00:00:00 +0000 |
1297 | +++ init/tests/data/upstart-pre-security.json 2013-05-27 14:31:27 +0000 |
1298 | @@ -0,0 +1,1 @@ |
1299 | +{ "sessions": [ ], "events": [ { "session": 0, "name": "Christmas", "env": [ "JOB=udevtrigger", "INSTANCE=", "RESULT=ok" ], "fd": -1, "progress": "EVENT_HANDLING", "failed": 1, "blockers": 1 } ], "job_classes": [ { "session": 0, "name": "security", "path": "\/com\/ubuntu\/Upstart\/jobs\/security", "instance": "", "jobs": [ { "name": "", "path": "\/com\/ubuntu\/Upstart\/jobs\/security\/_", "goal": "JOB_START", "state": "JOB_RUNNING", "env": [ ], "start_env": [ ], "stop_env": [ ], "stop_on": "runlevel [06]", "fds": [ ], "pid": [ 10, 11, 12, 13, 14 ], "kill_process": "PROCESS_INVALID", "failed": 0, "failed_process": "PROCESS_INVALID", "exit_status": 0, "respawn_time": 0, "respawn_count": 0, "trace_forks": 1, "trace_state": "TRACE_NONE", "log": [ { "path": null }, { "path": null }, { "path": null }, { "path": null }, { "path": null } ] } ], "description": null, "author": null, "version": null, "env": [ ], "export": [ ], "start_on": "virtual-filesystems", "stop_on": "runlevel [06]", "emits": [ ], "process": [ { "script": 0, "command": "a" }, { "script": 0, "command": "b" }, { "script": 0, "command": "c" }, { "script": 0, "command": "d" }, { "script": 0, "command": "e" } ], "expect": "EXPECT_FORK", "task": 0, "kill_timeout": 5, "kill_signal": 15, "respawn": 1, "respawn_limit": 10, "respawn_interval": 5, "normalexit": [ ], "console": "CONSOLE_LOG", "umask": 18, "nice": -21, "oom_score_adj": 0, "limits": [ { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 }, { "rlim_cur": 0, "rlim_max": 0 } ], "chroot": null, "chdir": null, "setuid": null, "setgid": null, "deleted": 0, "debug": 0, "usage": null } ] } |
1300 | |
1301 | === modified file 'init/tests/test_job.c' |
1302 | --- init/tests/test_job.c 2012-09-13 16:20:10 +0000 |
1303 | +++ init/tests/test_job.c 2013-05-27 14:31:27 +0000 |
1304 | @@ -1047,7 +1047,7 @@ |
1305 | } |
1306 | |
1307 | job->goal = JOB_START; |
1308 | - job->state = JOB_STARTING; |
1309 | + job->state = JOB_SECURITY; |
1310 | job->pid[PROCESS_PRE_START] = 0; |
1311 | |
1312 | job->blocker = NULL; |
1313 | @@ -1113,7 +1113,7 @@ |
1314 | } |
1315 | |
1316 | job->goal = JOB_START; |
1317 | - job->state = JOB_STARTING; |
1318 | + job->state = JOB_SECURITY; |
1319 | job->pid[PROCESS_MAIN] = 0; |
1320 | |
1321 | job->blocker = NULL; |
1322 | @@ -1187,7 +1187,7 @@ |
1323 | } |
1324 | |
1325 | job->goal = JOB_START; |
1326 | - job->state = JOB_STARTING; |
1327 | + job->state = JOB_SECURITY; |
1328 | job->pid[PROCESS_PRE_START] = 0; |
1329 | |
1330 | job->blocker = NULL; |
1331 | @@ -4000,14 +4000,31 @@ |
1332 | |
1333 | |
1334 | /* Check that the next state if we're starting a starting job is |
1335 | + * security. |
1336 | + */ |
1337 | + TEST_FEATURE ("with starting job and a goal of start"); |
1338 | + job->goal = JOB_START; |
1339 | + job->state = JOB_STARTING; |
1340 | + |
1341 | + TEST_EQ (job_next_state (job), JOB_SECURITY); |
1342 | + |
1343 | + /* Check that the next state if we're starting a security job is |
1344 | * pre-start. |
1345 | */ |
1346 | - TEST_FEATURE ("with starting job and a goal of start"); |
1347 | + TEST_FEATURE ("with security job and a goal of start"); |
1348 | job->goal = JOB_START; |
1349 | - job->state = JOB_STARTING; |
1350 | + job->state = JOB_SECURITY; |
1351 | |
1352 | TEST_EQ (job_next_state (job), JOB_PRE_START); |
1353 | |
1354 | + /* Check that the next state if we're stopping an security job is |
1355 | + * stopping. |
1356 | + */ |
1357 | + TEST_FEATURE ("with security job and a goal of stop"); |
1358 | + job->goal = JOB_STOP; |
1359 | + job->state = JOB_SECURITY; |
1360 | + |
1361 | + TEST_EQ (job_next_state (job), JOB_STOPPING); |
1362 | |
1363 | /* Check that the next state if we're stopping a pre-start job is |
1364 | * stopping. |
1365 | @@ -5695,6 +5712,13 @@ |
1366 | TEST_EQ_STR (name, "starting"); |
1367 | |
1368 | |
1369 | + /* Check that the JOB_SECURITY state returns the right string. */ |
1370 | + TEST_FEATURE ("with security state"); |
1371 | + name = job_state_name (JOB_SECURITY); |
1372 | + |
1373 | + TEST_EQ_STR (name, "security"); |
1374 | + |
1375 | + |
1376 | /* Check that the JOB_PRE_START state returns the right string. */ |
1377 | TEST_FEATURE ("with pre-start state"); |
1378 | name = job_state_name (JOB_PRE_START); |
1379 | @@ -5779,6 +5803,13 @@ |
1380 | TEST_EQ (state, JOB_STARTING); |
1381 | |
1382 | |
1383 | + /* Check that JOB_SECURITY is returned for the right string. */ |
1384 | + TEST_FEATURE ("with security state"); |
1385 | + state = job_state_from_name ("security"); |
1386 | + |
1387 | + TEST_EQ (state, JOB_SECURITY); |
1388 | + |
1389 | + |
1390 | /* Check that JOB_PRE_START is returned for the right string. */ |
1391 | TEST_FEATURE ("with pre-start state"); |
1392 | state = job_state_from_name ("pre-start"); |
1393 | |
1394 | === modified file 'init/tests/test_job_class.c' |
1395 | --- init/tests/test_job_class.c 2013-01-10 17:01:56 +0000 |
1396 | +++ init/tests/test_job_class.c 2013-05-27 14:31:27 +0000 |
1397 | @@ -145,6 +145,8 @@ |
1398 | TEST_EQ_P (class->setuid, NULL); |
1399 | TEST_EQ_P (class->setgid, NULL); |
1400 | |
1401 | + TEST_EQ_P (class->apparmor_switch, NULL); |
1402 | + |
1403 | TEST_FALSE (class->deleted); |
1404 | |
1405 | nih_free (class); |
1406 | |
1407 | === modified file 'init/tests/test_parse_job.c' |
1408 | --- init/tests/test_parse_job.c 2012-10-22 13:29:45 +0000 |
1409 | +++ init/tests/test_parse_job.c 2013-05-27 14:31:27 +0000 |
1410 | @@ -36,6 +36,7 @@ |
1411 | #include "conf.h" |
1412 | #include "parse_job.h" |
1413 | #include "errors.h" |
1414 | +#include "apparmor.h" |
1415 | |
1416 | |
1417 | void |
1418 | @@ -467,6 +468,267 @@ |
1419 | } |
1420 | |
1421 | void |
1422 | +test_stanza_apparmor (void) |
1423 | +{ |
1424 | + JobClass *job; |
1425 | + Process *process; |
1426 | + NihError *err; |
1427 | + size_t pos, lineno; |
1428 | + char buf[1024]; |
1429 | + |
1430 | + TEST_FUNCTION ("stanza_apparmor"); |
1431 | + |
1432 | + |
1433 | + /* Check that an apparmor load stanza sets the process of the |
1434 | + * job as a single string. |
1435 | + */ |
1436 | + TEST_FEATURE ("with load and profile"); |
1437 | + strcpy (buf, "apparmor load /etc/apparmor.d/usr.sbin.cupsd\n"); |
1438 | + |
1439 | + /* TODO: investigate why we can't use TEST_ALLOC_FAIL here. |
1440 | + * It fails when nih_sprintf() is used. |
1441 | + */ |
1442 | + |
1443 | + pos = 0; |
1444 | + lineno = 1; |
1445 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), |
1446 | + &pos, &lineno); |
1447 | + |
1448 | + TEST_EQ (pos, strlen (buf)); |
1449 | + TEST_EQ (lineno, 2); |
1450 | + |
1451 | + TEST_ALLOC_SIZE (job, sizeof (JobClass)); |
1452 | + |
1453 | + process = job->process[PROCESS_SECURITY]; |
1454 | + TEST_ALLOC_PARENT (process, job->process); |
1455 | + TEST_ALLOC_SIZE (process, sizeof (Process)); |
1456 | + TEST_EQ (process->script, FALSE); |
1457 | + TEST_ALLOC_PARENT (process->command, process); |
1458 | + strcpy (buf, APPARMOR_PARSER); |
1459 | + strcat (buf, " "); |
1460 | + strcat (buf, APPARMOR_PARSER_OPTS); |
1461 | + strcat (buf, " /etc/apparmor.d/usr.sbin.cupsd"); |
1462 | + TEST_EQ_STR (process->command, buf); |
1463 | + |
1464 | + nih_free (job); |
1465 | + |
1466 | + |
1467 | + /* Check that the last of multiple apparmor load stanzas is used. */ |
1468 | + TEST_FEATURE ("with multiple load"); |
1469 | + strcpy (buf, "apparmor load /etc/apparmor.d/usr.sbin.rsyslogd\n"); |
1470 | + strcat (buf, "apparmor load /etc/apparmor.d/usr.sbin.cupsd\n"); |
1471 | + |
1472 | + /* TODO: investigate why we can't use TEST_ALLOC_FAIL here. |
1473 | + * It fails when nih_sprintf() is used. |
1474 | + */ |
1475 | + |
1476 | + pos = 0; |
1477 | + lineno = 1; |
1478 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), |
1479 | + &pos, &lineno); |
1480 | + |
1481 | + TEST_EQ (pos, strlen (buf)); |
1482 | + TEST_EQ (lineno, 3); |
1483 | + |
1484 | + TEST_ALLOC_SIZE (job, sizeof (JobClass)); |
1485 | + |
1486 | + process = job->process[PROCESS_SECURITY]; |
1487 | + TEST_ALLOC_PARENT (process, job->process); |
1488 | + TEST_ALLOC_SIZE (process, sizeof (Process)); |
1489 | + TEST_EQ (process->script, FALSE); |
1490 | + TEST_ALLOC_PARENT (process->command, process); |
1491 | + strcpy (buf, APPARMOR_PARSER); |
1492 | + strcat (buf, " "); |
1493 | + strcat (buf, APPARMOR_PARSER_OPTS); |
1494 | + strcat (buf, " /etc/apparmor.d/usr.sbin.cupsd"); |
1495 | + TEST_EQ_STR (process->command, buf); |
1496 | + |
1497 | + nih_free (job); |
1498 | + |
1499 | + |
1500 | + /* Check that an apparmor load stanza without any arguments results |
1501 | + * in a syntax error. |
1502 | + */ |
1503 | + TEST_FEATURE ("with load but no profile"); |
1504 | + strcpy (buf, "apparmor load\n"); |
1505 | + |
1506 | + pos = 0; |
1507 | + lineno = 1; |
1508 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); |
1509 | + |
1510 | + TEST_EQ_P (job, NULL); |
1511 | + |
1512 | + err = nih_error_get (); |
1513 | + TEST_EQ (err->number, NIH_CONFIG_EXPECTED_TOKEN); |
1514 | + TEST_EQ (pos, 13); |
1515 | + TEST_EQ (lineno, 1); |
1516 | + nih_free (err); |
1517 | + |
1518 | + |
1519 | + /* Check that an apparmor load stanza with an extra argument |
1520 | + * results in a syntax error. |
1521 | + */ |
1522 | + TEST_FEATURE ("with extra argument to load"); |
1523 | + strcpy (buf, "apparmor load /etc/apparmor.d/usr.sbin.cupsd extra\n"); |
1524 | + |
1525 | + pos = 0; |
1526 | + lineno = 1; |
1527 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); |
1528 | + |
1529 | + TEST_EQ_P (job, NULL); |
1530 | + |
1531 | + err = nih_error_get (); |
1532 | + TEST_EQ (err->number, NIH_CONFIG_UNEXPECTED_TOKEN); |
1533 | + TEST_EQ (pos, 45); |
1534 | + TEST_EQ (lineno, 1); |
1535 | + nih_free (err); |
1536 | + |
1537 | + |
1538 | + /* Check that an apparmor stanza with an unknown second argument |
1539 | + * results in a syntax error. |
1540 | + */ |
1541 | + TEST_FEATURE ("with unknown argument"); |
1542 | + strcpy (buf, "apparmor foo\n"); |
1543 | + |
1544 | + pos = 0; |
1545 | + lineno = 1; |
1546 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); |
1547 | + |
1548 | + TEST_EQ_P (job, NULL); |
1549 | + |
1550 | + err = nih_error_get (); |
1551 | + TEST_EQ (err->number, NIH_CONFIG_UNKNOWN_STANZA); |
1552 | + TEST_EQ (pos, 9); |
1553 | + TEST_EQ (lineno, 1); |
1554 | + nih_free (err); |
1555 | + |
1556 | + |
1557 | + /* Check that an apparmor stanza with no second argument |
1558 | + * results in a syntax error. |
1559 | + */ |
1560 | + TEST_FEATURE ("with missing argument"); |
1561 | + strcpy (buf, "apparmor\n"); |
1562 | + |
1563 | + pos = 0; |
1564 | + lineno = 1; |
1565 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); |
1566 | + |
1567 | + TEST_EQ_P (job, NULL); |
1568 | + |
1569 | + err = nih_error_get (); |
1570 | + TEST_EQ (err->number, NIH_CONFIG_EXPECTED_TOKEN); |
1571 | + TEST_EQ (pos, 8); |
1572 | + TEST_EQ (lineno, 1); |
1573 | + nih_free (err); |
1574 | + |
1575 | + |
1576 | + /* Check that an apparmor switch stanza results in it |
1577 | + * being stored in the job. |
1578 | + */ |
1579 | + TEST_FEATURE ("with switch and profile"); |
1580 | + strcpy (buf, "apparmor switch /usr/sbin/cupsd\n"); |
1581 | + |
1582 | + TEST_ALLOC_FAIL { |
1583 | + pos = 0; |
1584 | + lineno = 1; |
1585 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), |
1586 | + &pos, &lineno); |
1587 | + |
1588 | + if (test_alloc_failed) { |
1589 | + TEST_EQ_P (job, NULL); |
1590 | + |
1591 | + err = nih_error_get (); |
1592 | + TEST_EQ (err->number, ENOMEM); |
1593 | + nih_free (err); |
1594 | + |
1595 | + continue; |
1596 | + } |
1597 | + |
1598 | + TEST_EQ (pos, strlen (buf)); |
1599 | + TEST_EQ (lineno, 2); |
1600 | + |
1601 | + TEST_ALLOC_SIZE (job, sizeof (JobClass)); |
1602 | + |
1603 | + TEST_ALLOC_PARENT (job->apparmor_switch, job); |
1604 | + TEST_EQ_STR (job->apparmor_switch, "/usr/sbin/cupsd"); |
1605 | + |
1606 | + nih_free (job); |
1607 | + } |
1608 | + |
1609 | + |
1610 | + /* Check that the last of multiple apparmor switch stanzas is used. */ |
1611 | + TEST_FEATURE ("with multiple apparmor switch stanzas"); |
1612 | + strcpy (buf, "apparmor switch /usr/sbin/rsyslogd\n"); |
1613 | + strcat (buf, "apparmor switch /usr/sbin/cupsd\n"); |
1614 | + |
1615 | + TEST_ALLOC_FAIL { |
1616 | + pos = 0; |
1617 | + lineno = 1; |
1618 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), |
1619 | + &pos, &lineno); |
1620 | + |
1621 | + if (test_alloc_failed) { |
1622 | + TEST_EQ_P (job, NULL); |
1623 | + |
1624 | + err = nih_error_get (); |
1625 | + TEST_EQ (err->number, ENOMEM); |
1626 | + nih_free (err); |
1627 | + |
1628 | + continue; |
1629 | + } |
1630 | + |
1631 | + TEST_EQ (pos, strlen (buf)); |
1632 | + TEST_EQ (lineno, 3); |
1633 | + |
1634 | + TEST_ALLOC_SIZE (job, sizeof (JobClass)); |
1635 | + |
1636 | + TEST_ALLOC_PARENT (job->apparmor_switch, job); |
1637 | + TEST_EQ_STR (job->apparmor_switch, "/usr/sbin/cupsd"); |
1638 | + |
1639 | + nih_free (job); |
1640 | + } |
1641 | + |
1642 | + |
1643 | + /* Check that an apparmor switch stanza without a profile results in |
1644 | + * a syntax error. |
1645 | + */ |
1646 | + TEST_FEATURE ("with switch and no profile"); |
1647 | + strcpy (buf, "apparmor switch\n"); |
1648 | + |
1649 | + pos = 0; |
1650 | + lineno = 1; |
1651 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); |
1652 | + |
1653 | + TEST_EQ_P (job, NULL); |
1654 | + |
1655 | + err = nih_error_get (); |
1656 | + TEST_EQ (err->number, NIH_CONFIG_EXPECTED_TOKEN); |
1657 | + TEST_EQ (pos, 15); |
1658 | + TEST_EQ (lineno, 1); |
1659 | + nih_free (err); |
1660 | + |
1661 | + |
1662 | + /* Check that an apparmor switch stanza with an extra second argument |
1663 | + * results in a syntax error. |
1664 | + */ |
1665 | + TEST_FEATURE ("with extra argument to switch"); |
1666 | + strcpy (buf, "apparmor switch /usr/sbin/cupsd extra\n"); |
1667 | + |
1668 | + pos = 0; |
1669 | + lineno = 1; |
1670 | + job = parse_job (NULL, NULL, NULL, "test", buf, strlen (buf), &pos, &lineno); |
1671 | + |
1672 | + TEST_EQ_P (job, NULL); |
1673 | + |
1674 | + err = nih_error_get (); |
1675 | + TEST_EQ (err->number, NIH_CONFIG_UNEXPECTED_TOKEN); |
1676 | + TEST_EQ (pos, 32); |
1677 | + TEST_EQ (lineno, 1); |
1678 | + nih_free (err); |
1679 | + |
1680 | +} |
1681 | + |
1682 | +void |
1683 | test_stanza_pre_start (void) |
1684 | { |
1685 | JobClass *job; |
1686 | @@ -8372,6 +8634,7 @@ |
1687 | |
1688 | test_stanza_exec (); |
1689 | test_stanza_script (); |
1690 | + test_stanza_apparmor (); |
1691 | test_stanza_pre_start (); |
1692 | test_stanza_post_start (); |
1693 | test_stanza_pre_stop (); |
1694 | |
1695 | === modified file 'init/tests/test_process.c' |
1696 | --- init/tests/test_process.c 2011-05-15 12:53:17 +0000 |
1697 | +++ init/tests/test_process.c 2013-05-27 14:31:27 +0000 |
1698 | @@ -68,6 +68,13 @@ |
1699 | TEST_EQ_STR (name, "main"); |
1700 | |
1701 | |
1702 | + /* Check that PROCESS_SECURITY returns the right string. */ |
1703 | + TEST_FEATURE ("with security process"); |
1704 | + name = process_name (PROCESS_SECURITY); |
1705 | + |
1706 | + TEST_EQ_STR (name, "security"); |
1707 | + |
1708 | + |
1709 | /* Check that PROCESS_PRE_START returns the right string. */ |
1710 | TEST_FEATURE ("with pre-start process"); |
1711 | name = process_name (PROCESS_PRE_START); |
1712 | @@ -117,6 +124,13 @@ |
1713 | TEST_EQ (process, PROCESS_MAIN); |
1714 | |
1715 | |
1716 | + /* Check that PROCESS_SECURITY is returned for the string. */ |
1717 | + TEST_FEATURE ("with security process"); |
1718 | + process = process_from_name ("security"); |
1719 | + |
1720 | + TEST_EQ (process, PROCESS_SECURITY); |
1721 | + |
1722 | + |
1723 | /* Check that PROCESS_PRE_START is returned for the string. */ |
1724 | TEST_FEATURE ("with pre-start process"); |
1725 | process = process_from_name ("pre-start"); |
1726 | |
1727 | === modified file 'init/tests/test_state.c' |
1728 | --- init/tests/test_state.c 2013-02-27 11:46:04 +0000 |
1729 | +++ init/tests/test_state.c 2013-05-27 14:31:27 +0000 |
1730 | @@ -143,6 +143,7 @@ |
1731 | __attribute__ ((warn_unused_result)); |
1732 | |
1733 | void test_upstart1_6_upgrade (const char *conf_file, const char *path); |
1734 | +void test_upstart_pre_security_upgrade (const char *conf_file, const char *path); |
1735 | |
1736 | /** |
1737 | * TestDataFile: |
1738 | @@ -175,6 +176,7 @@ |
1739 | **/ |
1740 | TestDataFile test_data_files[] = { |
1741 | { "bar", "upstart-1.6.json", test_upstart1_6_upgrade }, |
1742 | + { "security", "upstart-pre-security.json", test_upstart_pre_security_upgrade }, |
1743 | |
1744 | { NULL, NULL, NULL } |
1745 | }; |
1746 | @@ -615,6 +617,8 @@ |
1747 | if (obj_string_check (a, b, usage)) |
1748 | goto fail; |
1749 | |
1750 | + if (obj_string_check (a, b, apparmor_switch)) |
1751 | + goto fail; |
1752 | |
1753 | return 0; |
1754 | |
1755 | @@ -1013,6 +1017,12 @@ |
1756 | foo->process[PROCESS_MAIN]->command = NIH_MUST (nih_strdup (foo->process[PROCESS_MAIN], |
1757 | "echo hello !£$%^&*()_+-={}:@~;'#<>?,./")); |
1758 | |
1759 | + foo->process[PROCESS_SECURITY] = process_new (foo->process); |
1760 | + TEST_NE_P (foo->process[PROCESS_SECURITY], NULL); |
1761 | + foo->process[PROCESS_SECURITY]->script = 0; |
1762 | + foo->process[PROCESS_SECURITY]->command = NIH_MUST (nih_strdup (foo->process[PROCESS_SECURITY], |
1763 | + "/bin/true")); |
1764 | + |
1765 | foo->process[PROCESS_PRE_START] = process_new (foo->process); |
1766 | TEST_NE_P (foo->process[PROCESS_PRE_START], NULL); |
1767 | foo->process[PROCESS_PRE_START]->script = 0; |
1768 | @@ -3068,6 +3078,131 @@ |
1769 | |
1770 | nih_free (event); |
1771 | nih_free (conf_sources); |
1772 | + conf_sources = NULL; |
1773 | + nih_free (job_classes); |
1774 | + job_classes = NULL; |
1775 | +} |
1776 | + |
1777 | +/** |
1778 | + * test_upstart_pre_security_upgrade: |
1779 | + * |
1780 | + * @conf_file: name of ConfFile to create prior to running test, |
1781 | + * @path: full path to JSON data file to deserialise. |
1782 | + * |
1783 | + * Test for Upstart pre-security serialisation data format that doesn't |
1784 | + * contain apparmor_switch element, and PROCESS_SECURITY. |
1785 | + * |
1786 | + **/ |
1787 | +void |
1788 | +test_upstart_pre_security_upgrade (const char *conf_file, const char *path) |
1789 | +{ |
1790 | + nih_local char *json_string = NULL; |
1791 | + Event *event; |
1792 | + ConfSource *source; |
1793 | + ConfFile *file; |
1794 | + nih_local char *conf_file_path = NULL; |
1795 | + struct stat statbuf; |
1796 | + size_t len; |
1797 | + |
1798 | + nih_assert (conf_file); |
1799 | + nih_assert (path); |
1800 | + |
1801 | + conf_init (); |
1802 | + session_init (); |
1803 | + event_init (); |
1804 | + control_init (); |
1805 | + job_class_init (); |
1806 | + |
1807 | + TEST_LIST_EMPTY (sessions); |
1808 | + TEST_LIST_EMPTY (events); |
1809 | + TEST_LIST_EMPTY (conf_sources); |
1810 | + TEST_HASH_EMPTY (job_classes); |
1811 | + |
1812 | + /* Check data file exists */ |
1813 | + TEST_EQ (stat (path, &statbuf), 0); |
1814 | + |
1815 | + json_string = nih_file_read (NULL, path, &len); |
1816 | + TEST_NE_P (json_string, NULL); |
1817 | + |
1818 | + /* Create the ConfSource and ConfFile objects to simulate |
1819 | + * Upstart reading /etc/init on startup. Required since we |
1820 | + * don't currently serialise these objects. |
1821 | + */ |
1822 | + source = conf_source_new (NULL, "/tmp/security", CONF_JOB_DIR); |
1823 | + TEST_NE_P (source, NULL); |
1824 | + |
1825 | + conf_file_path = NIH_MUST (nih_sprintf (NULL, "%s/%s", |
1826 | + "/tmp/security", conf_file)); |
1827 | + |
1828 | + file = conf_file_new (source, conf_file_path); |
1829 | + TEST_NE_P (file, NULL); |
1830 | + |
1831 | + /* Recreate state from JSON data file */ |
1832 | + assert0 (state_from_string (json_string)); |
1833 | + |
1834 | + TEST_LIST_NOT_EMPTY (conf_sources); |
1835 | + TEST_LIST_NOT_EMPTY (events); |
1836 | + TEST_HASH_NOT_EMPTY (job_classes); |
1837 | + TEST_LIST_EMPTY (sessions); |
1838 | + |
1839 | + event = (Event *)nih_list_remove (events->next); |
1840 | + TEST_NE_P (event, NULL); |
1841 | + TEST_EQ_STR (event->name, "Christmas"); |
1842 | + |
1843 | + NIH_HASH_FOREACH (job_classes, iter) { |
1844 | + JobClass *class = (JobClass *)iter; |
1845 | + |
1846 | + TEST_EQ_STR (class->name, "security"); |
1847 | + TEST_EQ_STR (class->path, "/com/ubuntu/Upstart/jobs/security"); |
1848 | + TEST_EQ_P (class->apparmor_switch, NULL); |
1849 | + TEST_HASH_NOT_EMPTY (class->instances); |
1850 | + |
1851 | + TEST_EQ_P (class->process[PROCESS_SECURITY], NULL); |
1852 | + |
1853 | + TEST_FALSE (class->process[PROCESS_MAIN]->script); |
1854 | + TEST_FALSE (class->process[PROCESS_PRE_START]->script); |
1855 | + TEST_FALSE (class->process[PROCESS_POST_START]->script); |
1856 | + TEST_FALSE (class->process[PROCESS_PRE_STOP]->script); |
1857 | + TEST_FALSE (class->process[PROCESS_POST_STOP]->script); |
1858 | + |
1859 | + TEST_EQ_STR (class->process[PROCESS_MAIN]->command, "a"); |
1860 | + TEST_EQ_STR (class->process[PROCESS_PRE_START]->command, "b"); |
1861 | + TEST_EQ_STR (class->process[PROCESS_POST_START]->command, "c"); |
1862 | + TEST_EQ_STR (class->process[PROCESS_PRE_STOP]->command, "d"); |
1863 | + TEST_EQ_STR (class->process[PROCESS_POST_STOP]->command, "e"); |
1864 | + |
1865 | + NIH_HASH_FOREACH (class->instances, iter2) { |
1866 | + Job *job = (Job *)iter2; |
1867 | + nih_local char *instance_path = NULL; |
1868 | + |
1869 | + /* instance name */ |
1870 | + TEST_EQ_STR (job->name, ""); |
1871 | + |
1872 | + instance_path = NIH_MUST (nih_sprintf (NULL, "%s/_", class->path)); |
1873 | + TEST_EQ_STR (job->path, instance_path); |
1874 | + |
1875 | + TEST_EQ (job->pid[PROCESS_MAIN], 10); |
1876 | + TEST_EQ (job->pid[PROCESS_PRE_START], 11); |
1877 | + TEST_EQ (job->pid[PROCESS_POST_START], 12); |
1878 | + TEST_EQ (job->pid[PROCESS_PRE_STOP], 13); |
1879 | + TEST_EQ (job->pid[PROCESS_POST_STOP], 14); |
1880 | + TEST_EQ (job->pid[PROCESS_SECURITY], 0); |
1881 | + |
1882 | + TEST_EQ_P (job->log[PROCESS_MAIN], NULL); |
1883 | + TEST_EQ_P (job->log[PROCESS_PRE_START], NULL); |
1884 | + TEST_EQ_P (job->log[PROCESS_POST_START], NULL); |
1885 | + TEST_EQ_P (job->log[PROCESS_PRE_STOP], NULL); |
1886 | + TEST_EQ_P (job->log[PROCESS_POST_STOP], NULL); |
1887 | + TEST_EQ_P (job->log[PROCESS_SECURITY], NULL); |
1888 | + |
1889 | + } |
1890 | + } |
1891 | + |
1892 | + nih_free (event); |
1893 | + nih_free (conf_sources); |
1894 | + conf_sources = NULL; |
1895 | + nih_free (job_classes); |
1896 | + job_classes = NULL; |
1897 | } |
1898 | |
1899 | int |
This is excellent. I really like the way you've introduced the new security process type as it is extensible and allows any apparmor_parser failure output to be logged automatically ;-)
My only comment is that we could do with on more test to assert that we can handle reading an "entire" state file that does _not_ contain the new AppArmor-related serialisation data. Specifically, can you add an explicit test to init/tests/ test_state. c:test_ data_files that:
1) Reads in a prepared JSON file without the AppArmor content. >apparmor_ switch has a reasonable value. PROCESS_ SECURITY] has a reasonable value.
2) Asserts 'assert0 (state_from_string (json_string));'
3) Checks that JobClass-
4) Checks that Job->pid[
Also, a reminder-to-self:
Once lp:~jamesodhunt/upstart/serialise-remaining-objects lands, we'll need to bump STATE_VERSION to take account of the new serialisation format (PROCESS_SECURITY and JobClass- >apparmor_ switch) .