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