diff options
Diffstat (limited to 'security/yama')
-rw-r--r-- | security/yama/Kconfig | 13 | ||||
-rw-r--r-- | security/yama/Makefile | 3 | ||||
-rw-r--r-- | security/yama/yama_lsm.c | 323 |
3 files changed, 339 insertions, 0 deletions
diff --git a/security/yama/Kconfig b/security/yama/Kconfig new file mode 100644 index 000000000000..51d6709d8bbd --- /dev/null +++ b/security/yama/Kconfig | |||
@@ -0,0 +1,13 @@ | |||
1 | config SECURITY_YAMA | ||
2 | bool "Yama support" | ||
3 | depends on SECURITY | ||
4 | select SECURITYFS | ||
5 | select SECURITY_PATH | ||
6 | default n | ||
7 | help | ||
8 | This selects Yama, which extends DAC support with additional | ||
9 | system-wide security settings beyond regular Linux discretionary | ||
10 | access controls. Currently available is ptrace scope restriction. | ||
11 | Further information can be found in Documentation/security/Yama.txt. | ||
12 | |||
13 | If you are unsure how to answer this question, answer N. | ||
diff --git a/security/yama/Makefile b/security/yama/Makefile new file mode 100644 index 000000000000..8b5e06588456 --- /dev/null +++ b/security/yama/Makefile | |||
@@ -0,0 +1,3 @@ | |||
1 | obj-$(CONFIG_SECURITY_YAMA) := yama.o | ||
2 | |||
3 | yama-y := yama_lsm.o | ||
diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c new file mode 100644 index 000000000000..573723843a04 --- /dev/null +++ b/security/yama/yama_lsm.c | |||
@@ -0,0 +1,323 @@ | |||
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 | (tracer && 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 if (arg2 == PR_SET_PTRACER_ANY) { | ||
142 | rc = yama_ptracer_add(NULL, myself); | ||
143 | } else { | ||
144 | struct task_struct *tracer; | ||
145 | |||
146 | rcu_read_lock(); | ||
147 | tracer = find_task_by_vpid(arg2); | ||
148 | if (tracer) | ||
149 | get_task_struct(tracer); | ||
150 | else | ||
151 | rc = -EINVAL; | ||
152 | rcu_read_unlock(); | ||
153 | |||
154 | if (tracer) { | ||
155 | rc = yama_ptracer_add(tracer, myself); | ||
156 | put_task_struct(tracer); | ||
157 | } | ||
158 | } | ||
159 | |||
160 | put_task_struct(myself); | ||
161 | break; | ||
162 | } | ||
163 | |||
164 | return rc; | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * task_is_descendant - walk up a process family tree looking for a match | ||
169 | * @parent: the process to compare against while walking up from child | ||
170 | * @child: the process to start from while looking upwards for parent | ||
171 | * | ||
172 | * Returns 1 if child is a descendant of parent, 0 if not. | ||
173 | */ | ||
174 | static int task_is_descendant(struct task_struct *parent, | ||
175 | struct task_struct *child) | ||
176 | { | ||
177 | int rc = 0; | ||
178 | struct task_struct *walker = child; | ||
179 | |||
180 | if (!parent || !child) | ||
181 | return 0; | ||
182 | |||
183 | rcu_read_lock(); | ||
184 | if (!thread_group_leader(parent)) | ||
185 | parent = rcu_dereference(parent->group_leader); | ||
186 | while (walker->pid > 0) { | ||
187 | if (!thread_group_leader(walker)) | ||
188 | walker = rcu_dereference(walker->group_leader); | ||
189 | if (walker == parent) { | ||
190 | rc = 1; | ||
191 | break; | ||
192 | } | ||
193 | walker = rcu_dereference(walker->real_parent); | ||
194 | } | ||
195 | rcu_read_unlock(); | ||
196 | |||
197 | return rc; | ||
198 | } | ||
199 | |||
200 | /** | ||
201 | * ptracer_exception_found - tracer registered as exception for this tracee | ||
202 | * @tracer: the task_struct of the process attempting ptrace | ||
203 | * @tracee: the task_struct of the process to be ptraced | ||
204 | * | ||
205 | * Returns 1 if tracer has is ptracer exception ancestor for tracee. | ||
206 | */ | ||
207 | static int ptracer_exception_found(struct task_struct *tracer, | ||
208 | struct task_struct *tracee) | ||
209 | { | ||
210 | int rc = 0; | ||
211 | struct ptrace_relation *relation; | ||
212 | struct task_struct *parent = NULL; | ||
213 | bool found = false; | ||
214 | |||
215 | spin_lock_bh(&ptracer_relations_lock); | ||
216 | rcu_read_lock(); | ||
217 | if (!thread_group_leader(tracee)) | ||
218 | tracee = rcu_dereference(tracee->group_leader); | ||
219 | list_for_each_entry(relation, &ptracer_relations, node) | ||
220 | if (relation->tracee == tracee) { | ||
221 | parent = relation->tracer; | ||
222 | found = true; | ||
223 | break; | ||
224 | } | ||
225 | |||
226 | if (found && (parent == NULL || task_is_descendant(parent, tracer))) | ||
227 | rc = 1; | ||
228 | rcu_read_unlock(); | ||
229 | spin_unlock_bh(&ptracer_relations_lock); | ||
230 | |||
231 | return rc; | ||
232 | } | ||
233 | |||
234 | /** | ||
235 | * yama_ptrace_access_check - validate PTRACE_ATTACH calls | ||
236 | * @child: task that current task is attempting to ptrace | ||
237 | * @mode: ptrace attach mode | ||
238 | * | ||
239 | * Returns 0 if following the ptrace is allowed, -ve on error. | ||
240 | */ | ||
241 | static int yama_ptrace_access_check(struct task_struct *child, | ||
242 | unsigned int mode) | ||
243 | { | ||
244 | int rc; | ||
245 | |||
246 | /* If standard caps disallows it, so does Yama. We should | ||
247 | * only tighten restrictions further. | ||
248 | */ | ||
249 | rc = cap_ptrace_access_check(child, mode); | ||
250 | if (rc) | ||
251 | return rc; | ||
252 | |||
253 | /* require ptrace target be a child of ptracer on attach */ | ||
254 | if (mode == PTRACE_MODE_ATTACH && | ||
255 | ptrace_scope && | ||
256 | !task_is_descendant(current, child) && | ||
257 | !ptracer_exception_found(current, child) && | ||
258 | !capable(CAP_SYS_PTRACE)) | ||
259 | rc = -EPERM; | ||
260 | |||
261 | if (rc) { | ||
262 | char name[sizeof(current->comm)]; | ||
263 | printk_ratelimited(KERN_NOTICE "ptrace of non-child" | ||
264 | " pid %d was attempted by: %s (pid %d)\n", | ||
265 | child->pid, | ||
266 | get_task_comm(name, current), | ||
267 | current->pid); | ||
268 | } | ||
269 | |||
270 | return rc; | ||
271 | } | ||
272 | |||
273 | static struct security_operations yama_ops = { | ||
274 | .name = "yama", | ||
275 | |||
276 | .ptrace_access_check = yama_ptrace_access_check, | ||
277 | .task_prctl = yama_task_prctl, | ||
278 | .task_free = yama_task_free, | ||
279 | }; | ||
280 | |||
281 | #ifdef CONFIG_SYSCTL | ||
282 | static int zero; | ||
283 | static int one = 1; | ||
284 | |||
285 | struct ctl_path yama_sysctl_path[] = { | ||
286 | { .procname = "kernel", }, | ||
287 | { .procname = "yama", }, | ||
288 | { } | ||
289 | }; | ||
290 | |||
291 | static struct ctl_table yama_sysctl_table[] = { | ||
292 | { | ||
293 | .procname = "ptrace_scope", | ||
294 | .data = &ptrace_scope, | ||
295 | .maxlen = sizeof(int), | ||
296 | .mode = 0644, | ||
297 | .proc_handler = proc_dointvec_minmax, | ||
298 | .extra1 = &zero, | ||
299 | .extra2 = &one, | ||
300 | }, | ||
301 | { } | ||
302 | }; | ||
303 | #endif /* CONFIG_SYSCTL */ | ||
304 | |||
305 | static __init int yama_init(void) | ||
306 | { | ||
307 | if (!security_module_enable(&yama_ops)) | ||
308 | return 0; | ||
309 | |||
310 | printk(KERN_INFO "Yama: becoming mindful.\n"); | ||
311 | |||
312 | if (register_security(&yama_ops)) | ||
313 | panic("Yama: kernel registration failed.\n"); | ||
314 | |||
315 | #ifdef CONFIG_SYSCTL | ||
316 | if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table)) | ||
317 | panic("Yama: sysctl registration failed.\n"); | ||
318 | #endif | ||
319 | |||
320 | return 0; | ||
321 | } | ||
322 | |||
323 | security_initcall(yama_init); | ||