yama_lsm.c 12.2 KB
Newer Older
K
Kees Cook 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * Yama Linux Security Module
 *
 * Author: Kees Cook <keescook@chromium.org>
 *
 * Copyright (C) 2010 Canonical, Ltd.
 * Copyright (C) 2011 The Chromium OS Authors.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2, as
 * published by the Free Software Foundation.
 *
 */

C
Casey Schaufler 已提交
15
#include <linux/lsm_hooks.h>
K
Kees Cook 已提交
16 17 18 19
#include <linux/sysctl.h>
#include <linux/ptrace.h>
#include <linux/prctl.h>
#include <linux/ratelimit.h>
K
Kees Cook 已提交
20
#include <linux/workqueue.h>
K
Kees Cook 已提交
21
#include <linux/string_helpers.h>
22 23 24
#include <linux/task_work.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
K
Kees Cook 已提交
25

K
Kees Cook 已提交
26 27 28 29 30 31
#define YAMA_SCOPE_DISABLED	0
#define YAMA_SCOPE_RELATIONAL	1
#define YAMA_SCOPE_CAPABILITY	2
#define YAMA_SCOPE_NO_ATTACH	3

static int ptrace_scope = YAMA_SCOPE_RELATIONAL;
K
Kees Cook 已提交
32 33 34 35 36

/* describe a ptrace relationship for potential exception */
struct ptrace_relation {
	struct task_struct *tracer;
	struct task_struct *tracee;
K
Kees Cook 已提交
37
	bool invalid;
K
Kees Cook 已提交
38
	struct list_head node;
K
Kees Cook 已提交
39
	struct rcu_head rcu;
K
Kees Cook 已提交
40 41 42 43 44
};

static LIST_HEAD(ptracer_relations);
static DEFINE_SPINLOCK(ptracer_relations_lock);

K
Kees Cook 已提交
45 46 47
static void yama_relation_cleanup(struct work_struct *work);
static DECLARE_WORK(yama_relation_work, yama_relation_cleanup);

48 49 50 51 52 53 54 55
struct access_report_info {
	struct callback_head work;
	const char *access;
	struct task_struct *target;
	struct task_struct *agent;
};

static void __report_access(struct callback_head *work)
K
Kees Cook 已提交
56
{
57 58
	struct access_report_info *info =
		container_of(work, struct access_report_info, work);
K
Kees Cook 已提交
59 60
	char *target_cmd, *agent_cmd;

61 62
	target_cmd = kstrdup_quotable_cmdline(info->target, GFP_KERNEL);
	agent_cmd = kstrdup_quotable_cmdline(info->agent, GFP_KERNEL);
K
Kees Cook 已提交
63 64 65

	pr_notice_ratelimited(
		"ptrace %s of \"%s\"[%d] was attempted by \"%s\"[%d]\n",
66 67
		info->access, target_cmd, info->target->pid, agent_cmd,
		info->agent->pid);
K
Kees Cook 已提交
68 69 70

	kfree(agent_cmd);
	kfree(target_cmd);
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112

	put_task_struct(info->agent);
	put_task_struct(info->target);
	kfree(info);
}

/* defers execution because cmdline access can sleep */
static void report_access(const char *access, struct task_struct *target,
				struct task_struct *agent)
{
	struct access_report_info *info;
	char agent_comm[sizeof(agent->comm)];

	assert_spin_locked(&target->alloc_lock); /* for target->comm */

	if (current->flags & PF_KTHREAD) {
		/* I don't think kthreads call task_work_run() before exiting.
		 * Imagine angry ranting about procfs here.
		 */
		pr_notice_ratelimited(
		    "ptrace %s of \"%s\"[%d] was attempted by \"%s\"[%d]\n",
		    access, target->comm, target->pid,
		    get_task_comm(agent_comm, agent), agent->pid);
		return;
	}

