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> |
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 | ||
8a56038c KC |
45 | static void report_access(const char *access, struct task_struct *target, |
46 | struct task_struct *agent) | |
47 | { | |
48 | char *target_cmd, *agent_cmd; | |
49 | ||
74f430cd SL |
50 | target_cmd = kstrdup_quotable_cmdline(target, GFP_ATOMIC); |
51 | agent_cmd = kstrdup_quotable_cmdline(agent, GFP_ATOMIC); | |
8a56038c KC |
52 | |
53 | pr_notice_ratelimited( | |
54 | "ptrace %s of \"%s\"[%d] was attempted by \"%s\"[%d]\n", | |
55 | access, target_cmd, target->pid, agent_cmd, agent->pid); | |
56 | ||
57 | kfree(agent_cmd); | |
58 | kfree(target_cmd); | |
59 | } | |
60 | ||
235e7527 KC |
61 | /** |
62 | * yama_relation_cleanup - remove invalid entries from the relation list | |
63 | * | |
64 | */ | |
65 | static void yama_relation_cleanup(struct work_struct *work) | |
66 | { | |
67 | struct ptrace_relation *relation; | |
68 | ||
69 | spin_lock(&ptracer_relations_lock); | |
70 | rcu_read_lock(); | |
71 | list_for_each_entry_rcu(relation, &ptracer_relations, node) { | |
72 | if (relation->invalid) { | |
73 | list_del_rcu(&relation->node); | |
74 | kfree_rcu(relation, rcu); | |
75 | } | |
76 | } | |
77 | rcu_read_unlock(); | |
78 | spin_unlock(&ptracer_relations_lock); | |
79 | } | |
80 | ||
2d514487 KC |
81 | /** |
82 | * yama_ptracer_add - add/replace an exception for this tracer/tracee pair | |
83 | * @tracer: the task_struct of the process doing the ptrace | |
84 | * @tracee: the task_struct of the process to be ptraced | |
85 | * | |
86 | * Each tracee can have, at most, one tracer registered. Each time this | |
87 | * is called, the prior registered tracer will be replaced for the tracee. | |
88 | * | |
89 | * Returns 0 if relationship was added, -ve on error. | |
90 | */ | |
91 | static int yama_ptracer_add(struct task_struct *tracer, | |
92 | struct task_struct *tracee) | |
93 | { | |
93b69d43 | 94 | struct ptrace_relation *relation, *added; |
2d514487 KC |
95 | |
96 | added = kmalloc(sizeof(*added), GFP_KERNEL); | |
97 | if (!added) | |
98 | return -ENOMEM; | |
99 | ||
93b69d43 KC |
100 | added->tracee = tracee; |
101 | added->tracer = tracer; | |
235e7527 | 102 | added->invalid = false; |
93b69d43 | 103 | |
235e7527 | 104 | spin_lock(&ptracer_relations_lock); |
93b69d43 KC |
105 | rcu_read_lock(); |
106 | list_for_each_entry_rcu(relation, &ptracer_relations, node) { | |
235e7527 KC |
107 | if (relation->invalid) |
108 | continue; | |
93b69d43 KC |
109 | if (relation->tracee == tracee) { |
110 | list_replace_rcu(&relation->node, &added->node); | |
111 | kfree_rcu(relation, rcu); | |
112 | goto out; | |
2d514487 | 113 | } |
2d514487 | 114 | } |
2d514487 | 115 | |
93b69d43 | 116 | list_add_rcu(&added->node, &ptracer_relations); |
2d514487 | 117 | |
93b69d43 KC |
118 | out: |
119 | rcu_read_unlock(); | |
235e7527 | 120 | spin_unlock(&ptracer_relations_lock); |
93b69d43 | 121 | return 0; |
2d514487 KC |
122 | } |
123 | ||
124 | /** | |
125 | * yama_ptracer_del - remove exceptions related to the given tasks | |
126 | * @tracer: remove any relation where tracer task matches | |
127 | * @tracee: remove any relation where tracee task matches | |
128 | */ | |
129 | static void yama_ptracer_del(struct task_struct *tracer, | |
130 | struct task_struct *tracee) | |
131 | { | |
93b69d43 | 132 | struct ptrace_relation *relation; |
235e7527 | 133 | bool marked = false; |
2d514487 | 134 | |
93b69d43 KC |
135 | rcu_read_lock(); |
136 | list_for_each_entry_rcu(relation, &ptracer_relations, node) { | |
235e7527 KC |
137 | if (relation->invalid) |
138 | continue; | |
2d514487 | 139 | if (relation->tracee == tracee || |
bf06189e | 140 | (tracer && relation->tracer == tracer)) { |
235e7527 KC |
141 | relation->invalid = true; |
142 | marked = true; | |
2d514487 | 143 | } |
93b69d43 KC |
144 | } |
145 | rcu_read_unlock(); | |
235e7527 KC |
146 | |
147 | if (marked) | |
148 | schedule_work(&yama_relation_work); | |
2d514487 KC |
149 | } |
150 | ||
151 | /** | |
152 | * yama_task_free - check for task_pid to remove from exception list | |
153 | * @task: task being removed | |
154 | */ | |
c6993e4a | 155 | void yama_task_free(struct task_struct *task) |
2d514487 KC |
156 | { |
157 | yama_ptracer_del(task, task); | |
158 | } | |
159 | ||
160 | /** | |
161 | * yama_task_prctl - check for Yama-specific prctl operations | |
162 | * @option: operation | |
163 | * @arg2: argument | |
164 | * @arg3: argument | |
165 | * @arg4: argument | |
166 | * @arg5: argument | |
167 | * | |
168 | * Return 0 on success, -ve on error. -ENOSYS is returned when Yama | |
169 | * does not handle the given option. | |
170 | */ | |
c6993e4a | 171 | int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, |
2d514487 KC |
172 | unsigned long arg4, unsigned long arg5) |
173 | { | |
b1d9e6b0 | 174 | int rc = -ENOSYS; |
2d514487 KC |
175 | struct task_struct *myself = current; |
176 | ||
2d514487 KC |
177 | switch (option) { |
178 | case PR_SET_PTRACER: | |
179 | /* Since a thread can call prctl(), find the group leader | |
180 | * before calling _add() or _del() on it, since we want | |
181 | * process-level granularity of control. The tracer group | |
182 | * leader checking is handled later when walking the ancestry | |
183 | * at the time of PTRACE_ATTACH check. | |
184 | */ | |
185 | rcu_read_lock(); | |
186 | if (!thread_group_leader(myself)) | |
187 | myself = rcu_dereference(myself->group_leader); | |
188 | get_task_struct(myself); | |
189 | rcu_read_unlock(); | |
190 | ||
191 | if (arg2 == 0) { | |
192 | yama_ptracer_del(NULL, myself); | |
193 | rc = 0; | |
2e4930eb | 194 | } else if (arg2 == PR_SET_PTRACER_ANY || (int)arg2 == -1) { |
bf06189e | 195 | rc = yama_ptracer_add(NULL, myself); |
2d514487 KC |
196 | } else { |
197 | struct task_struct *tracer; | |
198 | ||
199 | rcu_read_lock(); | |
200 | tracer = find_task_by_vpid(arg2); | |
201 | if (tracer) | |
202 | get_task_struct(tracer); | |
203 | else | |
204 | rc = -EINVAL; | |
205 | rcu_read_unlock(); | |
206 | ||
207 | if (tracer) { | |
208 | rc = yama_ptracer_add(tracer, myself); | |
209 | put_task_struct(tracer); | |
210 | } | |
211 | } | |
212 | ||
213 | put_task_struct(myself); | |
214 | break; | |
215 | } | |
216 | ||
217 | return rc; | |
218 | } | |
219 | ||
220 | /** | |
221 | * task_is_descendant - walk up a process family tree looking for a match | |
222 | * @parent: the process to compare against while walking up from child | |
223 | * @child: the process to start from while looking upwards for parent | |
224 | * | |
225 | * Returns 1 if child is a descendant of parent, 0 if not. | |
226 | */ | |
227 | static int task_is_descendant(struct task_struct *parent, | |
228 | struct task_struct *child) | |
229 | { | |
230 | int rc = 0; | |
231 | struct task_struct *walker = child; | |
232 | ||
233 | if (!parent || !child) | |
234 | return 0; | |
235 | ||
236 | rcu_read_lock(); | |
237 | if (!thread_group_leader(parent)) | |
238 | parent = rcu_dereference(parent->group_leader); | |
239 | while (walker->pid > 0) { | |
240 | if (!thread_group_leader(walker)) | |
241 | walker = rcu_dereference(walker->group_leader); | |
242 | if (walker == parent) { | |
243 | rc = 1; | |
244 | break; | |
245 | } | |
246 | walker = rcu_dereference(walker->real_parent); | |
247 | } | |
248 | rcu_read_unlock(); | |
249 | ||
250 | return rc; | |
251 | } | |
252 | ||
253 | /** | |
254 | * ptracer_exception_found - tracer registered as exception for this tracee | |
255 | * @tracer: the task_struct of the process attempting ptrace | |
256 | * @tracee: the task_struct of the process to be ptraced | |
257 | * | |
258 | * Returns 1 if tracer has is ptracer exception ancestor for tracee. | |
259 | */ | |
260 | static int ptracer_exception_found(struct task_struct *tracer, | |
261 | struct task_struct *tracee) | |
262 | { | |
263 | int rc = 0; | |
264 | struct ptrace_relation *relation; | |
265 | struct task_struct *parent = NULL; | |
bf06189e | 266 | bool found = false; |
2d514487 | 267 | |
2d514487 KC |
268 | rcu_read_lock(); |
269 | if (!thread_group_leader(tracee)) | |
270 | tracee = rcu_dereference(tracee->group_leader); | |
235e7527 KC |
271 | list_for_each_entry_rcu(relation, &ptracer_relations, node) { |
272 | if (relation->invalid) | |
273 | continue; | |
2d514487 KC |
274 | if (relation->tracee == tracee) { |
275 | parent = relation->tracer; | |
bf06189e | 276 | found = true; |
2d514487 KC |
277 | break; |
278 | } | |
235e7527 | 279 | } |
2d514487 | 280 | |
bf06189e | 281 | if (found && (parent == NULL || task_is_descendant(parent, tracer))) |
2d514487 KC |
282 | rc = 1; |
283 | rcu_read_unlock(); | |
2d514487 KC |
284 | |
285 | return rc; | |
286 | } | |
287 | ||
288 | /** | |
289 | * yama_ptrace_access_check - validate PTRACE_ATTACH calls | |
290 | * @child: task that current task is attempting to ptrace | |
291 | * @mode: ptrace attach mode | |
292 | * | |
293 | * Returns 0 if following the ptrace is allowed, -ve on error. | |
294 | */ | |
b1d9e6b0 | 295 | static int yama_ptrace_access_check(struct task_struct *child, |
2d514487 KC |
296 | unsigned int mode) |
297 | { | |
b1d9e6b0 | 298 | int rc = 0; |
2d514487 KC |
299 | |
300 | /* require ptrace target be a child of ptracer on attach */ | |
3dfb7d8c | 301 | if (mode & PTRACE_MODE_ATTACH) { |
389da25f KC |
302 | switch (ptrace_scope) { |
303 | case YAMA_SCOPE_DISABLED: | |
304 | /* No additional restrictions. */ | |
305 | break; | |
306 | case YAMA_SCOPE_RELATIONAL: | |
4c44aaaf | 307 | rcu_read_lock(); |
389da25f KC |
308 | if (!task_is_descendant(current, child) && |
309 | !ptracer_exception_found(current, child) && | |
4c44aaaf | 310 | !ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE)) |
389da25f | 311 | rc = -EPERM; |
4c44aaaf | 312 | rcu_read_unlock(); |
389da25f KC |
313 | break; |
314 | case YAMA_SCOPE_CAPABILITY: | |
4c44aaaf EB |
315 | rcu_read_lock(); |
316 | if (!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE)) | |
389da25f | 317 | rc = -EPERM; |
4c44aaaf | 318 | rcu_read_unlock(); |
389da25f KC |
319 | break; |
320 | case YAMA_SCOPE_NO_ATTACH: | |
321 | default: | |
322 | rc = -EPERM; | |
323 | break; | |
324 | } | |
325 | } | |
2d514487 | 326 | |
8a56038c KC |
327 | if (rc && (mode & PTRACE_MODE_NOAUDIT) == 0) |
328 | report_access("attach", child, current); | |
2d514487 KC |
329 | |
330 | return rc; | |
331 | } | |
332 | ||
9d8dad74 KC |
333 | /** |
334 | * yama_ptrace_traceme - validate PTRACE_TRACEME calls | |
335 | * @parent: task that will become the ptracer of the current task | |
336 | * | |
337 | * Returns 0 if following the ptrace is allowed, -ve on error. | |
338 | */ | |
c6993e4a | 339 | int yama_ptrace_traceme(struct task_struct *parent) |
9d8dad74 | 340 | { |
b1d9e6b0 | 341 | int rc = 0; |
9d8dad74 KC |
342 | |
343 | /* Only disallow PTRACE_TRACEME on more aggressive settings. */ | |
344 | switch (ptrace_scope) { | |
345 | case YAMA_SCOPE_CAPABILITY: | |
eddc0a3a | 346 | if (!has_ns_capability(parent, current_user_ns(), CAP_SYS_PTRACE)) |
9d8dad74 KC |
347 | rc = -EPERM; |
348 | break; | |
349 | case YAMA_SCOPE_NO_ATTACH: | |
350 | rc = -EPERM; | |
351 | break; | |
352 | } | |
353 | ||
8a56038c KC |
354 | if (rc) |
355 | report_access("traceme", current, parent); | |
9d8dad74 KC |
356 | |
357 | return rc; | |
358 | } | |
359 | ||
b1d9e6b0 | 360 | static struct security_hook_list yama_hooks[] = { |
e20b043a CS |
361 | LSM_HOOK_INIT(ptrace_access_check, yama_ptrace_access_check), |
362 | LSM_HOOK_INIT(ptrace_traceme, yama_ptrace_traceme), | |
363 | LSM_HOOK_INIT(task_prctl, yama_task_prctl), | |
364 | LSM_HOOK_INIT(task_free, yama_task_free), | |
2d514487 | 365 | }; |
b1d9e6b0 | 366 | |
2d514487 | 367 | #ifdef CONFIG_SYSCTL |
389da25f KC |
368 | static int yama_dointvec_minmax(struct ctl_table *table, int write, |
369 | void __user *buffer, size_t *lenp, loff_t *ppos) | |
370 | { | |
41a4695c | 371 | struct ctl_table table_copy; |
389da25f KC |
372 | |
373 | if (write && !capable(CAP_SYS_PTRACE)) | |
374 | return -EPERM; | |
375 | ||
389da25f | 376 | /* Lock the max value if it ever gets set. */ |
41a4695c KC |
377 | table_copy = *table; |
378 | if (*(int *)table_copy.data == *(int *)table_copy.extra2) | |
379 | table_copy.extra1 = table_copy.extra2; | |
389da25f | 380 | |
41a4695c | 381 | return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos); |
389da25f KC |
382 | } |
383 | ||
2d514487 | 384 | static int zero; |
389da25f | 385 | static int max_scope = YAMA_SCOPE_NO_ATTACH; |
2d514487 KC |
386 | |
387 | struct ctl_path yama_sysctl_path[] = { | |
388 | { .procname = "kernel", }, | |
389 | { .procname = "yama", }, | |
390 | { } | |
391 | }; | |
392 | ||
393 | static struct ctl_table yama_sysctl_table[] = { | |
394 | { | |
395 | .procname = "ptrace_scope", | |
396 | .data = &ptrace_scope, | |
397 | .maxlen = sizeof(int), | |
398 | .mode = 0644, | |
389da25f | 399 | .proc_handler = yama_dointvec_minmax, |
2d514487 | 400 | .extra1 = &zero, |
389da25f | 401 | .extra2 = &max_scope, |
2d514487 KC |
402 | }, |
403 | { } | |
404 | }; | |
730daa16 | 405 | static void __init yama_init_sysctl(void) |
2d514487 | 406 | { |
2d514487 KC |
407 | if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table)) |
408 | panic("Yama: sysctl registration failed.\n"); | |
2d514487 | 409 | } |
730daa16 KC |
410 | #else |
411 | static inline void yama_init_sysctl(void) { } | |
412 | #endif /* CONFIG_SYSCTL */ | |
2d514487 | 413 | |
730daa16 KC |
414 | void __init yama_add_hooks(void) |
415 | { | |
416 | pr_info("Yama: becoming mindful.\n"); | |
417 | security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks)); | |
418 | yama_init_sysctl(); | |
419 | } |