]>
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 | ||
15 | #include <linux/security.h> | |
16 | #include <linux/sysctl.h> | |
17 | #include <linux/ptrace.h> | |
18 | #include <linux/prctl.h> | |
19 | #include <linux/ratelimit.h> | |
20 | ||
21 | static int ptrace_scope = 1; | |
22 | ||
23 | /* describe a ptrace relationship for potential exception */ | |
24 | struct ptrace_relation { | |
25 | struct task_struct *tracer; | |
26 | struct task_struct *tracee; | |
27 | struct list_head node; | |
28 | }; | |
29 | ||
30 | static LIST_HEAD(ptracer_relations); | |
31 | static DEFINE_SPINLOCK(ptracer_relations_lock); | |
32 | ||
33 | /** | |
34 | * yama_ptracer_add - add/replace an exception for this tracer/tracee pair | |
35 | * @tracer: the task_struct of the process doing the ptrace | |
36 | * @tracee: the task_struct of the process to be ptraced | |
37 | * | |
38 | * Each tracee can have, at most, one tracer registered. Each time this | |
39 | * is called, the prior registered tracer will be replaced for the tracee. | |
40 | * | |
41 | * Returns 0 if relationship was added, -ve on error. | |
42 | */ | |
43 | static int yama_ptracer_add(struct task_struct *tracer, | |
44 | struct task_struct *tracee) | |
45 | { | |
46 | int rc = 0; | |
47 | struct ptrace_relation *added; | |
48 | struct ptrace_relation *entry, *relation = NULL; | |
49 | ||
50 | added = kmalloc(sizeof(*added), GFP_KERNEL); | |
51 | if (!added) | |
52 | return -ENOMEM; | |
53 | ||
54 | spin_lock_bh(&ptracer_relations_lock); | |
55 | list_for_each_entry(entry, &ptracer_relations, node) | |
56 | if (entry->tracee == tracee) { | |
57 | relation = entry; | |
58 | break; | |
59 | } | |
60 | if (!relation) { | |
61 | relation = added; | |
62 | relation->tracee = tracee; | |
63 | list_add(&relation->node, &ptracer_relations); | |
64 | } | |
65 | relation->tracer = tracer; | |
66 | ||
67 | spin_unlock_bh(&ptracer_relations_lock); | |
68 | if (added != relation) | |
69 | kfree(added); | |
70 | ||
71 | return rc; | |
72 | } | |
73 | ||
74 | /** | |
75 | * yama_ptracer_del - remove exceptions related to the given tasks | |
76 | * @tracer: remove any relation where tracer task matches | |
77 | * @tracee: remove any relation where tracee task matches | |
78 | */ | |
79 | static void yama_ptracer_del(struct task_struct *tracer, | |
80 | struct task_struct *tracee) | |
81 | { | |
82 | struct ptrace_relation *relation, *safe; | |
83 | ||
84 | spin_lock_bh(&ptracer_relations_lock); | |
85 | list_for_each_entry_safe(relation, safe, &ptracer_relations, node) | |
86 | if (relation->tracee == tracee || | |
87 | relation->tracer == tracer) { | |
88 | list_del(&relation->node); | |
89 | kfree(relation); | |
90 | } | |
91 | spin_unlock_bh(&ptracer_relations_lock); | |
92 | } | |
93 | ||
94 | /** | |
95 | * yama_task_free - check for task_pid to remove from exception list | |
96 | * @task: task being removed | |
97 | */ | |
98 | static void yama_task_free(struct task_struct *task) | |
99 | { | |
100 | yama_ptracer_del(task, task); | |
101 | } | |
102 | ||
103 | /** | |
104 | * yama_task_prctl - check for Yama-specific prctl operations | |
105 | * @option: operation | |
106 | * @arg2: argument | |
107 | * @arg3: argument | |
108 | * @arg4: argument | |
109 | * @arg5: argument | |
110 | * | |
111 | * Return 0 on success, -ve on error. -ENOSYS is returned when Yama | |
112 | * does not handle the given option. | |
113 | */ | |
114 | static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, | |
115 | unsigned long arg4, unsigned long arg5) | |
116 | { | |
117 | int rc; | |
118 | struct task_struct *myself = current; | |
119 | ||
120 | rc = cap_task_prctl(option, arg2, arg3, arg4, arg5); | |
121 | if (rc != -ENOSYS) | |
122 | return rc; | |
123 | ||
124 | switch (option) { | |
125 | case PR_SET_PTRACER: | |
126 | /* Since a thread can call prctl(), find the group leader | |
127 | * before calling _add() or _del() on it, since we want | |
128 | * process-level granularity of control. The tracer group | |
129 | * leader checking is handled later when walking the ancestry | |
130 | * at the time of PTRACE_ATTACH check. | |
131 | */ | |
132 | rcu_read_lock(); | |
133 | if (!thread_group_leader(myself)) | |
134 | myself = rcu_dereference(myself->group_leader); | |
135 | get_task_struct(myself); | |
136 | rcu_read_unlock(); | |
137 | ||
138 | if (arg2 == 0) { | |
139 | yama_ptracer_del(NULL, myself); | |
140 | rc = 0; | |
141 | } else { | |
142 | struct task_struct *tracer; | |
143 | ||
144 | rcu_read_lock(); | |
145 | tracer = find_task_by_vpid(arg2); | |
146 | if (tracer) | |
147 | get_task_struct(tracer); | |
148 | else | |
149 | rc = -EINVAL; | |
150 | rcu_read_unlock(); | |
151 | ||
152 | if (tracer) { | |
153 | rc = yama_ptracer_add(tracer, myself); | |
154 | put_task_struct(tracer); | |
155 | } | |
156 | } | |
157 | ||
158 | put_task_struct(myself); | |
159 | break; | |
160 | } | |
161 | ||
162 | return rc; | |
163 | } | |
164 | ||
165 | /** | |
166 | * task_is_descendant - walk up a process family tree looking for a match | |
167 | * @parent: the process to compare against while walking up from child | |
168 | * @child: the process to start from while looking upwards for parent | |
169 | * | |
170 | * Returns 1 if child is a descendant of parent, 0 if not. | |
171 | */ | |
172 | static int task_is_descendant(struct task_struct *parent, | |
173 | struct task_struct *child) | |
174 | { | |
175 | int rc = 0; | |
176 | struct task_struct *walker = child; | |
177 | ||
178 | if (!parent || !child) | |
179 | return 0; | |
180 | ||
181 | rcu_read_lock(); | |
182 | if (!thread_group_leader(parent)) | |
183 | parent = rcu_dereference(parent->group_leader); | |
184 | while (walker->pid > 0) { | |
185 | if (!thread_group_leader(walker)) | |
186 | walker = rcu_dereference(walker->group_leader); | |
187 | if (walker == parent) { | |
188 | rc = 1; | |
189 | break; | |
190 | } | |
191 | walker = rcu_dereference(walker->real_parent); | |
192 | } | |
193 | rcu_read_unlock(); | |
194 | ||
195 | return rc; | |
196 | } | |
197 | ||
198 | /** | |
199 | * ptracer_exception_found - tracer registered as exception for this tracee | |
200 | * @tracer: the task_struct of the process attempting ptrace | |
201 | * @tracee: the task_struct of the process to be ptraced | |
202 | * | |
203 | * Returns 1 if tracer has is ptracer exception ancestor for tracee. | |
204 | */ | |
205 | static int ptracer_exception_found(struct task_struct *tracer, | |
206 | struct task_struct *tracee) | |
207 | { | |
208 | int rc = 0; | |
209 | struct ptrace_relation *relation; | |
210 | struct task_struct *parent = NULL; | |
211 | ||
212 | spin_lock_bh(&ptracer_relations_lock); | |
213 | rcu_read_lock(); | |
214 | if (!thread_group_leader(tracee)) | |
215 | tracee = rcu_dereference(tracee->group_leader); | |
216 | list_for_each_entry(relation, &ptracer_relations, node) | |
217 | if (relation->tracee == tracee) { | |
218 | parent = relation->tracer; | |
219 | break; | |
220 | } | |
221 | ||
222 | if (task_is_descendant(parent, tracer)) | |
223 | rc = 1; | |
224 | rcu_read_unlock(); | |
225 | spin_unlock_bh(&ptracer_relations_lock); | |
226 | ||
227 | return rc; | |
228 | } | |
229 | ||
230 | /** | |
231 | * yama_ptrace_access_check - validate PTRACE_ATTACH calls | |
232 | * @child: task that current task is attempting to ptrace | |
233 | * @mode: ptrace attach mode | |
234 | * | |
235 | * Returns 0 if following the ptrace is allowed, -ve on error. | |
236 | */ | |
237 | static int yama_ptrace_access_check(struct task_struct *child, | |
238 | unsigned int mode) | |
239 | { | |
240 | int rc; | |
241 | ||
242 | /* If standard caps disallows it, so does Yama. We should | |
243 | * only tighten restrictions further. | |
244 | */ | |
245 | rc = cap_ptrace_access_check(child, mode); | |
246 | if (rc) | |
247 | return rc; | |
248 | ||
249 | /* require ptrace target be a child of ptracer on attach */ | |
250 | if (mode == PTRACE_MODE_ATTACH && | |
251 | ptrace_scope && | |
252 | !task_is_descendant(current, child) && | |
253 | !ptracer_exception_found(current, child) && | |
254 | !capable(CAP_SYS_PTRACE)) | |
255 | rc = -EPERM; | |
256 | ||
257 | if (rc) { | |
258 | char name[sizeof(current->comm)]; | |
259 | printk_ratelimited(KERN_NOTICE "ptrace of non-child" | |
260 | " pid %d was attempted by: %s (pid %d)\n", | |
261 | child->pid, | |
262 | get_task_comm(name, current), | |
263 | current->pid); | |
264 | } | |
265 | ||
266 | return rc; | |
267 | } | |
268 | ||
269 | static struct security_operations yama_ops = { | |
270 | .name = "yama", | |
271 | ||
272 | .ptrace_access_check = yama_ptrace_access_check, | |
273 | .task_prctl = yama_task_prctl, | |
274 | .task_free = yama_task_free, | |
275 | }; | |
276 | ||
277 | #ifdef CONFIG_SYSCTL | |
278 | static int zero; | |
279 | static int one = 1; | |
280 | ||
281 | struct ctl_path yama_sysctl_path[] = { | |
282 | { .procname = "kernel", }, | |
283 | { .procname = "yama", }, | |
284 | { } | |
285 | }; | |
286 | ||
287 | static struct ctl_table yama_sysctl_table[] = { | |
288 | { | |
289 | .procname = "ptrace_scope", | |
290 | .data = &ptrace_scope, | |
291 | .maxlen = sizeof(int), | |
292 | .mode = 0644, | |
293 | .proc_handler = proc_dointvec_minmax, | |
294 | .extra1 = &zero, | |
295 | .extra2 = &one, | |
296 | }, | |
297 | { } | |
298 | }; | |
299 | #endif /* CONFIG_SYSCTL */ | |
300 | ||
301 | static __init int yama_init(void) | |
302 | { | |
303 | if (!security_module_enable(&yama_ops)) | |
304 | return 0; | |
305 | ||
306 | printk(KERN_INFO "Yama: becoming mindful.\n"); | |
307 | ||
308 | if (register_security(&yama_ops)) | |
309 | panic("Yama: kernel registration failed.\n"); | |
310 | ||
311 | #ifdef CONFIG_SYSCTL | |
312 | if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table)) | |
313 | panic("Yama: sysctl registration failed.\n"); | |
314 | #endif | |
315 | ||
316 | return 0; | |
317 | } | |
318 | ||
319 | security_initcall(yama_init); |