	info = kmalloc(sizeof(*info), GFP_ATOMIC);
	if (!info)
		return;
	init_task_work(&info->work, __report_access);
	get_task_struct(target);
	get_task_struct(agent);
	info->access = access;
	info->target = target;
	info->agent = agent;
	if (task_work_add(current, &info->work, true) == 0)
		return; /* success */

	WARN(1, "report_access called from exiting task");
	put_task_struct(target);
	put_task_struct(agent);
	kfree(info);
K
Kees Cook 已提交
113 114
}

K
Kees Cook 已提交
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
/**
 * yama_relation_cleanup - remove invalid entries from the relation list
 *
 */
static void yama_relation_cleanup(struct work_struct *work)
{
	struct ptrace_relation *relation;

	spin_lock(&ptracer_relations_lock);
	rcu_read_lock();
	list_for_each_entry_rcu(relation, &ptracer_relations, node) {
		if (relation->invalid) {
			list_del_rcu(&relation->node);
			kfree_rcu(relation, rcu);
		}
	}
	rcu_read_unlock();
	spin_unlock(&ptracer_relations_lock);
}

K
Kees Cook 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147
/**
 * yama_ptracer_add - add/replace an exception for this tracer/tracee pair
 * @tracer: the task_struct of the process doing the ptrace
 * @tracee: the task_struct of the process to be ptraced
 *
 * Each tracee can have, at most, one tracer registered. Each time this
 * is called, the prior registered tracer will be replaced for the tracee.
 *
 * Returns 0 if relationship was added, -ve on error.
 */
static int yama_ptracer_add(struct task_struct *tracer,
			    struct task_struct *tracee)
{
K
Kees Cook 已提交
148
	struct ptrace_relation *relation, *added;
K
Kees Cook 已提交
149 150 151 152 153

	added = kmalloc(sizeof(*added), GFP_KERNEL);
	if (!added)
		return -ENOMEM;

K
Kees Cook 已提交
154 155
	added->tracee = tracee;
	added->tracer = tracer;
K
Kees Cook 已提交
156
	added->invalid = false;
K
Kees Cook 已提交
157

K
Kees Cook 已提交
158
	spin_lock(&ptracer_relations_lock);
K
Kees Cook 已提交
159 160
	rcu_read_lock();
	list_for_each_entry_rcu(relation, &ptracer_relations, node) {
K
Kees Cook 已提交
161 162
		if (relation->invalid)
			continue;
K
Kees Cook 已提交
163 164 165 166
		if (relation->tracee == tracee) {
			list_replace_rcu(&relation->node, &added->node);
			kfree_rcu(relation, rcu);
			goto out;
K
Kees Cook 已提交
167 168 169
		}
	}

K
Kees Cook 已提交
170
	list_add_rcu(&added->node, &ptracer_relations);
K
Kees Cook 已提交
171

K
Kees Cook 已提交
172 173
out:
	rcu_read_unlock();
K
Kees Cook 已提交
174
	spin_unlock(&ptracer_relations_lock);
K
Kees Cook 已提交
175
	return 0;
K
Kees Cook 已提交
176 177 178 179 180 181 182 183 184 185
}

/**
 * yama_ptracer_del - remove exceptions related to the given tasks
 * @tracer: remove any relation where tracer task matches
 * @tracee: remove any relation where tracee task matches
 */
static void yama_ptracer_del(struct task_struct *tracer,
			     struct task_struct *tracee)
{
K
Kees Cook 已提交
186
	struct ptrace_relation *relation;
K
Kees Cook 已提交
187
	bool marked = false;
K
Kees Cook 已提交
188

K
Kees Cook 已提交
189 190
	rcu_read_lock();
	list_for_each_entry_rcu(relation, &ptracer_relations, node) {
K
Kees Cook 已提交
191 192
		if (relation->invalid)
			continue;
K
Kees Cook 已提交
193
		if (relation->tracee == tracee ||
K
Kees Cook 已提交
194
		    (tracer && relation->tracer == tracer)) {
K
Kees Cook 已提交
195 196
			relation->invalid = true;
			marked = true;
K
Kees Cook 已提交
197
		}
K
Kees Cook 已提交
198 199
	}
	rcu_read_unlock();
K
Kees Cook 已提交
200 201 202

