Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
2d514487 KC |
2 | /* |
3 | * Yama Linux Security Module | |
4 | * | |
5 | * Author: Kees Cook <keescook@chromium.org> | |
6 | * | |
7 | * Copyright (C) 2010 Canonical, Ltd. | |
8 | * Copyright (C) 2011 The Chromium OS Authors. | |
2d514487 KC |
9 | */ |
10 | ||
3c4ed7bd | 11 | #include <linux/lsm_hooks.h> |
2d514487 KC |
12 | #include <linux/sysctl.h> |
13 | #include <linux/ptrace.h> | |
14 | #include <linux/prctl.h> | |
15 | #include <linux/ratelimit.h> | |
235e7527 | 16 | #include <linux/workqueue.h> |
8a56038c | 17 | #include <linux/string_helpers.h> |
dca6b414 JH |
18 | #include <linux/task_work.h> |
19 | #include <linux/sched.h> | |
20 | #include <linux/spinlock.h> | |
f3b8788c | 21 | #include <uapi/linux/lsm.h> |
2d514487 | 22 | |
389da25f KC |
23 | #define YAMA_SCOPE_DISABLED 0 |
24 | #define YAMA_SCOPE_RELATIONAL 1 | |
25 | #define YAMA_SCOPE_CAPABILITY 2 | |
26 | #define YAMA_SCOPE_NO_ATTACH 3 | |
27 | ||
28 | static int ptrace_scope = YAMA_SCOPE_RELATIONAL; | |
2d514487 KC |
29 | |
30 | /* describe a ptrace relationship for potential exception */ | |
31 | struct ptrace_relation { | |
32 | struct task_struct *tracer; | |
33 | struct task_struct *tracee; | |
235e7527 | 34 | bool invalid; |
2d514487 | 35 | struct list_head node; |
93b69d43 | 36 | struct rcu_head rcu; |
2d514487 KC |
37 | }; |
38 | ||
39 | static LIST_HEAD(ptracer_relations); | |
40 | static DEFINE_SPINLOCK(ptracer_relations_lock); | |
41 | ||
235e7527 KC |
42 | static void yama_relation_cleanup(struct work_struct *work); |
43 | static DECLARE_WORK(yama_relation_work, yama_relation_cleanup); | |
44 | ||
dca6b414 JH |
45 | struct access_report_info { |
46 | struct callback_head work; | |
47 | const char *access; | |
48 | struct task_struct *target; | |
49 | struct task_struct *agent; | |
50 | }; | |
51 | ||
52 | static void __report_access(struct callback_head *work) | |
8a56038c | 53 | { |
dca6b414 JH |
54 | struct access_report_info *info = |
55 | container_of(work, struct access_report_info, work); | |
8a56038c KC |
56 | char *target_cmd, *agent_cmd; |
57 | ||
dca6b414 JH |
58 | target_cmd = kstrdup_quotable_cmdline(info->target, GFP_KERNEL); |
59 | agent_cmd = kstrdup_quotable_cmdline(info->agent, GFP_KERNEL); | |
8a56038c KC |
60 | |
61 | pr_notice_ratelimited( | |
62 | "ptrace %s of \"%s\"[%d] was attempted by \"%s\"[%d]\n", | |
dca6b414 JH |
63 | info->access, target_cmd, info->target->pid, agent_cmd, |
64 | info->agent->pid); | |
8a56038c KC |
65 | |
66 | kfree(agent_cmd); | |
67 | kfree(target_cmd); | |
dca6b414 JH |
68 | |
69 | put_task_struct(info->agent); | |
70 | put_task_struct(info->target); | |
71 | kfree(info); | |
72 | } | |
73 | ||
74 | /* defers execution because cmdline access can sleep */ | |
75 | static void report_access(const char *access, struct task_struct *target, | |
76 | struct task_struct *agent) | |
77 | { | |
78 | struct access_report_info *info; | |
79 | char agent_comm[sizeof(agent->comm)]; | |
80 | ||
81 | assert_spin_locked(&target->alloc_lock); /* for target->comm */ | |
82 | ||
83 | if (current->flags & PF_KTHREAD) { | |
84 | /* I don't think kthreads call task_work_run() before exiting. | |
85 | * Imagine angry ranting about procfs here. | |
86 | */ | |
87 | pr_notice_ratelimited( | |
88 | "ptrace %s of \"%s\"[%d] was attempted by \"%s\"[%d]\n", | |
89 | access, target->comm, target->pid, | |
90 | get_task_comm(agent_comm, agent), agent->pid); | |
91 | return; | |
92 | } | |
93 | ||
94 | info = kmalloc(sizeof(*info), GFP_ATOMIC); | |
95 | if (!info) | |
96 | return; | |
97 | init_task_work(&info->work, __report_access); | |
98 | get_task_struct(target); | |
99 | get_task_struct(agent); | |
100 | info->access = access; | |
101 | info->target = target; | |
102 | info->agent = agent; | |
91989c70 | 103 | if (task_work_add(current, &info->work, TWA_RESUME) == 0) |
dca6b414 JH |
104 | return; /* success */ |
105 | ||
106 | WARN(1, "report_access called from exiting task"); | |
107 | put_task_struct(target); | |
108 | put_task_struct(agent); | |
109 | kfree(info); | |
8a56038c KC |
110 | } |
111 | ||
235e7527 KC |
112 | /** |
113 | * yama_relation_cleanup - remove invalid entries from the relation list | |
114 | * | |
115 | */ | |
116 | static void yama_relation_cleanup(struct work_struct *work) | |
117 | { | |
118 | struct ptrace_relation *relation; | |
119 | ||
120 | spin_lock(&ptracer_relations_lock); | |
121 | rcu_read_lock(); | |
122 | list_for_each_entry_rcu(relation, &ptracer_relations, node) { | |
123 | if (relation->invalid) { | |
124 | list_del_rcu(&relation->node); | |
125 | kfree_rcu(relation, rcu); | |
126 | } | |
127 | } | |
128 | rcu_read_unlock(); | |
129 | spin_unlock(&ptracer_relations_lock); | |
130 | } | |
131 | ||
2d514487 KC |
132 | /** |
133 | * yama_ptracer_add - add/replace an exception for this tracer/tracee pair | |
134 | * @tracer: the task_struct of the process doing the ptrace | |
135 | * @tracee: the task_struct of the process to be ptraced | |
136 | * | |
137 | * Each tracee can have, at most, one tracer registered. Each time this | |
138 | * is called, the prior registered tracer will be replaced for the tracee. | |
139 | * | |
140 | * Returns 0 if relationship was added, -ve on error. | |
141 | */ | |
142 | static int yama_ptracer_add(struct task_struct *tracer, | |
143 | struct task_struct *tracee) | |
144 | { | |
93b69d43 | 145 | struct ptrace_relation *relation, *added; |
2d514487 KC |
146 | |
147 | added = kmalloc(sizeof(*added), GFP_KERNEL); | |
148 | if (!added) | |
149 | return -ENOMEM; | |
150 | ||
93b69d43 KC |
151 | added->tracee = tracee; |
152 | added->tracer = tracer; | |
235e7527 | 153 | added->invalid = false; |
93b69d43 | 154 | |
235e7527 | 155 | spin_lock(&ptracer_relations_lock); |
93b69d43 KC |
156 | rcu_read_lock(); |
157 | list_for_each_entry_rcu(relation, &ptracer_relations, node) { | |
235e7527 KC |
158 | if (relation->invalid) |
159 | continue; | |
93b69d43 KC |
160 | if (relation->tracee == tracee) { |
161 | list_replace_rcu(&relation->node, &added->node); | |
162 | kfree_rcu(relation, rcu); | |
163 | goto out; | |
2d514487 | 164 | } |
2d514487 | 165 | } |
2d514487 | 166 | |
93b69d43 | 167 | list_add_rcu(&added->node, &ptracer_relations); |
2d514487 | 168 | |
93b69d43 KC |
169 | out: |
170 | rcu_read_unlock(); | |
235e7527 | 171 | spin_unlock(&ptracer_relations_lock); |
93b69d43 | 172 | return 0; |
2d514487 KC |
173 | } |
174 | ||
175 | /** | |
176 | * yama_ptracer_del - remove exceptions related to the given tasks | |
177 | * @tracer: remove any relation where tracer task matches | |
178 | * @tracee: remove any relation where tracee task matches | |
179 | */ | |
180 | static void yama_ptracer_del(struct task_struct *tracer, | |
181 | struct task_struct *tracee) | |
182 | { | |
93b69d43 | 183 | struct ptrace_relation *relation; |
235e7527 | 184 | bool marked = false; |
2d514487 | 185 | |
93b69d43 KC |
186 | rcu_read_lock(); |
187 | list_for_each_entry_rcu(relation, &ptracer_relations, node) { | |
235e7527 KC |
188 | if (relation->invalid) |
189 | continue; | |
2d514487 | 190 | if (relation->tracee == tracee || |
bf06189e | 191 | (tracer && relation->tracer == tracer)) { |
235e7527 KC |
192 | relation->invalid = true; |
193 | marked = true; | |
2d514487 | 194 | } |
93b69d43 KC |
195 | } |
196 | rcu_read_unlock(); | |
235e7527 KC |
197 | |
198 | if (marked) | |
199 | schedule_work(&yama_relation_work); | |
2d514487 KC |
200 | } |
201 | ||
202 | /** | |
203 | * yama_task_free - check for task_pid to remove from exception list | |
204 | * @task: task being removed | |
205 | */ | |
1aa176ef | 206 | static void yama_task_free(struct task_struct *task) |
2d514487 KC |
207 | { |
208 | yama_ptracer_del(task, task); | |
209 | } | |
210 | ||
211 | /** | |
212 | * yama_task_prctl - check for Yama-specific prctl operations | |
213 | * @option: operation | |
214 | * @arg2: argument | |
215 | * @arg3: argument | |
216 | * @arg4: argument | |
217 | * @arg5: argument | |
218 | * | |
219 | * Return 0 on success, -ve on error. -ENOSYS is returned when Yama | |
220 | * does not handle the given option. | |
221 | */ | |
1aa176ef | 222 | static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, |
2d514487 KC |
223 | unsigned long arg4, unsigned long arg5) |
224 | { | |
b1d9e6b0 | 225 | int rc = -ENOSYS; |
2d514487 KC |
226 | struct task_struct *myself = current; |
227 | ||
2d514487 KC |
228 | switch (option) { |
229 | case PR_SET_PTRACER: | |
230 | /* Since a thread can call prctl(), find the group leader | |
231 | * before calling _add() or _del() on it, since we want | |
232 | * process-level granularity of control. The tracer group | |
233 | * leader checking is handled later when walking the ancestry | |
234 | * at the time of PTRACE_ATTACH check. | |
235 | */ | |
236 | rcu_read_lock(); | |
237 | if (!thread_group_leader(myself)) | |
238 | myself = rcu_dereference(myself->group_leader); | |
239 | get_task_struct(myself); | |
240 | rcu_read_unlock(); | |
241 | ||
242 | if (arg2 == 0) { | |
243 | yama_ptracer_del(NULL, myself); | |
244 | rc = 0; | |
2e4930eb | 245 | } else if (arg2 == PR_SET_PTRACER_ANY || (int)arg2 == -1) { |
bf06189e | 246 | rc = yama_ptracer_add(NULL, myself); |
2d514487 KC |
247 | } else { |
248 | struct task_struct *tracer; | |
249 | ||
2ee08260 MR |
250 | tracer = find_get_task_by_vpid(arg2); |
251 | if (!tracer) { | |
2d514487 | 252 | rc = -EINVAL; |
2ee08260 | 253 | } else { |
2d514487 KC |
254 | rc = yama_ptracer_add(tracer, myself); |
255 | put_task_struct(tracer); | |
256 | } | |
257 | } | |
258 | ||
259 | put_task_struct(myself); | |
260 | break; | |
261 | } | |
262 | ||
263 | return rc; | |
264 | } | |
265 | ||
266 | /** | |
267 | * task_is_descendant - walk up a process family tree looking for a match | |
268 | * @parent: the process to compare against while walking up from child | |
269 | * @child: the process to start from while looking upwards for parent | |
270 | * | |
271 | * Returns 1 if child is a descendant of parent, 0 if not. | |
272 | */ | |
273 | static int task_is_descendant(struct task_struct *parent, | |
274 | struct task_struct *child) | |
275 | { | |
276 | int rc = 0; | |
277 | struct task_struct *walker = child; | |
278 | ||
279 | if (!parent || !child) | |
280 | return 0; | |
281 | ||
282 | rcu_read_lock(); | |
283 | if (!thread_group_leader(parent)) | |
284 | parent = rcu_dereference(parent->group_leader); | |
285 | while (walker->pid > 0) { | |
286 | if (!thread_group_leader(walker)) | |
287 | walker = rcu_dereference(walker->group_leader); | |
288 | if (walker == parent) { | |
289 | rc = 1; | |
290 | break; | |
291 | } | |
292 | walker = rcu_dereference(walker->real_parent); | |
293 | } | |
294 | rcu_read_unlock(); | |
295 | ||
296 | return rc; | |
297 | } | |
298 | ||
299 | /** | |
300 | * ptracer_exception_found - tracer registered as exception for this tracee | |
301 | * @tracer: the task_struct of the process attempting ptrace | |
302 | * @tracee: the task_struct of the process to be ptraced | |
303 | * | |
50523a29 | 304 | * Returns 1 if tracer has a ptracer exception ancestor for tracee. |
2d514487 KC |
305 | */ |
306 | static int ptracer_exception_found(struct task_struct *tracer, | |
307 | struct task_struct *tracee) | |
308 | { | |
309 | int rc = 0; | |
310 | struct ptrace_relation *relation; | |
311 | struct task_struct *parent = NULL; | |
bf06189e | 312 | bool found = false; |
2d514487 | 313 | |
2d514487 | 314 | rcu_read_lock(); |
50523a29 JS |
315 | |
316 | /* | |
317 | * If there's already an active tracing relationship, then make an | |
318 | * exception for the sake of other accesses, like process_vm_rw(). | |
319 | */ | |
320 | parent = ptrace_parent(tracee); | |
321 | if (parent != NULL && same_thread_group(parent, tracer)) { | |
322 | rc = 1; | |
323 | goto unlock; | |
324 | } | |
325 | ||
326 | /* Look for a PR_SET_PTRACER relationship. */ | |
2d514487 KC |
327 | if (!thread_group_leader(tracee)) |
328 | tracee = rcu_dereference(tracee->group_leader); | |
235e7527 KC |
329 | list_for_each_entry_rcu(relation, &ptracer_relations, node) { |
330 | if (relation->invalid) | |
331 | continue; | |
2d514487 KC |
332 | if (relation->tracee == tracee) { |
333 | parent = relation->tracer; | |
bf06189e | 334 | found = true; |
2d514487 KC |
335 | break; |
336 | } | |
235e7527 | 337 | } |
2d514487 | 338 | |
bf06189e | 339 | if (found && (parent == NULL || task_is_descendant(parent, tracer))) |
2d514487 | 340 | rc = 1; |
50523a29 JS |
341 | |
342 | unlock: | |
2d514487 | 343 | rcu_read_unlock(); |
2d514487 KC |
344 | |
345 | return rc; | |
346 | } | |
347 | ||
348 | /** | |
349 | * yama_ptrace_access_check - validate PTRACE_ATTACH calls | |
350 | * @child: task that current task is attempting to ptrace | |
351 | * @mode: ptrace attach mode | |
352 | * | |
353 | * Returns 0 if following the ptrace is allowed, -ve on error. | |
354 | */ | |
b1d9e6b0 | 355 | static int yama_ptrace_access_check(struct task_struct *child, |
2d514487 KC |
356 | unsigned int mode) |
357 | { | |
b1d9e6b0 | 358 | int rc = 0; |
2d514487 KC |
359 | |
360 | /* require ptrace target be a child of ptracer on attach */ | |
3dfb7d8c | 361 | if (mode & PTRACE_MODE_ATTACH) { |
389da25f KC |
362 | switch (ptrace_scope) { |
363 | case YAMA_SCOPE_DISABLED: | |
364 | /* No additional restrictions. */ | |
365 | break; | |
366 | case YAMA_SCOPE_RELATIONAL: | |
4c44aaaf | 367 | rcu_read_lock(); |
9474f4e7 KC |
368 | if (!pid_alive(child)) |
369 | rc = -EPERM; | |
370 | if (!rc && !task_is_descendant(current, child) && | |
389da25f | 371 | !ptracer_exception_found(current, child) && |
4c44aaaf | 372 | !ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE)) |
389da25f | 373 | rc = -EPERM; |
4c44aaaf | 374 | rcu_read_unlock(); |
389da25f KC |
375 | break; |
376 | case YAMA_SCOPE_CAPABILITY: | |
4c44aaaf EB |
377 | rcu_read_lock(); |
378 | if (!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE)) | |
389da25f | 379 | rc = -EPERM; |
4c44aaaf | 380 | rcu_read_unlock(); |
389da25f KC |
381 | break; |
382 | case YAMA_SCOPE_NO_ATTACH: | |
383 | default: | |
384 | rc = -EPERM; | |
385 | break; | |
386 | } | |
387 | } | |
2d514487 | 388 | |
8a56038c KC |
389 | if (rc && (mode & PTRACE_MODE_NOAUDIT) == 0) |
390 | report_access("attach", child, current); | |
2d514487 KC |
391 | |
392 | return rc; | |
393 | } | |
394 | ||
9d8dad74 KC |
395 | /** |
396 | * yama_ptrace_traceme - validate PTRACE_TRACEME calls | |
397 | * @parent: task that will become the ptracer of the current task | |
398 | * | |
399 | * Returns 0 if following the ptrace is allowed, -ve on error. | |
400 | */ | |
1aa176ef | 401 | static int yama_ptrace_traceme(struct task_struct *parent) |
9d8dad74 | 402 | { |
b1d9e6b0 | 403 | int rc = 0; |
9d8dad74 KC |
404 | |
405 | /* Only disallow PTRACE_TRACEME on more aggressive settings. */ | |
406 | switch (ptrace_scope) { | |
407 | case YAMA_SCOPE_CAPABILITY: | |
eddc0a3a | 408 | if (!has_ns_capability(parent, current_user_ns(), CAP_SYS_PTRACE)) |
9d8dad74 KC |
409 | rc = -EPERM; |
410 | break; | |
411 | case YAMA_SCOPE_NO_ATTACH: | |
412 | rc = -EPERM; | |
413 | break; | |
414 | } | |
415 | ||
dca6b414 JH |
416 | if (rc) { |
417 | task_lock(current); | |
8a56038c | 418 | report_access("traceme", current, parent); |
dca6b414 JH |
419 | task_unlock(current); |
420 | } | |
9d8dad74 KC |
421 | |
422 | return rc; | |
423 | } | |
424 | ||
b1a867ee | 425 | static const struct lsm_id yama_lsmid = { |
f3b8788c CS |
426 | .name = "yama", |
427 | .id = LSM_ID_YAMA, | |
428 | }; | |
429 | ||
f22f9aaf | 430 | static struct security_hook_list yama_hooks[] __ro_after_init = { |
e20b043a CS |
431 | LSM_HOOK_INIT(ptrace_access_check, yama_ptrace_access_check), |
432 | LSM_HOOK_INIT(ptrace_traceme, yama_ptrace_traceme), | |
433 | LSM_HOOK_INIT(task_prctl, yama_task_prctl), | |
434 | LSM_HOOK_INIT(task_free, yama_task_free), | |
2d514487 | 435 | }; |
b1d9e6b0 | 436 | |
2d514487 | 437 | #ifdef CONFIG_SYSCTL |
389da25f | 438 | static int yama_dointvec_minmax(struct ctl_table *table, int write, |
32927393 | 439 | void *buffer, size_t *lenp, loff_t *ppos) |
389da25f | 440 | { |
41a4695c | 441 | struct ctl_table table_copy; |
389da25f KC |
442 | |
443 | if (write && !capable(CAP_SYS_PTRACE)) | |
444 | return -EPERM; | |
445 | ||
389da25f | 446 | /* Lock the max value if it ever gets set. */ |
41a4695c KC |
447 | table_copy = *table; |
448 | if (*(int *)table_copy.data == *(int *)table_copy.extra2) | |
449 | table_copy.extra1 = table_copy.extra2; | |
389da25f | 450 | |
41a4695c | 451 | return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos); |
389da25f KC |
452 | } |
453 | ||
389da25f | 454 | static int max_scope = YAMA_SCOPE_NO_ATTACH; |
2d514487 | 455 | |
2d514487 KC |
456 | static struct ctl_table yama_sysctl_table[] = { |
457 | { | |
458 | .procname = "ptrace_scope", | |
459 | .data = &ptrace_scope, | |
460 | .maxlen = sizeof(int), | |
461 | .mode = 0644, | |
389da25f | 462 | .proc_handler = yama_dointvec_minmax, |
eec4844f | 463 | .extra1 = SYSCTL_ZERO, |
389da25f | 464 | .extra2 = &max_scope, |
2d514487 KC |
465 | }, |
466 | { } | |
467 | }; | |
730daa16 | 468 | static void __init yama_init_sysctl(void) |
2d514487 | 469 | { |
98cfeb8d | 470 | if (!register_sysctl("kernel/yama", yama_sysctl_table)) |
2d514487 | 471 | panic("Yama: sysctl registration failed.\n"); |
2d514487 | 472 | } |
730daa16 KC |
473 | #else |
474 | static inline void yama_init_sysctl(void) { } | |
475 | #endif /* CONFIG_SYSCTL */ | |
2d514487 | 476 | |
d6aed64b | 477 | static int __init yama_init(void) |
730daa16 KC |
478 | { |
479 | pr_info("Yama: becoming mindful.\n"); | |
f3b8788c | 480 | security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks), &yama_lsmid); |
730daa16 | 481 | yama_init_sysctl(); |
d6aed64b | 482 | return 0; |
730daa16 | 483 | } |
d6aed64b KC |
484 | |
485 | DEFINE_LSM(yama) = { | |
486 | .name = "yama", | |
487 | .init = yama_init, | |
488 | }; |