1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * AppArmor security module |
4 | * |
5 | * This file contains AppArmor task related definitions and mediation |
6 | * |
7 | * Copyright 2017 Canonical Ltd. |
8 | * |
9 | * TODO |
10 | * If a task uses change_hat it currently does not return to the old |
11 | * cred or task context but instead creates a new one. Ideally the task |
12 | * should return to the previous cred if it has not been modified. |
13 | */ |
14 | |
15 | #include <linux/gfp.h> |
16 | #include <linux/ptrace.h> |
17 | |
18 | #include "include/audit.h" |
19 | #include "include/cred.h" |
20 | #include "include/policy.h" |
21 | #include "include/task.h" |
22 | |
23 | /** |
24 | * aa_get_task_label - Get another task's label |
25 | * @task: task to query (NOT NULL) |
26 | * |
27 | * Returns: counted reference to @task's label |
28 | */ |
29 | struct aa_label *aa_get_task_label(struct task_struct *task) |
30 | { |
31 | struct aa_label *p; |
32 | |
33 | rcu_read_lock(); |
34 | p = aa_get_newest_cred_label(__task_cred(task)); |
35 | rcu_read_unlock(); |
36 | |
37 | return p; |
38 | } |
39 | |
40 | /** |
41 | * aa_replace_current_label - replace the current tasks label |
42 | * @label: new label (NOT NULL) |
43 | * |
44 | * Returns: 0 or error on failure |
45 | */ |
46 | int aa_replace_current_label(struct aa_label *label) |
47 | { |
48 | struct aa_label *old = aa_current_raw_label(); |
49 | struct aa_task_ctx *ctx = task_ctx(current); |
50 | struct cred *new; |
51 | |
52 | AA_BUG(!label); |
53 | |
54 | if (old == label) |
55 | return 0; |
56 | |
57 | if (current_cred() != current_real_cred()) |
58 | return -EBUSY; |
59 | |
60 | new = prepare_creds(); |
61 | if (!new) |
62 | return -ENOMEM; |
63 | |
64 | if (ctx->nnp && label_is_stale(ctx->nnp)) { |
65 | struct aa_label *tmp = ctx->nnp; |
66 | |
67 | ctx->nnp = aa_get_newest_label(l: tmp); |
68 | aa_put_label(l: tmp); |
69 | } |
70 | if (unconfined(label) || (labels_ns(old) != labels_ns(label))) |
71 | /* |
72 | * if switching to unconfined or a different label namespace |
73 | * clear out context state |
74 | */ |
75 | aa_clear_task_ctx_trans(ctx: task_ctx(current)); |
76 | |
77 | /* |
78 | * be careful switching cred label, when racing replacement it |
79 | * is possible that the cred labels's->proxy->label is the reference |
80 | * keeping @label valid, so make sure to get its reference before |
81 | * dropping the reference on the cred's label |
82 | */ |
83 | aa_get_label(l: label); |
84 | aa_put_label(l: cred_label(cred: new)); |
85 | set_cred_label(cred: new, label); |
86 | |
87 | commit_creds(new); |
88 | return 0; |
89 | } |
90 | |
91 | |
92 | /** |
93 | * aa_set_current_onexec - set the tasks change_profile to happen onexec |
94 | * @label: system label to set at exec (MAYBE NULL to clear value) |
95 | * @stack: whether stacking should be done |
96 | */ |
97 | void aa_set_current_onexec(struct aa_label *label, bool stack) |
98 | { |
99 | struct aa_task_ctx *ctx = task_ctx(current); |
100 | |
101 | aa_get_label(l: label); |
102 | aa_put_label(l: ctx->onexec); |
103 | ctx->onexec = label; |
104 | ctx->token = stack; |
105 | } |
106 | |
107 | /** |
108 | * aa_set_current_hat - set the current tasks hat |
109 | * @label: label to set as the current hat (NOT NULL) |
110 | * @token: token value that must be specified to change from the hat |
111 | * |
112 | * Do switch of tasks hat. If the task is currently in a hat |
113 | * validate the token to match. |
114 | * |
115 | * Returns: 0 or error on failure |
116 | */ |
117 | int aa_set_current_hat(struct aa_label *label, u64 token) |
118 | { |
119 | struct aa_task_ctx *ctx = task_ctx(current); |
120 | struct cred *new; |
121 | |
122 | new = prepare_creds(); |
123 | if (!new) |
124 | return -ENOMEM; |
125 | AA_BUG(!label); |
126 | |
127 | if (!ctx->previous) { |
128 | /* transfer refcount */ |
129 | ctx->previous = cred_label(cred: new); |
130 | ctx->token = token; |
131 | } else if (ctx->token == token) { |
132 | aa_put_label(l: cred_label(cred: new)); |
133 | } else { |
134 | /* previous_profile && ctx->token != token */ |
135 | abort_creds(new); |
136 | return -EACCES; |
137 | } |
138 | |
139 | set_cred_label(cred: new, label: aa_get_newest_label(l: label)); |
140 | /* clear exec on switching context */ |
141 | aa_put_label(l: ctx->onexec); |
142 | ctx->onexec = NULL; |
143 | |
144 | commit_creds(new); |
145 | return 0; |
146 | } |
147 | |
148 | /** |
149 | * aa_restore_previous_label - exit from hat context restoring previous label |
150 | * @token: the token that must be matched to exit hat context |
151 | * |
152 | * Attempt to return out of a hat to the previous label. The token |
153 | * must match the stored token value. |
154 | * |
155 | * Returns: 0 or error of failure |
156 | */ |
157 | int aa_restore_previous_label(u64 token) |
158 | { |
159 | struct aa_task_ctx *ctx = task_ctx(current); |
160 | struct cred *new; |
161 | |
162 | if (ctx->token != token) |
163 | return -EACCES; |
164 | /* ignore restores when there is no saved label */ |
165 | if (!ctx->previous) |
166 | return 0; |
167 | |
168 | new = prepare_creds(); |
169 | if (!new) |
170 | return -ENOMEM; |
171 | |
172 | aa_put_label(l: cred_label(cred: new)); |
173 | set_cred_label(cred: new, label: aa_get_newest_label(l: ctx->previous)); |
174 | AA_BUG(!cred_label(new)); |
175 | /* clear exec && prev information when restoring to previous context */ |
176 | aa_clear_task_ctx_trans(ctx); |
177 | |
178 | commit_creds(new); |
179 | |
180 | return 0; |
181 | } |
182 | |
183 | /** |
184 | * audit_ptrace_mask - convert mask to permission string |
185 | * @mask: permission mask to convert |
186 | * |
187 | * Returns: pointer to static string |
188 | */ |
189 | static const char *audit_ptrace_mask(u32 mask) |
190 | { |
191 | switch (mask) { |
192 | case MAY_READ: |
193 | return "read" ; |
194 | case MAY_WRITE: |
195 | return "trace" ; |
196 | case AA_MAY_BE_READ: |
197 | return "readby" ; |
198 | case AA_MAY_BE_TRACED: |
199 | return "tracedby" ; |
200 | } |
201 | return "" ; |
202 | } |
203 | |
204 | /* call back to audit ptrace fields */ |
205 | static void audit_ptrace_cb(struct audit_buffer *ab, void *va) |
206 | { |
207 | struct common_audit_data *sa = va; |
208 | struct apparmor_audit_data *ad = aad(sa); |
209 | |
210 | if (ad->request & AA_PTRACE_PERM_MASK) { |
211 | audit_log_format(ab, fmt: " requested_mask=\"%s\"" , |
212 | audit_ptrace_mask(mask: ad->request)); |
213 | |
214 | if (ad->denied & AA_PTRACE_PERM_MASK) { |
215 | audit_log_format(ab, fmt: " denied_mask=\"%s\"" , |
216 | audit_ptrace_mask(mask: ad->denied)); |
217 | } |
218 | } |
219 | audit_log_format(ab, fmt: " peer=" ); |
220 | aa_label_xaudit(ab, labels_ns(ad->subj_label), label: ad->peer, |
221 | FLAGS_NONE, GFP_ATOMIC); |
222 | } |
223 | |
224 | /* assumes check for RULE_MEDIATES is already done */ |
225 | /* TODO: conditionals */ |
226 | static int profile_ptrace_perm(const struct cred *cred, |
227 | struct aa_profile *profile, |
228 | struct aa_label *peer, u32 request, |
229 | struct apparmor_audit_data *ad) |
230 | { |
231 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
232 | typeof(*rules), list); |
233 | struct aa_perms perms = { }; |
234 | |
235 | ad->subj_cred = cred; |
236 | ad->peer = peer; |
237 | aa_profile_match_label(profile, rules, label: peer, AA_CLASS_PTRACE, request, |
238 | perms: &perms); |
239 | aa_apply_modes_to_perms(profile, perms: &perms); |
240 | return aa_check_perms(profile, perms: &perms, request, ad, cb: audit_ptrace_cb); |
241 | } |
242 | |
243 | static int profile_tracee_perm(const struct cred *cred, |
244 | struct aa_profile *tracee, |
245 | struct aa_label *tracer, u32 request, |
246 | struct apparmor_audit_data *ad) |
247 | { |
248 | if (profile_unconfined(tracee) || unconfined(tracer) || |
249 | !ANY_RULE_MEDIATES(head: &tracee->rules, AA_CLASS_PTRACE)) |
250 | return 0; |
251 | |
252 | return profile_ptrace_perm(cred, profile: tracee, peer: tracer, request, ad); |
253 | } |
254 | |
255 | static int profile_tracer_perm(const struct cred *cred, |
256 | struct aa_profile *tracer, |
257 | struct aa_label *tracee, u32 request, |
258 | struct apparmor_audit_data *ad) |
259 | { |
260 | if (profile_unconfined(tracer)) |
261 | return 0; |
262 | |
263 | if (ANY_RULE_MEDIATES(head: &tracer->rules, AA_CLASS_PTRACE)) |
264 | return profile_ptrace_perm(cred, profile: tracer, peer: tracee, request, ad); |
265 | |
266 | /* profile uses the old style capability check for ptrace */ |
267 | if (&tracer->label == tracee) |
268 | return 0; |
269 | |
270 | ad->subj_label = &tracer->label; |
271 | ad->peer = tracee; |
272 | ad->request = 0; |
273 | ad->error = aa_capable(subj_cred: cred, label: &tracer->label, CAP_SYS_PTRACE, |
274 | CAP_OPT_NONE); |
275 | |
276 | return aa_audit(type: AUDIT_APPARMOR_AUTO, profile: tracer, ad, cb: audit_ptrace_cb); |
277 | } |
278 | |
279 | /** |
280 | * aa_may_ptrace - test if tracer task can trace the tracee |
281 | * @tracer: label of the task doing the tracing (NOT NULL) |
282 | * @tracee: task label to be traced |
283 | * @request: permission request |
284 | * |
285 | * Returns: %0 else error code if permission denied or error |
286 | */ |
287 | int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer, |
288 | const struct cred *tracee_cred, struct aa_label *tracee, |
289 | u32 request) |
290 | { |
291 | struct aa_profile *profile; |
292 | u32 xrequest = request << PTRACE_PERM_SHIFT; |
293 | DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_PTRACE, OP_PTRACE); |
294 | |
295 | return xcheck_labels(tracer, tracee, profile, |
296 | profile_tracer_perm(tracer_cred, profile, tracee, |
297 | request, &sa), |
298 | profile_tracee_perm(tracee_cred, profile, tracer, |
299 | xrequest, &sa)); |
300 | } |
301 | |
302 | /* call back to audit ptrace fields */ |
303 | static void audit_ns_cb(struct audit_buffer *ab, void *va) |
304 | { |
305 | struct apparmor_audit_data *ad = aad_of_va(va); |
306 | |
307 | if (ad->request & AA_USERNS_CREATE) |
308 | audit_log_format(ab, fmt: " requested=\"userns_create\"" ); |
309 | |
310 | if (ad->denied & AA_USERNS_CREATE) |
311 | audit_log_format(ab, fmt: " denied=\"userns_create\"" ); |
312 | } |
313 | |
314 | int aa_profile_ns_perm(struct aa_profile *profile, |
315 | struct apparmor_audit_data *ad, |
316 | u32 request) |
317 | { |
318 | struct aa_perms perms = { }; |
319 | int error = 0; |
320 | |
321 | ad->subj_label = &profile->label; |
322 | ad->request = request; |
323 | |
324 | if (!profile_unconfined(profile)) { |
325 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
326 | typeof(*rules), |
327 | list); |
328 | aa_state_t state; |
329 | |
330 | state = RULE_MEDIATES(rules, class: ad->class); |
331 | if (!state) |
332 | /* TODO: add flag to complain about unmediated */ |
333 | return 0; |
334 | perms = *aa_lookup_perms(policy: rules->policy, state); |
335 | aa_apply_modes_to_perms(profile, perms: &perms); |
336 | error = aa_check_perms(profile, perms: &perms, request, ad, |
337 | cb: audit_ns_cb); |
338 | } |
339 | |
340 | return error; |
341 | } |
342 | |