	if (marked)
		schedule_work(&yama_relation_work);
K
Kees Cook 已提交
203 204 205 206 207 208
}

/**
 * yama_task_free - check for task_pid to remove from exception list
 * @task: task being removed
 */
J
Jann Horn 已提交
209
static void yama_task_free(struct task_struct *task)
K
Kees Cook 已提交
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
{
	yama_ptracer_del(task, task);
}

/**
 * yama_task_prctl - check for Yama-specific prctl operations
 * @option: operation
 * @arg2: argument
 * @arg3: argument
 * @arg4: argument
 * @arg5: argument
 *
 * Return 0 on success, -ve on error.  -ENOSYS is returned when Yama
 * does not handle the given option.
 */
J
Jann Horn 已提交
225
static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3,
K
Kees Cook 已提交
226 227
			   unsigned long arg4, unsigned long arg5)
{
C
Casey Schaufler 已提交
228
	int rc = -ENOSYS;
K
Kees Cook 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
	struct task_struct *myself = current;

	switch (option) {
	case PR_SET_PTRACER:
		/* Since a thread can call prctl(), find the group leader
		 * before calling _add() or _del() on it, since we want
		 * process-level granularity of control. The tracer group
		 * leader checking is handled later when walking the ancestry
		 * at the time of PTRACE_ATTACH check.
		 */
		rcu_read_lock();
		if (!thread_group_leader(myself))
			myself = rcu_dereference(myself->group_leader);
		get_task_struct(myself);
		rcu_read_unlock();

		if (arg2 == 0) {
			yama_ptracer_del(NULL, myself);
			rc = 0;
K
Kees Cook 已提交
248
		} else if (arg2 == PR_SET_PTRACER_ANY || (int)arg2 == -1) {
K
Kees Cook 已提交
249
			rc = yama_ptracer_add(NULL, myself);
K
Kees Cook 已提交
250 251 252
		} else {
			struct task_struct *tracer;

253 254
			tracer = find_get_task_by_vpid(arg2);
			if (!tracer) {
K
Kees Cook 已提交
255
				rc = -EINVAL;
256
			} else {
K
Kees Cook 已提交
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
				rc = yama_ptracer_add(tracer, myself);
				put_task_struct(tracer);
			}
		}

		put_task_struct(myself);
		break;
	}

	return rc;
}

/**
 * task_is_descendant - walk up a process family tree looking for a match
 * @parent: the process to compare against while walking up from child
 * @child: the process to start from while looking upwards for parent
 *
 * Returns 1 if child is a descendant of parent, 0 if not.
 */
static int task_is_descendant(struct task_struct *parent,
			      struct task_struct *child)
{
	int rc = 0;
	struct task_struct *walker = child;

	if (!parent || !child)
		return 0;

	rcu_read_lock();
	if (!thread_group_leader(parent))
		parent = rcu_dereference(parent->group_leader);
	while (walker->pid > 0) {
		if (!thread_group_leader(walker))
			walker = rcu_dereference(walker->group_leader);
		if (walker == parent) {
			rc = 1;
			break;
		}
		walker = rcu_dereference(walker->real_parent);
	}
	rcu_read_unlock();

	return rc;
}

/**
 * ptracer_exception_found - tracer registered as exception for this tracee
 * @tracer: the task_struct of the process attempting ptrace
 * @tracee: the task_struct of the process to be ptraced
 *
307
 * Returns 1 if tracer has a ptracer exception ancestor for tracee.
K
Kees Cook 已提交
308 309 310 311 312 313 314
 */
static int ptracer_exception_found(struct task_struct *tracer,
				   struct task_struct *tracee)
{
	int rc = 0;
	struct ptrace_relation *relation;
	struct task_struct *parent = NULL;
K
Kees Cook 已提交
315
	bool found = false;
K
Kees Cook 已提交
316 317

