]>
Commit | Line | Data |
---|---|---|
cc73685d | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
250b1eec | 2 | |
1160ce89 CB |
3 | #include "config.h" |
4 | ||
d38dd64a | 5 | #include <errno.h> |
e075f5d9 | 6 | #include <stdio.h> |
9958532b | 7 | #include <stdlib.h> |
e075f5d9 | 8 | #include <sys/mount.h> |
d38dd64a CB |
9 | #include <sys/stat.h> |
10 | #include <sys/types.h> | |
85108024 | 11 | #include <sys/vfs.h> |
d38dd64a | 12 | #include <unistd.h> |
f2363e38 | 13 | |
d38dd64a | 14 | #include "caps.h" |
c988c8b1 | 15 | #include "cgroups/cgroup_utils.h" |
7aff4f43 | 16 | #include "conf.h" |
7e556d18 | 17 | #include "initutils.h" |
46bf13b7 | 18 | #include "file_utils.h" |
d38dd64a CB |
19 | #include "log.h" |
20 | #include "lsm.h" | |
589a930f | 21 | #include "open_utils.h" |
1800f924 | 22 | #include "parse.h" |
f40988c7 | 23 | #include "process_utils.h" |
d38dd64a | 24 | #include "utils.h" |
e075f5d9 | 25 | |
ac2cecc4 | 26 | lxc_log_define(apparmor, lsm); |
e075f5d9 | 27 | |
fe4de9a6 | 28 | #define AA_DEF_PROFILE "lxc-container-default" |
603fd084 | 29 | #define AA_DEF_PROFILE_CGNS "lxc-container-default-cgns" |
e075f5d9 SH |
30 | #define AA_MOUNT_RESTR "/sys/kernel/security/apparmor/features/mount/mask" |
31 | #define AA_ENABLED_FILE "/sys/module/apparmor/parameters/enabled" | |
480c876b | 32 | #define AA_UNCHANGED "unchanged" |
1800f924 WB |
33 | #define AA_GENERATED "generated" |
34 | ||
35 | #define AA_CMD_LOAD 'r' | |
36 | #define AA_CMD_UNLOAD 'R' | |
37 | #define AA_CMD_PARSE 'Q' | |
38 | ||
39 | static const char AA_PROFILE_BASE[] = | |
40 | " ### Base profile\n" | |
41 | " capability,\n" | |
42 | " dbus,\n" | |
43 | " file,\n" | |
44 | " network,\n" | |
45 | " umount,\n" | |
46 | "\n" | |
47 | " # Allow us to receive signals from anywhere.\n" | |
48 | " signal (receive),\n" | |
49 | "\n" | |
50 | " # Allow us to send signals to ourselves\n" | |
51 | " signal peer=@{profile_name},\n" | |
52 | "\n" | |
53 | " # Allow other processes to read our /proc entries, futexes, perf tracing and\n" | |
54 | " # kcmp for now (they will need 'read' in the first place). Administrators can\n" | |
55 | " # override with:\n" | |
56 | " # deny ptrace (readby) ...\n" | |
57 | " ptrace (readby),\n" | |
58 | "\n" | |
59 | " # Allow other processes to trace us by default (they will need 'trace' in\n" | |
60 | " # the first place). Administrators can override with:\n" | |
61 | " # deny ptrace (tracedby) ...\n" | |
62 | " ptrace (tracedby),\n" | |
63 | "\n" | |
64 | " # Allow us to ptrace ourselves\n" | |
65 | " ptrace peer=@{profile_name},\n" | |
66 | "\n" | |
67 | " # ignore DENIED message on / remount\n" | |
68 | " deny mount options=(ro, remount) -> /,\n" | |
69 | " deny mount options=(ro, remount, silent) -> /,\n" | |
70 | "\n" | |
71 | " # allow tmpfs mounts everywhere\n" | |
72 | " mount fstype=tmpfs,\n" | |
73 | "\n" | |
74 | " # allow hugetlbfs mounts everywhere\n" | |
75 | " mount fstype=hugetlbfs,\n" | |
76 | "\n" | |
77 | " # allow mqueue mounts everywhere\n" | |
78 | " mount fstype=mqueue,\n" | |
79 | "\n" | |
80 | " # allow fuse mounts everywhere\n" | |
81 | " mount fstype=fuse,\n" | |
82 | " mount fstype=fuse.*,\n" | |
83 | "\n" | |
84 | " # deny access under /proc/bus to avoid e.g. messing with pci devices directly\n" | |
85 | " deny @{PROC}/bus/** wklx,\n" | |
86 | "\n" | |
87 | " # deny writes in /proc/sys/fs but allow binfmt_misc to be mounted\n" | |
88 | " mount fstype=binfmt_misc -> /proc/sys/fs/binfmt_misc/,\n" | |
89 | " deny @{PROC}/sys/fs/** wklx,\n" | |
90 | "\n" | |
91 | " # allow efivars to be mounted, writing to it will be blocked though\n" | |
92 | " mount fstype=efivarfs -> /sys/firmware/efi/efivars/,\n" | |
93 | "\n" | |
94 | " # block some other dangerous paths\n" | |
95 | " deny @{PROC}/kcore rwklx,\n" | |
96 | " deny @{PROC}/sysrq-trigger rwklx,\n" | |
95ad620e | 97 | " deny @{PROC}/acpi/** rwklx,\n" |
1800f924 WB |
98 | "\n" |
99 | " # deny writes in /sys except for /sys/fs/cgroup, also allow\n" | |
100 | " # fusectl, securityfs and debugfs to be mounted there (read-only)\n" | |
101 | " mount fstype=fusectl -> /sys/fs/fuse/connections/,\n" | |
102 | " mount fstype=securityfs -> /sys/kernel/security/,\n" | |
103 | " mount fstype=debugfs -> /sys/kernel/debug/,\n" | |
104 | " deny mount fstype=debugfs -> /var/lib/ureadahead/debugfs/,\n" | |
105 | " mount fstype=proc -> /proc/,\n" | |
106 | " mount fstype=sysfs -> /sys/,\n" | |
107 | " mount options=(rw, nosuid, nodev, noexec, remount) -> /sys/,\n" | |
108 | " deny /sys/firmware/efi/efivars/** rwklx,\n" | |
109 | " # note, /sys/kernel/security/** handled below\n" | |
110 | " mount options=(ro, nosuid, nodev, noexec, remount, strictatime) -> /sys/fs/cgroup/,\n" | |
111 | "\n" | |
112 | " # deny reads from debugfs\n" | |
113 | " deny /sys/kernel/debug/{,**} rwklx,\n" | |
114 | "\n" | |
9e61fb1f | 115 | " # allow paths to be made dependent, shared, private or unbindable\n" |
50e3e83d | 116 | " # TODO: This currently doesn't work due to the apparmor parser treating those as allowing all mounts.\n" |
1800f924 WB |
117 | "# mount options=(rw,make-slave) -> **,\n" |
118 | "# mount options=(rw,make-rslave) -> **,\n" | |
119 | "# mount options=(rw,make-shared) -> **,\n" | |
120 | "# mount options=(rw,make-rshared) -> **,\n" | |
121 | "# mount options=(rw,make-private) -> **,\n" | |
122 | "# mount options=(rw,make-rprivate) -> **,\n" | |
123 | "# mount options=(rw,make-unbindable) -> **,\n" | |
124 | "# mount options=(rw,make-runbindable) -> **,\n" | |
125 | "\n" | |
8fddf007 WB |
126 | "# Allow limited modification of mount propagation\n" |
127 | " mount options=(rw,make-slave) -> /,\n" | |
128 | " mount options=(rw,make-rslave) -> /,\n" | |
129 | " mount options=(rw,make-shared) -> /,\n" | |
130 | " mount options=(rw,make-rshared) -> /,\n" | |
131 | " mount options=(rw,make-private) -> /,\n" | |
132 | " mount options=(rw,make-rprivate) -> /,\n" | |
133 | " mount options=(rw,make-unbindable) -> /,\n" | |
134 | " mount options=(rw,make-runbindable) -> /,\n" | |
135 | "\n" | |
1800f924 WB |
136 | " # allow bind-mounts of anything except /proc, /sys and /dev\n" |
137 | " mount options=(rw,bind) /[^spd]*{,/**},\n" | |
138 | " mount options=(rw,bind) /d[^e]*{,/**},\n" | |
139 | " mount options=(rw,bind) /de[^v]*{,/**},\n" | |
140 | " mount options=(rw,bind) /dev/.[^l]*{,/**},\n" | |
141 | " mount options=(rw,bind) /dev/.l[^x]*{,/**},\n" | |
142 | " mount options=(rw,bind) /dev/.lx[^c]*{,/**},\n" | |
143 | " mount options=(rw,bind) /dev/.lxc?*{,/**},\n" | |
144 | " mount options=(rw,bind) /dev/[^.]*{,/**},\n" | |
145 | " mount options=(rw,bind) /dev?*{,/**},\n" | |
146 | " mount options=(rw,bind) /p[^r]*{,/**},\n" | |
147 | " mount options=(rw,bind) /pr[^o]*{,/**},\n" | |
148 | " mount options=(rw,bind) /pro[^c]*{,/**},\n" | |
149 | " mount options=(rw,bind) /proc?*{,/**},\n" | |
150 | " mount options=(rw,bind) /s[^y]*{,/**},\n" | |
151 | " mount options=(rw,bind) /sy[^s]*{,/**},\n" | |
152 | " mount options=(rw,bind) /sys?*{,/**},\n" | |
153 | "\n" | |
8fddf007 WB |
154 | " # Allow rbind-mounts of anything except /, /dev, /proc and /sys\n" |
155 | " mount options=(rw,rbind) /[^spd]*{,/**},\n" | |
156 | " mount options=(rw,rbind) /d[^e]*{,/**},\n" | |
157 | " mount options=(rw,rbind) /de[^v]*{,/**},\n" | |
158 | " mount options=(rw,rbind) /dev?*{,/**},\n" | |
159 | " mount options=(rw,rbind) /p[^r]*{,/**},\n" | |
160 | " mount options=(rw,rbind) /pr[^o]*{,/**},\n" | |
161 | " mount options=(rw,rbind) /pro[^c]*{,/**},\n" | |
162 | " mount options=(rw,rbind) /proc?*{,/**},\n" | |
163 | " mount options=(rw,rbind) /s[^y]*{,/**},\n" | |
164 | " mount options=(rw,rbind) /sy[^s]*{,/**},\n" | |
165 | " mount options=(rw,rbind) /sys?*{,/**},\n" | |
1800f924 WB |
166 | "\n" |
167 | " # allow moving mounts except for /proc, /sys and /dev\n" | |
168 | " mount options=(rw,move) /[^spd]*{,/**},\n" | |
169 | " mount options=(rw,move) /d[^e]*{,/**},\n" | |
170 | " mount options=(rw,move) /de[^v]*{,/**},\n" | |
171 | " mount options=(rw,move) /dev/.[^l]*{,/**},\n" | |
172 | " mount options=(rw,move) /dev/.l[^x]*{,/**},\n" | |
173 | " mount options=(rw,move) /dev/.lx[^c]*{,/**},\n" | |
174 | " mount options=(rw,move) /dev/.lxc?*{,/**},\n" | |
175 | " mount options=(rw,move) /dev/[^.]*{,/**},\n" | |
176 | " mount options=(rw,move) /dev?*{,/**},\n" | |
177 | " mount options=(rw,move) /p[^r]*{,/**},\n" | |
178 | " mount options=(rw,move) /pr[^o]*{,/**},\n" | |
179 | " mount options=(rw,move) /pro[^c]*{,/**},\n" | |
180 | " mount options=(rw,move) /proc?*{,/**},\n" | |
181 | " mount options=(rw,move) /s[^y]*{,/**},\n" | |
182 | " mount options=(rw,move) /sy[^s]*{,/**},\n" | |
183 | " mount options=(rw,move) /sys?*{,/**},\n" | |
184 | "\n" | |
185 | " # generated by: lxc-generate-aa-rules.py container-rules.base\n" | |
186 | " deny /proc/sys/[^kn]*{,/**} wklx,\n" | |
187 | " deny /proc/sys/k[^e]*{,/**} wklx,\n" | |
188 | " deny /proc/sys/ke[^r]*{,/**} wklx,\n" | |
189 | " deny /proc/sys/ker[^n]*{,/**} wklx,\n" | |
190 | " deny /proc/sys/kern[^e]*{,/**} wklx,\n" | |
191 | " deny /proc/sys/kerne[^l]*{,/**} wklx,\n" | |
192 | " deny /proc/sys/kernel/[^smhd]*{,/**} wklx,\n" | |
193 | " deny /proc/sys/kernel/d[^o]*{,/**} wklx,\n" | |
194 | " deny /proc/sys/kernel/do[^m]*{,/**} wklx,\n" | |
195 | " deny /proc/sys/kernel/dom[^a]*{,/**} wklx,\n" | |
196 | " deny /proc/sys/kernel/doma[^i]*{,/**} wklx,\n" | |
197 | " deny /proc/sys/kernel/domai[^n]*{,/**} wklx,\n" | |
198 | " deny /proc/sys/kernel/domain[^n]*{,/**} wklx,\n" | |
199 | " deny /proc/sys/kernel/domainn[^a]*{,/**} wklx,\n" | |
200 | " deny /proc/sys/kernel/domainna[^m]*{,/**} wklx,\n" | |
201 | " deny /proc/sys/kernel/domainnam[^e]*{,/**} wklx,\n" | |
202 | " deny /proc/sys/kernel/domainname?*{,/**} wklx,\n" | |
203 | " deny /proc/sys/kernel/h[^o]*{,/**} wklx,\n" | |
204 | " deny /proc/sys/kernel/ho[^s]*{,/**} wklx,\n" | |
205 | " deny /proc/sys/kernel/hos[^t]*{,/**} wklx,\n" | |
206 | " deny /proc/sys/kernel/host[^n]*{,/**} wklx,\n" | |
207 | " deny /proc/sys/kernel/hostn[^a]*{,/**} wklx,\n" | |
208 | " deny /proc/sys/kernel/hostna[^m]*{,/**} wklx,\n" | |
209 | " deny /proc/sys/kernel/hostnam[^e]*{,/**} wklx,\n" | |
210 | " deny /proc/sys/kernel/hostname?*{,/**} wklx,\n" | |
211 | " deny /proc/sys/kernel/m[^s]*{,/**} wklx,\n" | |
212 | " deny /proc/sys/kernel/ms[^g]*{,/**} wklx,\n" | |
213 | " deny /proc/sys/kernel/msg*/** wklx,\n" | |
214 | " deny /proc/sys/kernel/s[^he]*{,/**} wklx,\n" | |
215 | " deny /proc/sys/kernel/se[^m]*{,/**} wklx,\n" | |
216 | " deny /proc/sys/kernel/sem*/** wklx,\n" | |
217 | " deny /proc/sys/kernel/sh[^m]*{,/**} wklx,\n" | |
218 | " deny /proc/sys/kernel/shm*/** wklx,\n" | |
219 | " deny /proc/sys/kernel?*{,/**} wklx,\n" | |
220 | " deny /proc/sys/n[^e]*{,/**} wklx,\n" | |
221 | " deny /proc/sys/ne[^t]*{,/**} wklx,\n" | |
222 | " deny /proc/sys/net?*{,/**} wklx,\n" | |
223 | " deny /sys/[^fdck]*{,/**} wklx,\n" | |
224 | " deny /sys/c[^l]*{,/**} wklx,\n" | |
225 | " deny /sys/cl[^a]*{,/**} wklx,\n" | |
226 | " deny /sys/cla[^s]*{,/**} wklx,\n" | |
227 | " deny /sys/clas[^s]*{,/**} wklx,\n" | |
228 | " deny /sys/class/[^n]*{,/**} wklx,\n" | |
229 | " deny /sys/class/n[^e]*{,/**} wklx,\n" | |
230 | " deny /sys/class/ne[^t]*{,/**} wklx,\n" | |
231 | " deny /sys/class/net?*{,/**} wklx,\n" | |
232 | " deny /sys/class?*{,/**} wklx,\n" | |
233 | " deny /sys/d[^e]*{,/**} wklx,\n" | |
234 | " deny /sys/de[^v]*{,/**} wklx,\n" | |
235 | " deny /sys/dev[^i]*{,/**} wklx,\n" | |
236 | " deny /sys/devi[^c]*{,/**} wklx,\n" | |
237 | " deny /sys/devic[^e]*{,/**} wklx,\n" | |
238 | " deny /sys/device[^s]*{,/**} wklx,\n" | |
239 | " deny /sys/devices/[^v]*{,/**} wklx,\n" | |
240 | " deny /sys/devices/v[^i]*{,/**} wklx,\n" | |
241 | " deny /sys/devices/vi[^r]*{,/**} wklx,\n" | |
242 | " deny /sys/devices/vir[^t]*{,/**} wklx,\n" | |
243 | " deny /sys/devices/virt[^u]*{,/**} wklx,\n" | |
244 | " deny /sys/devices/virtu[^a]*{,/**} wklx,\n" | |
245 | " deny /sys/devices/virtua[^l]*{,/**} wklx,\n" | |
246 | " deny /sys/devices/virtual/[^n]*{,/**} wklx,\n" | |
247 | " deny /sys/devices/virtual/n[^e]*{,/**} wklx,\n" | |
248 | " deny /sys/devices/virtual/ne[^t]*{,/**} wklx,\n" | |
249 | " deny /sys/devices/virtual/net?*{,/**} wklx,\n" | |
250 | " deny /sys/devices/virtual?*{,/**} wklx,\n" | |
251 | " deny /sys/devices?*{,/**} wklx,\n" | |
252 | " deny /sys/f[^s]*{,/**} wklx,\n" | |
253 | " deny /sys/fs/[^c]*{,/**} wklx,\n" | |
254 | " deny /sys/fs/c[^g]*{,/**} wklx,\n" | |
255 | " deny /sys/fs/cg[^r]*{,/**} wklx,\n" | |
256 | " deny /sys/fs/cgr[^o]*{,/**} wklx,\n" | |
257 | " deny /sys/fs/cgro[^u]*{,/**} wklx,\n" | |
258 | " deny /sys/fs/cgrou[^p]*{,/**} wklx,\n" | |
259 | " deny /sys/fs/cgroup?*{,/**} wklx,\n" | |
260 | " deny /sys/fs?*{,/**} wklx,\n" | |
261 | ; | |
262 | ||
263 | static const char AA_PROFILE_UNIX_SOCKETS[] = | |
264 | "\n" | |
265 | " ### Feature: unix\n" | |
266 | " # Allow receive via unix sockets from anywhere\n" | |
267 | " unix (receive),\n" | |
268 | "\n" | |
269 | " # Allow all unix sockets in the container\n" | |
270 | " unix peer=(label=@{profile_name}),\n" | |
271 | ; | |
272 | ||
273 | static const char AA_PROFILE_CGROUP_NAMESPACES[] = | |
274 | "\n" | |
275 | " ### Feature: cgroup namespace\n" | |
276 | " mount fstype=cgroup -> /sys/fs/cgroup/**,\n" | |
277 | " mount fstype=cgroup2 -> /sys/fs/cgroup/**,\n" | |
278 | ; | |
279 | ||
280 | /* '_BASE' because we still need to append generated change_profile rules */ | |
281 | static const char AA_PROFILE_STACKING_BASE[] = | |
282 | "\n" | |
283 | " ### Feature: apparmor stacking\n" | |
284 | " ### Configuration: apparmor profile loading (in namespace)\n" | |
285 | " deny /sys/k[^e]*{,/**} wklx,\n" | |
286 | " deny /sys/ke[^r]*{,/**} wklx,\n" | |
287 | " deny /sys/ker[^n]*{,/**} wklx,\n" | |
288 | " deny /sys/kern[^e]*{,/**} wklx,\n" | |
289 | " deny /sys/kerne[^l]*{,/**} wklx,\n" | |
290 | " deny /sys/kernel/[^s]*{,/**} wklx,\n" | |
291 | " deny /sys/kernel/s[^e]*{,/**} wklx,\n" | |
292 | " deny /sys/kernel/se[^c]*{,/**} wklx,\n" | |
293 | " deny /sys/kernel/sec[^u]*{,/**} wklx,\n" | |
294 | " deny /sys/kernel/secu[^r]*{,/**} wklx,\n" | |
295 | " deny /sys/kernel/secur[^i]*{,/**} wklx,\n" | |
296 | " deny /sys/kernel/securi[^t]*{,/**} wklx,\n" | |
297 | " deny /sys/kernel/securit[^y]*{,/**} wklx,\n" | |
298 | " deny /sys/kernel/security/[^a]*{,/**} wklx,\n" | |
299 | " deny /sys/kernel/security/a[^p]*{,/**} wklx,\n" | |
300 | " deny /sys/kernel/security/ap[^p]*{,/**} wklx,\n" | |
301 | " deny /sys/kernel/security/app[^a]*{,/**} wklx,\n" | |
302 | " deny /sys/kernel/security/appa[^r]*{,/**} wklx,\n" | |
303 | " deny /sys/kernel/security/appar[^m]*{,/**} wklx,\n" | |
304 | " deny /sys/kernel/security/apparm[^o]*{,/**} wklx,\n" | |
305 | " deny /sys/kernel/security/apparmo[^r]*{,/**} wklx,\n" | |
306 | " deny /sys/kernel/security/apparmor?*{,/**} wklx,\n" | |
307 | " deny /sys/kernel/security?*{,/**} wklx,\n" | |
308 | " deny /sys/kernel?*{,/**} wklx,\n" | |
309 | ; | |
310 | ||
311 | static const char AA_PROFILE_NO_STACKING[] = | |
312 | "\n" | |
313 | " ### Feature: apparmor stacking (not present)\n" | |
314 | " deny /sys/k*{,/**} rwklx,\n" | |
315 | ; | |
316 | ||
317 | /* '_BASE' because we need to append change_profile for stacking */ | |
318 | static const char AA_PROFILE_NESTING_BASE[] = | |
319 | "\n" | |
320 | " ### Configuration: nesting\n" | |
321 | " pivot_root,\n" | |
322 | " ptrace,\n" | |
323 | " signal,\n" | |
324 | "\n" | |
325 | /* NOTE: See conf.c's "nesting_helpers" for details. */ | |
326 | " deny /dev/.lxc/proc/** rw,\n" | |
327 | " deny /dev/.lxc/sys/** rw,\n" | |
328 | "\n" | |
329 | " mount fstype=proc -> /usr/lib/*/lxc/**,\n" | |
330 | " mount fstype=sysfs -> /usr/lib/*/lxc/**,\n" | |
1800f924 | 331 | "\n" |
8fddf007 WB |
332 | " # Allow nested LXD\n" |
333 | " mount none -> /var/lib/lxd/shmounts/,\n" | |
334 | " mount /var/lib/lxd/shmounts/ -> /var/lib/lxd/shmounts/,\n" | |
335 | " mount options=bind /var/lib/lxd/shmounts/** -> /var/lib/lxd/**,\n" | |
336 | "\n" | |
50e3e83d | 337 | " # TODO: There doesn't seem to be a way to ask for:\n" |
1800f924 WB |
338 | " # mount options=(ro,nosuid,nodev,noexec,remount,bind),\n" |
339 | " # as we always get mount to $cdir/proc/sys with those flags denied\n" | |
340 | " # So allow all mounts until that is straightened out:\n" | |
341 | " mount,\n" | |
342 | ; | |
343 | ||
344 | static const char AA_PROFILE_UNPRIVILEGED[] = | |
345 | "\n" | |
346 | " ### Configuration: unprivileged container\n" | |
347 | " pivot_root,\n" | |
348 | "\n" | |
349 | " # Allow modifying mount propagation\n" | |
350 | " mount options=(rw,make-slave) -> **,\n" | |
351 | " mount options=(rw,make-rslave) -> **,\n" | |
352 | " mount options=(rw,make-shared) -> **,\n" | |
353 | " mount options=(rw,make-rshared) -> **,\n" | |
354 | " mount options=(rw,make-private) -> **,\n" | |
355 | " mount options=(rw,make-rprivate) -> **,\n" | |
356 | " mount options=(rw,make-unbindable) -> **,\n" | |
357 | " mount options=(rw,make-runbindable) -> **,\n" | |
358 | "\n" | |
359 | " # Allow all bind-mounts\n" | |
360 | " mount options=(rw,bind),\n" | |
361 | " mount options=(rw,rbind),\n" | |
362 | "\n" | |
363 | " # Allow remounting things read-only\n" | |
364 | " mount options=(ro,remount),\n" | |
365 | ; | |
e075f5d9 | 366 | |
af04d847 | 367 | static void load_mount_features_enabled(struct lsm_ops *ops) |
7aff4f43 SH |
368 | { |
369 | struct stat statbuf; | |
370 | int ret; | |
85108024 | 371 | |
7aff4f43 | 372 | ret = stat(AA_MOUNT_RESTR, &statbuf); |
7196c7b3 | 373 | if (ret == 0) |
af04d847 | 374 | ops->aa_mount_features_enabled = 1; |
7aff4f43 SH |
375 | } |
376 | ||
fe4de9a6 | 377 | /* aa_getcon is not working right now. Use our hand-rolled version below */ |
af04d847 | 378 | static int apparmor_enabled(struct lsm_ops *ops) |
fe4de9a6 | 379 | { |
3bb6ff01 | 380 | __do_fclose FILE *fin = NULL; |
fe4de9a6 DE |
381 | char e; |
382 | int ret; | |
383 | ||
7e556d18 | 384 | fin = fopen_cloexec(AA_ENABLED_FILE, "r"); |
fe4de9a6 DE |
385 | if (!fin) |
386 | return 0; | |
af04d847 | 387 | |
fe4de9a6 | 388 | ret = fscanf(fin, "%c", &e); |
7196c7b3 | 389 | if (ret == 1 && e == 'Y') { |
af04d847 | 390 | load_mount_features_enabled(ops); |
fe4de9a6 | 391 | return 1; |
7196c7b3 SH |
392 | } |
393 | ||
fe4de9a6 DE |
394 | return 0; |
395 | } | |
9958532b | 396 | |
301a5f8e AS |
397 | static int __apparmor_process_label_open(struct lsm_ops *ops, pid_t pid, int o_flags, bool on_exec) |
398 | { | |
399 | int ret = -1; | |
400 | int labelfd; | |
401 | char path[LXC_LSMATTRLEN]; | |
402 | ||
403 | if (on_exec) | |
404 | TRACE("On-exec not supported with AppArmor"); | |
405 | ||
47f4914d AS |
406 | /* first try the apparmor subdir */ |
407 | ret = snprintf(path, LXC_LSMATTRLEN, "/proc/%d/attr/apparmor/current", pid); | |
961878da | 408 | if (ret < 0 || (size_t)ret >= LXC_LSMATTRLEN) |
47f4914d AS |
409 | return -1; |
410 | ||
411 | labelfd = open(path, o_flags); | |
412 | if (labelfd >= 0) | |
413 | return labelfd; | |
414 | else if (errno != ENOENT) | |
415 | goto error; | |
416 | ||
417 | /* fallback to legacy global attr directory */ | |
301a5f8e | 418 | ret = snprintf(path, LXC_LSMATTRLEN, "/proc/%d/attr/current", pid); |
961878da | 419 | if (ret < 0 || (size_t)ret >= LXC_LSMATTRLEN) |
301a5f8e AS |
420 | return -1; |
421 | ||
422 | labelfd = open(path, o_flags); | |
47f4914d AS |
423 | if (labelfd >= 0) |
424 | return labelfd; | |
301a5f8e | 425 | |
47f4914d AS |
426 | error: |
427 | return log_error_errno(-errno, errno, "Unable to open AppArmor LSM label file descriptor"); | |
301a5f8e AS |
428 | } |
429 | ||
af04d847 | 430 | static char *apparmor_process_label_get(struct lsm_ops *ops, pid_t pid) |
e075f5d9 | 431 | { |
ba9055c9 | 432 | __do_close int fd_label = -EBADF; |
699e7f88 | 433 | __do_free char *buf = NULL; |
301a5f8e | 434 | __do_free char *label = NULL; |
ba9055c9 | 435 | int ret; |
301a5f8e | 436 | size_t len; |
9958532b | 437 | |
ba9055c9 CB |
438 | fd_label = __apparmor_process_label_open(ops, pid, O_RDONLY, false); |
439 | if (fd_label < 0) | |
440 | return NULL; | |
441 | ||
699e7f88 | 442 | ret = fd_to_buf(fd_label, &buf, &len); |
ba9055c9 | 443 | if (ret < 0) |
9958532b | 444 | return NULL; |
301a5f8e | 445 | |
ba9055c9 CB |
446 | if (len == 0) |
447 | return NULL; | |
301a5f8e | 448 | |
699e7f88 EV |
449 | label = malloc(len + 1); |
450 | if (!label) | |
451 | return NULL; | |
452 | memcpy(label, buf, len); | |
453 | label[len] = '\0'; | |
454 | ||
301a5f8e AS |
455 | len = strcspn(label, "\n \t"); |
456 | if (len) | |
457 | label[len] = '\0'; | |
458 | ||
459 | return move_ptr(label); | |
9958532b SH |
460 | } |
461 | ||
afc691a0 CB |
462 | static char *apparmor_process_label_get_at(struct lsm_ops *ops, int fd_pid) |
463 | { | |
464 | __do_free char *label = NULL; | |
465 | size_t len; | |
466 | ||
47f4914d AS |
467 | /* first try the apparmor subdir, then fall back to legacy interface */ |
468 | label = read_file_at(fd_pid, "attr/apparmor/current", PROTECT_OPEN, PROTECT_LOOKUP_BENEATH); | |
469 | if (!label && errno == ENOENT) | |
470 | label = read_file_at(fd_pid, "attr/current", PROTECT_OPEN, PROTECT_LOOKUP_BENEATH); | |
afc691a0 CB |
471 | if (!label) |
472 | return log_error_errno(NULL, errno, "Failed to get AppArmor context"); | |
473 | ||
474 | len = strcspn(label, "\n \t"); | |
475 | if (len) | |
476 | label[len] = '\0'; | |
afc691a0 CB |
477 | return move_ptr(label); |
478 | } | |
479 | ||
9bfdc0ad SH |
480 | /* |
481 | * Probably makes sense to reorganize these to only read | |
482 | * the label once | |
483 | */ | |
af04d847 | 484 | static bool apparmor_am_unconfined(struct lsm_ops *ops) |
9958532b | 485 | { |
af04d847 | 486 | char *p = apparmor_process_label_get(ops, lxc_raw_getpid()); |
9bfdc0ad | 487 | bool ret = false; |
644bbdbc | 488 | if (!p || strequal(p, "unconfined")) |
9bfdc0ad SH |
489 | ret = true; |
490 | free(p); | |
491 | return ret; | |
492 | } | |
493 | ||
374625aa SH |
494 | static bool aa_needs_transition(char *curlabel) |
495 | { | |
496 | if (!curlabel) | |
497 | return false; | |
644bbdbc | 498 | if (strequal(curlabel, "unconfined")) |
374625aa | 499 | return false; |
644bbdbc | 500 | if (strequal(curlabel, "/usr/bin/lxc-start")) |
374625aa SH |
501 | return false; |
502 | return true; | |
e075f5d9 SH |
503 | } |
504 | ||
1800f924 WB |
505 | static inline void uint64hex(char *buf, uint64_t num) |
506 | { | |
507 | size_t i; | |
508 | ||
509 | buf[16] = 0; | |
510 | for (i = 16; i--;) { | |
511 | char c = (char)(num & 0xf); | |
512 | buf[i] = c + (c < 0xa ? '0' : 'a' - 0xa); | |
513 | num >>= 4; | |
514 | } | |
515 | } | |
516 | ||
517 | static inline char *shorten_apparmor_name(char *name) | |
518 | { | |
519 | size_t len = strlen(name); | |
520 | if (len + 7 > 253) { | |
521 | uint64_t hash; | |
522 | hash = fnv_64a_buf(name, len, FNV1A_64_INIT); | |
523 | name = must_realloc(name, 16 + 1); | |
524 | uint64hex(name, hash); | |
525 | } | |
526 | ||
527 | return name; | |
528 | } | |
529 | ||
530 | /* Replace slashes with hyphens */ | |
531 | static inline void sanitize_path(char *path) | |
532 | { | |
533 | size_t i; | |
534 | ||
535 | for (i = 0; path[i]; i++) | |
536 | if (path[i] == '/') | |
537 | path[i] = '-'; | |
538 | } | |
539 | ||
540 | static inline char *apparmor_dir(const char *ctname, const char *lxcpath) | |
541 | { | |
542 | return must_make_path(lxcpath, ctname, "apparmor", NULL); | |
543 | } | |
544 | ||
545 | ||
546 | static inline char *apparmor_profile_full(const char *ctname, const char *lxcpath) | |
547 | { | |
fe70edee | 548 | return shorten_apparmor_name(must_concat(NULL, "lxc-", ctname, "_<", lxcpath, ">", NULL)); |
1800f924 WB |
549 | } |
550 | ||
551 | /* Like apparmor_profile_full() but with slashes replaced by hyphens */ | |
552 | static inline char *apparmor_namespace(const char *ctname, const char *lxcpath) | |
553 | { | |
554 | char *full; | |
555 | ||
556 | full = apparmor_profile_full(ctname, lxcpath); | |
557 | sanitize_path(full); | |
558 | ||
559 | return full; | |
560 | } | |
561 | ||
af04d847 | 562 | static bool check_apparmor_parser_version(struct lsm_ops *ops) |
1800f924 | 563 | { |
3bb6ff01 | 564 | int major = 0, minor = 0, micro = 0, ret = 0; |
1800f924 WB |
565 | struct lxc_popen_FILE *parserpipe; |
566 | int rc; | |
3bb6ff01 | 567 | |
af04d847 CB |
568 | switch (ops->aa_parser_available) { |
569 | case 0: | |
3bb6ff01 | 570 | return false; |
af04d847 CB |
571 | case 1: |
572 | return true; | |
573 | } | |
1800f924 WB |
574 | |
575 | parserpipe = lxc_popen("apparmor_parser --version"); | |
576 | if (!parserpipe) { | |
577 | fprintf(stderr, "Failed to run check for apparmor_parser\n"); | |
3bb6ff01 | 578 | goto out; |
1800f924 WB |
579 | } |
580 | ||
581 | rc = fscanf(parserpipe->f, "AppArmor parser version %d.%d.%d", &major, &minor, µ); | |
582 | if (rc < 1) { | |
583 | lxc_pclose(parserpipe); | |
584 | /* We stay silent for now as this most likely means the shell | |
585 | * lxc_popen executed failed to find the apparmor_parser binary. | |
50e3e83d | 586 | * See the TODO comment above for details. |
1800f924 | 587 | */ |
3bb6ff01 | 588 | goto out; |
1800f924 WB |
589 | } |
590 | ||
591 | rc = lxc_pclose(parserpipe); | |
592 | if (rc < 0) { | |
593 | fprintf(stderr, "Error waiting for child process\n"); | |
3bb6ff01 | 594 | goto out; |
1800f924 WB |
595 | } |
596 | if (rc != 0) { | |
597 | fprintf(stderr, "'apparmor_parser --version' executed with an error status\n"); | |
3bb6ff01 | 598 | goto out; |
1800f924 WB |
599 | } |
600 | ||
3bb6ff01 | 601 | if ((major > 2) || (major == 2 && minor > 10) || (major == 2 && minor == 10 && micro >= 95)) |
af04d847 | 602 | ops->aa_supports_unix = 1; |
1800f924 | 603 | |
3bb6ff01 CB |
604 | ret = 1; |
605 | ||
606 | out: | |
af04d847 | 607 | ops->aa_parser_available = ret; |
3bb6ff01 | 608 | return ret == 1; |
1800f924 WB |
609 | } |
610 | ||
611 | static bool file_is_yes(const char *path) | |
612 | { | |
a36e286b | 613 | __do_close int fd = -EBADF; |
1800f924 | 614 | ssize_t rd; |
1800f924 WB |
615 | char buf[8]; /* we actually just expect "yes" or "no" */ |
616 | ||
617 | fd = open(path, O_RDONLY | O_CLOEXEC); | |
618 | if (fd < 0) | |
619 | return false; | |
620 | ||
1fabf7d4 | 621 | rd = lxc_read_nointr(fd, buf, sizeof(buf)); |
1800f924 | 622 | |
12b90260 | 623 | return rd >= 4 && strnequal(buf, "yes\n", 4); |
1800f924 WB |
624 | } |
625 | ||
39b72573 | 626 | static bool apparmor_can_stack(void) |
1800f924 WB |
627 | { |
628 | int major, minor, scanned; | |
629 | FILE *f; | |
630 | ||
631 | if (!file_is_yes("/sys/kernel/security/apparmor/features/domain/stack")) | |
632 | return false; | |
633 | ||
634 | f = fopen_cloexec("/sys/kernel/security/apparmor/features/domain/version", "r"); | |
635 | if (!f) | |
636 | return false; | |
637 | ||
638 | scanned = fscanf(f, "%d.%d", &major, &minor); | |
639 | fclose(f); | |
640 | if (scanned != 2) | |
641 | return false; | |
642 | ||
643 | return major > 1 || (major == 1 && minor >= 2); | |
644 | } | |
645 | ||
646 | static void must_append_sized_full(char **buf, size_t *bufsz, const char *data, | |
647 | size_t size, bool append_newline) | |
648 | { | |
649 | size_t newsize = *bufsz + size; | |
650 | ||
651 | if (append_newline) | |
652 | ++newsize; | |
653 | ||
654 | *buf = must_realloc(*buf, newsize); | |
655 | memcpy(*buf + *bufsz, data, size); | |
656 | ||
657 | if (append_newline) | |
658 | (*buf)[newsize - 1] = '\n'; | |
659 | ||
660 | *bufsz = newsize; | |
661 | } | |
662 | ||
663 | static void must_append_sized(char **buf, size_t *bufsz, const char *data, size_t size) | |
664 | { | |
665 | return must_append_sized_full(buf, bufsz, data, size, false); | |
666 | } | |
667 | ||
668 | static bool is_privileged(struct lxc_conf *conf) | |
669 | { | |
0589d744 | 670 | return list_empty(&conf->id_map); |
1800f924 WB |
671 | } |
672 | ||
8fddf007 WB |
673 | static const char* AA_ALL_DEST_PATH_LIST[] = { |
674 | " -> /[^spd]*{,/**},\n", | |
675 | " -> /d[^e]*{,/**},\n", | |
676 | " -> /de[^v]*{,/**},\n", | |
677 | " -> /dev/.[^l]*{,/**},\n", | |
678 | " -> /dev/.l[^x]*{,/**},\n", | |
679 | " -> /dev/.lx[^c]*{,/**},\n", | |
680 | " -> /dev/.lxc?*{,/**},\n", | |
681 | " -> /dev/[^.]*{,/**},\n", | |
682 | " -> /dev?*{,/**},\n", | |
683 | " -> /p[^r]*{,/**},\n", | |
684 | " -> /pr[^o]*{,/**},\n", | |
685 | " -> /pro[^c]*{,/**},\n", | |
686 | " -> /proc?*{,/**},\n", | |
687 | " -> /s[^y]*{,/**},\n", | |
688 | " -> /sy[^s]*{,/**},\n", | |
689 | " -> /sys?*{,/**},\n", | |
690 | NULL, | |
691 | }; | |
692 | ||
693 | static const struct mntopt_t { | |
694 | const char *opt; | |
695 | size_t len; | |
696 | } REMOUNT_OPTIONS[] = { | |
697 | { ",nodev", sizeof(",nodev")-1 }, | |
698 | { ",nosuid", sizeof(",nosuid")-1 }, | |
699 | { ",noexec", sizeof(",noexec")-1 }, | |
700 | }; | |
701 | ||
702 | static void append_remount_rule(char **profile, size_t *size, const char *rule) | |
703 | { | |
704 | size_t rule_len = strlen(rule); | |
705 | ||
706 | for (const char **dest = AA_ALL_DEST_PATH_LIST; *dest; ++dest) { | |
707 | must_append_sized(profile, size, rule, rule_len); | |
708 | must_append_sized(profile, size, *dest, strlen(*dest)); | |
709 | } | |
710 | } | |
711 | ||
712 | static void append_all_remount_rules(char **profile, size_t *size) | |
713 | { | |
714 | /* | |
715 | * That's 30, and we add at most: | |
716 | * ",nodev,nosuid,noexec,strictatime -> /dev/.lx[^c]*{,/ **},\ n", | |
717 | * which is anouther ~58, this s hould be enough: | |
718 | */ | |
719 | char buf[128] = " mount options=(ro,remount,bind"; | |
720 | const size_t buf_append_pos = strlen(buf); | |
721 | ||
722 | const size_t opt_count = ARRAY_SIZE(REMOUNT_OPTIONS); | |
8fddf007 WB |
723 | |
724 | must_append_sized(profile, size, | |
725 | "# allow various ro-bind-*re*mounts\n", | |
726 | sizeof("# allow various ro-bind-*re*mounts\n")-1); | |
727 | ||
961878da | 728 | for (size_t opt_bits = 0; opt_bits != (size_t)1 << opt_count; ++opt_bits) { |
8fddf007 WB |
729 | size_t at = buf_append_pos; |
730 | unsigned bit = 1; | |
731 | size_t o; | |
732 | ||
733 | for (o = 0; o != opt_count; ++o, bit <<= 1) { | |
734 | if (opt_bits & bit) { | |
735 | const struct mntopt_t *opt = &REMOUNT_OPTIONS[o]; | |
736 | memcpy(&buf[at], opt->opt, opt->len); | |
737 | at += opt->len; | |
738 | } | |
739 | } | |
740 | ||
741 | memcpy(&buf[at], ")", sizeof(")")); | |
742 | append_remount_rule(profile, size, buf); | |
743 | ||
744 | /* noatime and strictatime don't go together */ | |
745 | memcpy(&buf[at], ",noatime)", sizeof(",noatime)")); | |
746 | append_remount_rule(profile, size, buf); | |
747 | memcpy(&buf[at], ",strictatime)", sizeof(",strictatime)")); | |
748 | append_remount_rule(profile, size, buf); | |
749 | } | |
750 | } | |
751 | ||
af04d847 | 752 | static char *get_apparmor_profile_content(struct lsm_ops *ops, struct lxc_conf *conf, const char *lxcpath) |
1800f924 WB |
753 | { |
754 | char *profile, *profile_name_full; | |
755 | size_t size; | |
1fb1e667 | 756 | struct string_entry *rule; |
1800f924 WB |
757 | |
758 | profile_name_full = apparmor_profile_full(conf->name, lxcpath); | |
759 | ||
fe70edee | 760 | profile = must_concat(NULL, |
1800f924 WB |
761 | "#include <tunables/global>\n" |
762 | "profile \"", profile_name_full, "\" flags=(attach_disconnected,mediate_deleted) {\n", | |
763 | NULL); | |
764 | size = strlen(profile); | |
765 | ||
766 | must_append_sized(&profile, &size, AA_PROFILE_BASE, | |
6333c915 | 767 | STRARRAYLEN(AA_PROFILE_BASE)); |
1800f924 | 768 | |
8fddf007 WB |
769 | append_all_remount_rules(&profile, &size); |
770 | ||
af04d847 | 771 | if (ops->aa_supports_unix) |
1800f924 | 772 | must_append_sized(&profile, &size, AA_PROFILE_UNIX_SOCKETS, |
6333c915 | 773 | STRARRAYLEN(AA_PROFILE_UNIX_SOCKETS)); |
1800f924 WB |
774 | |
775 | if (file_exists("/proc/self/ns/cgroup")) | |
776 | must_append_sized(&profile, &size, AA_PROFILE_CGROUP_NAMESPACES, | |
6333c915 | 777 | STRARRAYLEN(AA_PROFILE_CGROUP_NAMESPACES)); |
1800f924 | 778 | |
af04d847 | 779 | if (ops->aa_can_stack && !ops->aa_is_stacked) { |
1800f924 WB |
780 | char *namespace, *temp; |
781 | ||
782 | must_append_sized(&profile, &size, AA_PROFILE_STACKING_BASE, | |
6333c915 | 783 | STRARRAYLEN(AA_PROFILE_STACKING_BASE)); |
1800f924 WB |
784 | |
785 | namespace = apparmor_namespace(conf->name, lxcpath); | |
fe70edee | 786 | temp = must_concat(NULL, " change_profile -> \":", namespace, ":*\",\n" |
1800f924 WB |
787 | " change_profile -> \":", namespace, "://*\",\n", |
788 | NULL); | |
789 | free(namespace); | |
790 | ||
791 | must_append_sized(&profile, &size, temp, strlen(temp)); | |
792 | free(temp); | |
793 | } else { | |
794 | must_append_sized(&profile, &size, AA_PROFILE_NO_STACKING, | |
6333c915 | 795 | STRARRAYLEN(AA_PROFILE_NO_STACKING)); |
1800f924 WB |
796 | } |
797 | ||
798 | if (conf->lsm_aa_allow_nesting) { | |
799 | must_append_sized(&profile, &size, AA_PROFILE_NESTING_BASE, | |
6333c915 | 800 | STRARRAYLEN(AA_PROFILE_NESTING_BASE)); |
1800f924 | 801 | |
af04d847 | 802 | if (!ops->aa_can_stack || ops->aa_is_stacked) { |
1800f924 WB |
803 | char *temp; |
804 | ||
fe70edee | 805 | temp = must_concat(NULL, " change_profile -> \"", |
1800f924 WB |
806 | profile_name_full, "\",\n", NULL); |
807 | must_append_sized(&profile, &size, temp, strlen(temp)); | |
808 | free(temp); | |
809 | } | |
810 | } | |
811 | ||
812 | if (!is_privileged(conf) || am_host_unpriv()) | |
813 | must_append_sized(&profile, &size, AA_PROFILE_UNPRIVILEGED, | |
6333c915 | 814 | STRARRAYLEN(AA_PROFILE_UNPRIVILEGED)); |
1800f924 | 815 | |
1fb1e667 CB |
816 | list_for_each_entry(rule, &conf->lsm_aa_raw, head) { |
817 | const char *line = rule->val; | |
1800f924 WB |
818 | |
819 | must_append_sized_full(&profile, &size, line, strlen(line), true); | |
820 | } | |
821 | ||
822 | /* include terminating \0 byte */ | |
823 | must_append_sized(&profile, &size, "}\n", 3); | |
824 | ||
825 | free(profile_name_full); | |
826 | ||
827 | return profile; | |
828 | } | |
829 | ||
fe4de9a6 | 830 | /* |
1800f924 WB |
831 | * apparmor_parser creates a cache file using the parsed file's name as a name. |
832 | * This means there may be multiple containers with the same name but different | |
833 | * lxcpaths. Therefore we need a sanitized version of the complete profile name | |
834 | * as profile file-name. | |
835 | * We already get this exactly from apparmor_namespace(). | |
fe4de9a6 | 836 | */ |
1800f924 | 837 | static char *make_apparmor_profile_path(const char *ctname, const char *lxcpath) |
e075f5d9 | 838 | { |
1800f924 | 839 | char *ret, *filename; |
7aff4f43 | 840 | |
1800f924 WB |
841 | filename = apparmor_namespace(ctname, lxcpath); |
842 | ret = must_make_path(lxcpath, ctname, "apparmor", filename, NULL); | |
843 | free(filename); | |
844 | ||
845 | return ret; | |
846 | } | |
847 | ||
848 | static char *make_apparmor_namespace_path(const char *ctname, const char *lxcpath) | |
849 | { | |
850 | char *ret, *namespace; | |
851 | ||
852 | namespace = apparmor_namespace(ctname, lxcpath); | |
853 | ret = must_make_path("/sys/kernel/security/apparmor/policy/namespaces", namespace, NULL); | |
854 | free(namespace); | |
855 | ||
856 | return ret; | |
857 | } | |
858 | ||
af04d847 | 859 | static bool make_apparmor_namespace(struct lsm_ops *ops, struct lxc_conf *conf, const char *lxcpath) |
1800f924 WB |
860 | { |
861 | char *path; | |
862 | ||
af04d847 | 863 | if (!ops->aa_can_stack || ops->aa_is_stacked) |
1800f924 WB |
864 | return true; |
865 | ||
866 | path = make_apparmor_namespace_path(conf->name, lxcpath); | |
867 | errno = 0; | |
868 | if (mkdir(path, 0755) < 0 && errno != EEXIST) { | |
869 | SYSERROR("Error creating AppArmor namespace: %s", path); | |
870 | free(path); | |
871 | return false; | |
872 | } | |
873 | free(path); | |
874 | ||
875 | return true; | |
876 | } | |
877 | ||
878 | static void remove_apparmor_namespace(struct lxc_conf *conf, const char *lxcpath) | |
879 | { | |
880 | char *path; | |
881 | ||
882 | path = make_apparmor_namespace_path(conf->name, lxcpath); | |
883 | if (rmdir(path) != 0) | |
884 | SYSERROR("Error removing AppArmor namespace"); | |
885 | free(path); | |
886 | } | |
887 | ||
888 | struct apparmor_parser_args { | |
889 | char cmd; | |
890 | char *file; | |
891 | }; | |
892 | ||
893 | static int apparmor_parser_exec(void *data) | |
894 | { | |
895 | struct apparmor_parser_args *args = data; | |
896 | char cmdbuf[] = { '-', args->cmd, 'W', 'L', 0 }; | |
897 | ||
898 | execlp("apparmor_parser", "apparmor_parser", cmdbuf, APPARMOR_CACHE_DIR, args->file, NULL); | |
899 | ||
900 | return -1; | |
901 | } | |
902 | ||
903 | static int run_apparmor_parser(char command, | |
904 | struct lxc_conf *conf, | |
905 | const char *lxcpath) | |
906 | { | |
85d67fba | 907 | char output[PATH_MAX]; |
1800f924 WB |
908 | int ret; |
909 | struct apparmor_parser_args args = { | |
910 | .cmd = command, | |
911 | .file = make_apparmor_profile_path(conf->name, lxcpath), | |
912 | }; | |
913 | ||
914 | ret = run_command(output, sizeof(output), apparmor_parser_exec, (void*)&args); | |
915 | if (ret < 0) { | |
916 | ERROR("Failed to run apparmor_parser on \"%s\": %s", args.file, output); | |
917 | ret = -1; | |
918 | } | |
919 | ||
920 | ||
921 | free(args.file); | |
922 | return ret; | |
923 | } | |
924 | ||
925 | static void remove_apparmor_profile(struct lxc_conf *conf, const char *lxcpath) | |
926 | { | |
927 | char *path; | |
928 | ||
929 | /* It's ok if these deletes fail: if the container was never started, | |
930 | * we'll have never written a profile or cached it. | |
931 | */ | |
932 | ||
933 | path = make_apparmor_profile_path(conf->name, lxcpath); | |
934 | (void)unlink(path); | |
935 | free(path); | |
936 | ||
937 | /* Also remove the apparmor/ subdirectory */ | |
938 | path = apparmor_dir(conf->name, lxcpath); | |
939 | (void)rmdir(path); | |
940 | free(path); | |
941 | } | |
942 | ||
af04d847 | 943 | static int load_apparmor_profile(struct lsm_ops *ops, struct lxc_conf *conf, const char *lxcpath) |
1800f924 WB |
944 | { |
945 | struct stat profile_sb; | |
946 | size_t content_len; | |
947 | int ret = -1; | |
948 | size_t old_len = 0; | |
949 | char *profile_path = NULL, *old_content = NULL, *new_content = NULL; | |
950 | int profile_fd = -1; | |
951 | ||
af04d847 | 952 | if (!make_apparmor_namespace(ops, conf, lxcpath)) |
1800f924 WB |
953 | return -1; |
954 | ||
955 | /* In order to avoid forcing a profile parse (potentially slow) on | |
956 | * every container start, let's use apparmor's binary policy cache, | |
957 | * which checks mtime of the files to figure out if the policy needs to | |
958 | * be regenerated. | |
959 | * | |
960 | * Since it uses mtimes, we shouldn't just always write out our local | |
961 | * apparmor template; instead we should check to see whether the | |
962 | * template is the same as ours. If it isn't we should write our | |
963 | * version out so that the new changes are reflected and we definitely | |
964 | * force a recompile. | |
965 | */ | |
966 | ||
967 | profile_path = make_apparmor_profile_path(conf->name, lxcpath); | |
968 | profile_fd = open(profile_path, O_RDONLY | O_CLOEXEC); | |
969 | if (profile_fd >= 0) { | |
970 | if (fstat(profile_fd, &profile_sb) < 0) { | |
971 | SYSERROR("Error accessing old profile from %s", | |
972 | profile_path); | |
973 | goto out; | |
974 | } | |
975 | old_len = profile_sb.st_size; | |
db3cbfa7 WB |
976 | if (old_len) { |
977 | old_content = lxc_strmmap(NULL, old_len, PROT_READ, | |
978 | MAP_PRIVATE, profile_fd, 0); | |
979 | if (old_content == MAP_FAILED) { | |
980 | SYSERROR("Failed to mmap old profile from %s", | |
981 | profile_path); | |
982 | goto out; | |
983 | } | |
1800f924 WB |
984 | } |
985 | } else if (errno != ENOENT) { | |
986 | SYSERROR("Error reading old profile from %s", profile_path); | |
987 | goto out; | |
988 | } | |
989 | ||
af04d847 | 990 | new_content = get_apparmor_profile_content(ops, conf, lxcpath); |
1800f924 WB |
991 | if (!new_content) |
992 | goto out; | |
993 | ||
994 | content_len = strlen(new_content); | |
995 | ||
996 | if (!old_content || old_len != content_len || memcmp(old_content, new_content, content_len) != 0) { | |
997 | char *path; | |
998 | ||
539c3977 | 999 | ret = lxc_mkdir_p(APPARMOR_CACHE_DIR, 0755); |
1800f924 WB |
1000 | if (ret < 0) { |
1001 | SYSERROR("Error creating AppArmor profile cache directory " APPARMOR_CACHE_DIR); | |
1002 | goto out; | |
1003 | } | |
1004 | ||
1005 | path = apparmor_dir(conf->name, lxcpath); | |
539c3977 | 1006 | ret = lxc_mkdir_p(path, 0755); |
1800f924 WB |
1007 | if (ret < 0) { |
1008 | SYSERROR("Error creating AppArmor profile directory: %s", path); | |
1009 | free(path); | |
1010 | goto out; | |
1011 | } | |
1012 | free(path); | |
1013 | ||
1014 | ret = lxc_write_to_file(profile_path, new_content, content_len, false, 0600); | |
1015 | if (ret < 0) { | |
1016 | SYSERROR("Error writing profile to %s", profile_path); | |
1017 | goto out; | |
1018 | } | |
1019 | } | |
1020 | ||
1021 | ret = run_apparmor_parser(AA_CMD_LOAD, conf, lxcpath); | |
1022 | if (ret != 0) | |
1023 | goto out_remove_profile; | |
1024 | ||
1025 | conf->lsm_aa_profile_created = true; | |
1026 | ||
1027 | goto out_ok; | |
1028 | ||
1029 | out_remove_profile: | |
1030 | remove_apparmor_profile(conf, lxcpath); | |
1031 | out: | |
1032 | remove_apparmor_namespace(conf, lxcpath); | |
1033 | out_ok: | |
1034 | if (profile_fd >= 0) { | |
1035 | if (old_content) | |
1036 | lxc_strmunmap(old_content, old_len); | |
1037 | close(profile_fd); | |
1038 | } | |
1039 | free(profile_path); | |
1040 | free(new_content); | |
1041 | return ret; | |
1042 | } | |
1043 | ||
1044 | /* | |
1045 | * Ensure that the container's policy namespace is unloaded to free kernel | |
1046 | * memory. This does not delete the policy from disk or cache. | |
1047 | */ | |
af04d847 | 1048 | static void apparmor_cleanup(struct lsm_ops *ops, struct lxc_conf *conf, const char *lxcpath) |
1800f924 | 1049 | { |
af04d847 | 1050 | if (!ops->aa_admin) |
1800f924 WB |
1051 | return; |
1052 | ||
1053 | if (!conf->lsm_aa_profile_created) | |
1054 | return; | |
1055 | ||
1056 | remove_apparmor_namespace(conf, lxcpath); | |
1057 | (void)run_apparmor_parser(AA_CMD_UNLOAD, conf, lxcpath); | |
1058 | ||
1059 | remove_apparmor_profile(conf, lxcpath); | |
1060 | } | |
1061 | ||
af04d847 | 1062 | static int apparmor_prepare(struct lsm_ops *ops, struct lxc_conf *conf, const char *lxcpath) |
1800f924 WB |
1063 | { |
1064 | int ret = -1; | |
1065 | const char *label; | |
1066 | char *curlabel = NULL, *genlabel = NULL; | |
1067 | ||
af04d847 CB |
1068 | if (!ops->aa_enabled) |
1069 | return log_error(-1, "AppArmor not enabled"); | |
1800f924 WB |
1070 | |
1071 | label = conf->lsm_aa_profile; | |
e075f5d9 | 1072 | |
480c876b | 1073 | /* user may request that we just ignore apparmor */ |
644bbdbc | 1074 | if (label && strequal(label, AA_UNCHANGED)) { |
1800f924 WB |
1075 | INFO("AppArmor profile unchanged per user request"); |
1076 | conf->lsm_aa_profile_computed = must_copy_string(label); | |
480c876b SH |
1077 | return 0; |
1078 | } | |
1079 | ||
644bbdbc | 1080 | if (label && strequal(label, AA_GENERATED)) { |
af04d847 | 1081 | if (!check_apparmor_parser_version(ops)) { |
1800f924 WB |
1082 | ERROR("Cannot use generated profile: apparmor_parser not available"); |
1083 | goto out; | |
1084 | } | |
1085 | ||
1086 | /* auto-generate profile based on available/requested security features */ | |
af04d847 | 1087 | if (load_apparmor_profile(ops, conf, lxcpath) != 0) { |
1800f924 WB |
1088 | ERROR("Failed to load generated AppArmor profile"); |
1089 | goto out; | |
1090 | } | |
1091 | ||
1092 | genlabel = apparmor_profile_full(conf->name, lxcpath); | |
1093 | if (!genlabel) { | |
1094 | ERROR("Failed to build AppArmor profile name"); | |
1095 | goto out; | |
1096 | } | |
1097 | ||
af04d847 | 1098 | if (ops->aa_can_stack && !ops->aa_is_stacked) { |
1800f924 WB |
1099 | char *namespace = apparmor_namespace(conf->name, lxcpath); |
1100 | size_t llen = strlen(genlabel); | |
6333c915 | 1101 | must_append_sized(&genlabel, &llen, "//&:", STRARRAYLEN("//&:")); |
1800f924 | 1102 | must_append_sized(&genlabel, &llen, namespace, strlen(namespace)); |
6333c915 | 1103 | must_append_sized(&genlabel, &llen, ":", STRARRAYLEN(":") + 1); /* with the nul byte */ |
1800f924 WB |
1104 | free(namespace); |
1105 | } | |
1106 | ||
1107 | label = genlabel; | |
1108 | } | |
1109 | ||
af04d847 | 1110 | curlabel = apparmor_process_label_get(ops, lxc_raw_getpid()); |
374625aa | 1111 | |
af04d847 | 1112 | if (!ops->aa_can_stack && aa_needs_transition(curlabel)) { |
1a0e70ac | 1113 | /* we're already confined, and stacking isn't supported */ |
374625aa | 1114 | |
644bbdbc | 1115 | if (!label || strequal(curlabel, label)) { |
1a0e70ac | 1116 | /* no change requested */ |
1800f924 WB |
1117 | ret = 0; |
1118 | goto out; | |
9bfdc0ad | 1119 | } |
374625aa | 1120 | |
1800f924 WB |
1121 | ERROR("Already AppArmor confined, but new label requested."); |
1122 | goto out; | |
9bfdc0ad SH |
1123 | } |
1124 | ||
fe4de9a6 | 1125 | if (!label) { |
1800f924 WB |
1126 | if (cgns_supported()) |
1127 | label = AA_DEF_PROFILE_CGNS; | |
fe4de9a6 | 1128 | else |
1800f924 | 1129 | label = AA_DEF_PROFILE; |
7aff4f43 SH |
1130 | } |
1131 | ||
644bbdbc | 1132 | if (!ops->aa_mount_features_enabled && !strequal(label, "unconfined")) { |
7aff4f43 SH |
1133 | WARN("Incomplete AppArmor support in your kernel"); |
1134 | if (!conf->lsm_aa_allow_incomplete) { | |
1135 | ERROR("If you really want to start this container, set"); | |
69e38e00 | 1136 | ERROR("lxc.apparmor.allow_incomplete = 1"); |
7aff4f43 | 1137 | ERROR("in your container configuration file"); |
1800f924 | 1138 | goto out; |
7aff4f43 | 1139 | } |
e075f5d9 | 1140 | } |
e075f5d9 | 1141 | |
1800f924 WB |
1142 | conf->lsm_aa_profile_computed = must_copy_string(label); |
1143 | ret = 0; | |
1144 | ||
1145 | out: | |
1146 | if (genlabel) { | |
1147 | free(genlabel); | |
1148 | if (ret != 0) | |
af04d847 | 1149 | apparmor_cleanup(ops, conf, lxcpath); |
1800f924 WB |
1150 | } |
1151 | free(curlabel); | |
1152 | return ret; | |
1153 | } | |
1154 | ||
af04d847 | 1155 | static int apparmor_keyring_label_set(struct lsm_ops *ops, const char *label) |
d701d729 CB |
1156 | { |
1157 | return 0; | |
1158 | } | |
1159 | ||
af04d847 | 1160 | static int apparmor_process_label_fd_get(struct lsm_ops *ops, pid_t pid, bool on_exec) |
d701d729 | 1161 | { |
301a5f8e | 1162 | return __apparmor_process_label_open(ops, pid, O_RDWR, on_exec); |
d701d729 CB |
1163 | } |
1164 | ||
0e8effda CB |
1165 | static int apparmor_process_label_set_at(struct lsm_ops *ops, int label_fd, |
1166 | const char *label, bool on_exec) | |
d701d729 | 1167 | { |
389eb7c6 | 1168 | __do_free char *command = NULL; |
d701d729 CB |
1169 | int ret = -1; |
1170 | size_t len; | |
d701d729 CB |
1171 | |
1172 | if (on_exec) | |
389eb7c6 | 1173 | TRACE("Changing AppArmor profile on exec not supported"); |
d701d729 CB |
1174 | |
1175 | len = strlen(label) + strlen("changeprofile ") + 1; | |
389eb7c6 | 1176 | command = zalloc(len); |
d701d729 CB |
1177 | if (!command) |
1178 | return ret_errno(ENOMEM); | |
1179 | ||
1180 | ret = snprintf(command, len, "changeprofile %s", label); | |
1181 | if (ret < 0 || (size_t)ret >= len) | |
1182 | return -EFBIG; | |
1183 | ||
1184 | ret = lxc_write_nointr(label_fd, command, len - 1); | |
0e8effda CB |
1185 | if (ret < 0) |
1186 | return syserror("Failed to write AppArmor profile \"%s\" to %d", | |
1187 | label, label_fd); | |
d701d729 CB |
1188 | |
1189 | INFO("Set AppArmor label to \"%s\"", label); | |
0e8effda | 1190 | return 0; |
d701d729 CB |
1191 | } |
1192 | ||
1800f924 WB |
1193 | /* |
1194 | * apparmor_process_label_set: Set AppArmor process profile | |
1195 | * | |
1196 | * @label : the profile to set | |
1197 | * @conf : the container configuration to use if @label is NULL | |
1198 | * @default : use the default profile if @label is NULL | |
1199 | * @on_exec : this is ignored. Apparmor profile will be changed immediately | |
1200 | * | |
1201 | * Returns 0 on success, < 0 on failure | |
1202 | * | |
1203 | * Notes: This relies on /proc being available. | |
1204 | */ | |
af04d847 CB |
1205 | static int apparmor_process_label_set(struct lsm_ops *ops, const char *inlabel, |
1206 | struct lxc_conf *conf, bool on_exec) | |
1800f924 | 1207 | { |
fbf281d3 CB |
1208 | __do_close int label_fd = -EBADF; |
1209 | int ret; | |
1800f924 WB |
1210 | const char *label; |
1211 | ||
af04d847 | 1212 | if (!ops->aa_enabled) |
fbf281d3 | 1213 | return log_error_errno(-EOPNOTSUPP, EOPNOTSUPP, "AppArmor not enabled"); |
1800f924 WB |
1214 | |
1215 | label = inlabel ? inlabel : conf->lsm_aa_profile_computed; | |
fbf281d3 CB |
1216 | if (!label) |
1217 | return log_error_errno(-EINVAL, EINVAL, "LSM wasn't prepared"); | |
1800f924 WB |
1218 | |
1219 | /* user may request that we just ignore apparmor */ | |
644bbdbc | 1220 | if (strequal(label, AA_UNCHANGED)) |
fbf281d3 | 1221 | return log_info(0, "AppArmor profile unchanged per user request"); |
7aff4f43 | 1222 | |
644bbdbc | 1223 | if (strequal(label, "unconfined") && apparmor_am_unconfined(ops)) |
fbf281d3 CB |
1224 | return log_info(0, "AppArmor profile unchanged"); |
1225 | ||
1226 | label_fd = apparmor_process_label_fd_get(ops, lxc_raw_gettid(), on_exec); | |
1227 | if (label_fd < 0) | |
1228 | return log_error_errno(-EINVAL, EINVAL, "Failed to change AppArmor profile to %s", label); | |
e075f5d9 | 1229 | |
af04d847 | 1230 | ret = apparmor_process_label_set_at(ops, label_fd, label, on_exec); |
fbf281d3 CB |
1231 | if (ret < 0) |
1232 | return log_error_errno(-EINVAL, EINVAL, "Failed to change AppArmor profile to %s", label); | |
5288a74f | 1233 | |
fbf281d3 | 1234 | return log_info(0, "Changed AppArmor profile to %s", label); |
e075f5d9 SH |
1235 | } |
1236 | ||
d701d729 | 1237 | static struct lsm_ops apparmor_ops = { |
af04d847 CB |
1238 | .name = "AppArmor", |
1239 | .aa_admin = -1, | |
1240 | .aa_can_stack = -1, | |
1241 | .aa_enabled = -1, | |
1242 | .aa_is_stacked = -1, | |
1243 | .aa_mount_features_enabled = -1, | |
1244 | .aa_parser_available = -1, | |
1245 | .aa_supports_unix = -1, | |
1246 | .cleanup = apparmor_cleanup, | |
1247 | .enabled = apparmor_enabled, | |
1248 | .keyring_label_set = apparmor_keyring_label_set, | |
1249 | .prepare = apparmor_prepare, | |
1250 | .process_label_fd_get = apparmor_process_label_fd_get, | |
1251 | .process_label_get = apparmor_process_label_get, | |
1252 | .process_label_set = apparmor_process_label_set, | |
afc691a0 | 1253 | .process_label_get_at = apparmor_process_label_get_at, |
af04d847 | 1254 | .process_label_set_at = apparmor_process_label_set_at, |
fe4de9a6 | 1255 | }; |
9958532b | 1256 | |
af04d847 | 1257 | struct lsm_ops *lsm_apparmor_ops_init(void) |
e075f5d9 | 1258 | { |
af04d847 CB |
1259 | apparmor_ops.aa_admin = 0; |
1260 | apparmor_ops.aa_can_stack = 0; | |
1261 | apparmor_ops.aa_enabled = 0; | |
1262 | apparmor_ops.aa_is_stacked = 0; | |
1263 | apparmor_ops.aa_mount_features_enabled = 0; | |
1264 | apparmor_ops.aa_parser_available = -1; | |
1265 | apparmor_ops.aa_supports_unix = 0; | |
1266 | ||
1267 | if (!apparmor_enabled(&apparmor_ops)) | |
fe4de9a6 | 1268 | return NULL; |
1800f924 | 1269 | |
af04d847 CB |
1270 | apparmor_ops.aa_can_stack = apparmor_can_stack(); |
1271 | if (apparmor_ops.aa_can_stack) | |
1272 | apparmor_ops.aa_is_stacked = file_is_yes("/sys/kernel/security/apparmor/.ns_stacked"); | |
1800f924 WB |
1273 | |
1274 | #if HAVE_LIBCAP | |
af04d847 | 1275 | apparmor_ops.aa_admin = lxc_proc_cap_is_set(CAP_SETGID, CAP_EFFECTIVE); |
1800f924 | 1276 | #endif |
af04d847 | 1277 | if (!apparmor_ops.aa_admin) |
1800f924 | 1278 | WARN("Per-container AppArmor profiles are disabled because the mac_admin capability is missing"); |
af04d847 | 1279 | else if (am_host_unpriv() && !apparmor_ops.aa_is_stacked) |
1800f924 | 1280 | WARN("Per-container AppArmor profiles are disabled because LXC is running in an unprivileged container without stacking"); |
1800f924 | 1281 | |
af04d847 | 1282 | apparmor_ops.aa_enabled = 1; |
d701d729 | 1283 | return &apparmor_ops; |
e075f5d9 | 1284 | } |