	rcu_read_lock();
318 319 320 321 322 323 324 325 326 327 328 329

	/*
	 * If there's already an active tracing relationship, then make an
	 * exception for the sake of other accesses, like process_vm_rw().
	 */
	parent = ptrace_parent(tracee);
	if (parent != NULL && same_thread_group(parent, tracer)) {
		rc = 1;
		goto unlock;
	}

	/* Look for a PR_SET_PTRACER relationship. */
K
Kees Cook 已提交
330 331
	if (!thread_group_leader(tracee))
		tracee = rcu_dereference(tracee->group_leader);
K
Kees Cook 已提交
332 333 334
	list_for_each_entry_rcu(relation, &ptracer_relations, node) {
		if (relation->invalid)
			continue;
K
Kees Cook 已提交
335 336
		if (relation->tracee == tracee) {
			parent = relation->tracer;
K
Kees Cook 已提交
337
			found = true;
K
Kees Cook 已提交
338 339
			break;
		}
K
Kees Cook 已提交
340
	}
K
Kees Cook 已提交
341

K
Kees Cook 已提交
342
	if (found && (parent == NULL || task_is_descendant(parent, tracer)))
K
Kees Cook 已提交
343
		rc = 1;
344 345

unlock:
K
Kees Cook 已提交
346 347 348 349 350 351 352 353 354 355 356 357
	rcu_read_unlock();

	return rc;
}

/**
 * yama_ptrace_access_check - validate PTRACE_ATTACH calls
 * @child: task that current task is attempting to ptrace
 * @mode: ptrace attach mode
 *
 * Returns 0 if following the ptrace is allowed, -ve on error.
 */
C
Casey Schaufler 已提交
358
static int yama_ptrace_access_check(struct task_struct *child,
K
Kees Cook 已提交
359 360
				    unsigned int mode)
{
C
Casey Schaufler 已提交
361
	int rc = 0;
K
Kees Cook 已提交
362 363

	/* require ptrace target be a child of ptracer on attach */
364
	if (mode & PTRACE_MODE_ATTACH) {
K
Kees Cook 已提交
365 366 367 368 369
		switch (ptrace_scope) {
		case YAMA_SCOPE_DISABLED:
			/* No additional restrictions. */
			break;
		case YAMA_SCOPE_RELATIONAL:
E
Eric W. Biederman 已提交
370
			rcu_read_lock();
371 372 373
			if (!pid_alive(child))
				rc = -EPERM;
			if (!rc && !task_is_descendant(current, child) &&
K
Kees Cook 已提交
374
			    !ptracer_exception_found(current, child) &&
E
Eric W. Biederman 已提交
375
			    !ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
K
Kees Cook 已提交
376
				rc = -EPERM;
E
Eric W. Biederman 已提交
377
			rcu_read_unlock();
K
Kees Cook 已提交
378 379
			break;
		case YAMA_SCOPE_CAPABILITY:
E
Eric W. Biederman 已提交
380 381
			rcu_read_lock();
			if (!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
K
Kees Cook 已提交
382
				rc = -EPERM;
E
Eric W. Biederman 已提交
383
			rcu_read_unlock();
K
Kees Cook 已提交
384 385 386 387 388 389 390
			break;
		case YAMA_SCOPE_NO_ATTACH:
		default:
			rc = -EPERM;
			break;
		}
	}
K
Kees Cook 已提交
391

K
Kees Cook 已提交
392 393
	if (rc && (mode & PTRACE_MODE_NOAUDIT) == 0)
		report_access("attach", child, current);
K
Kees Cook 已提交
394 395 396 397

	return rc;
}

398 399 400 401 402 403
/**
 * yama_ptrace_traceme - validate PTRACE_TRACEME calls
 * @parent: task that will become the ptracer of the current task
 *
 * Returns 0 if following the ptrace is allowed, -ve on error.
 */
J
Jann Horn 已提交
404
static int yama_ptrace_traceme(struct task_struct *parent)
405
{
C
Casey Schaufler 已提交
406
	int rc = 0;
407 408 409 410

	/* Only disallow PTRACE_TRACEME on more aggressive settings. */
	switch (ptrace_scope) {
	case YAMA_SCOPE_CAPABILITY:
411
		if (!has_ns_capability(parent, current_user_ns(), CAP_SYS_PTRACE))
412 413 414 415 416 417 418
			rc = -EPERM;
		break;
	case YAMA_SCOPE_NO_ATTACH:
		rc = -EPERM;
		break;
	}

419 420
	if (rc) {
		task_lock(current);
K
Kees Cook 已提交
421
		report_access("traceme", current, parent);
422 423
		task_unlock(current);
	}
424 425 426 427

	return rc;
}

428
static struct security_hook_list yama_hooks[] __lsm_ro_after_init = {
429 430 431 432
	LSM_HOOK_INIT(ptrace_access_check, yama_ptrace_access_check),
	LSM_HOOK_INIT(ptrace_traceme, yama_ptrace_traceme),
	LSM_HOOK_INIT(task_prctl, yama_task_prctl),
	LSM_HOOK_INIT(task_free, yama_task_free),
K
Kees Cook 已提交
433
};
C
Casey Schaufler 已提交
434

K
Kees Cook 已提交
435
#ifdef CONFIG_SYSCTL
K
Kees Cook 已提交
436 437 438
static int yama_dointvec_minmax(struct ctl_table *table, int write,
				void __user *buffer, size_t *lenp, loff_t *ppos)
{
439
	struct ctl_table table_copy;
K
Kees Cook 已提交
440 441 442 443 444

	if (write && !capable(CAP_SYS_PTRACE))
		return -EPERM;

	/* Lock the max value if it ever gets set. */
445 446 447
	table_copy = *table;
	if (*(int *)table_copy.data == *(int *)table_copy.extra2)
		table_copy.extra1 = table_copy.extra2;
K
Kees Cook 已提交
448

449
	return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos);
K
Kees Cook 已提交
450 451
}

K
Kees Cook 已提交
452
static int zero;
K
Kees Cook 已提交
453
static int max_scope = YAMA_SCOPE_NO_ATTACH;
K
Kees Cook 已提交
454

J
Jann Horn 已提交
455
static struct ctl_path yama_sysctl_path[] = {
K
Kees Cook 已提交
456 457 458 459 460 461 462 463 464 465 466
	{ .procname = "kernel", },
	{ .procname = "yama", },
	{ }
};

static struct ctl_table yama_sysctl_table[] = {
	{
		.procname       = "ptrace_scope",
		.data           = &ptrace_scope,
		.maxlen         = sizeof(int),
		.mode           = 0644,
K
Kees Cook 已提交
467
		.proc_handler   = yama_dointvec_minmax,
K
Kees Cook 已提交
468
		.extra1         = &zero,
K
Kees Cook 已提交
469
		.extra2         = &max_scope,
K
Kees Cook 已提交
470 471 472
	},
	{ }
};
473
static void __init yama_init_sysctl(void)
K
Kees Cook 已提交
474 475 476 477
{
	if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table))
		panic("Yama: sysctl registration failed.\n");
}
478 479 480
#else
static inline void yama_init_sysctl(void) { }
#endif /* CONFIG_SYSCTL */
K
Kees Cook 已提交
481

K
Kees Cook 已提交
482
static int __init yama_init(void)
483 484
{
	pr_info("Yama: becoming mindful.\n");
485
	security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks), "yama");
486
	yama_init_sysctl();
K
Kees Cook 已提交
487
	return 0;
488
}
K
Kees Cook 已提交
489 490 491 492 493

DEFINE_LSM(yama) = {
	.name = "yama",
	.init = yama_init,
};