summaryrefslogtreecommitdiff
path: root/kernel/ucount.c
blob: 6c2205c0befd00b4300ba1198bc46d41a3324f36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation, version 2 of the
 *  License.
 */

#include <linux/stat.h>
#include <linux/sysctl.h>
#include <linux/slab.h>
#include <linux/user_namespace.h>

#ifdef CONFIG_SYSCTL
static struct ctl_table_set *
set_lookup(struct ctl_table_root *root)
{
	return &current_user_ns()->set;
}

static int set_is_seen(struct ctl_table_set *set)
{
	return &current_user_ns()->set == set;
}

static int set_permissions(struct ctl_table_header *head,
				  struct ctl_table *table)
{
	struct user_namespace *user_ns =
		container_of(head->set, struct user_namespace, set);
	int mode;

	/* Allow users with CAP_SYS_RESOURCE unrestrained access */
	if (ns_capable(user_ns, CAP_SYS_RESOURCE))
		mode = (table->mode & S_IRWXU) >> 6;
	else
	/* Allow all others at most read-only access */
		mode = table->mode & S_IROTH;
	return (mode << 6) | (mode << 3) | mode;
}

static struct ctl_table_root set_root = {
	.lookup = set_lookup,
	.permissions = set_permissions,
};

static int zero = 0;
static int int_max = INT_MAX;
static struct ctl_table userns_table[] = {
	{
		.procname	= "max_user_namespaces",
		.data		= &init_user_ns.max_user_namespaces,
		.maxlen		= sizeof(init_user_ns.max_user_namespaces),
		.mode		= 0644,
		.proc_handler	= proc_dointvec_minmax,
		.extra1		= &zero,
		.extra2		= &int_max,
	},
	{ }
};
#endif /* CONFIG_SYSCTL */

bool setup_userns_sysctls(struct user_namespace *ns)
{
#ifdef CONFIG_SYSCTL
	struct ctl_table *tbl;
	setup_sysctl_set(&ns->set, &set_root, set_is_seen);
	tbl = kmemdup(userns_table, sizeof(userns_table), GFP_KERNEL);
	if (tbl) {
		tbl[0].data = &ns->max_user_namespaces;

		ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl);
	}
	if (!ns->sysctls) {
		kfree(tbl);
		retire_sysctl_set(&ns->set);
		return false;
	}
#endif
	return true;
}

void retire_userns_sysctls(struct user_namespace *ns)
{
#ifdef CONFIG_SYSCTL
	struct ctl_table *tbl;

	tbl = ns->sysctls->ctl_table_arg;
	unregister_sysctl_table(ns->sysctls);
	retire_sysctl_set(&ns->set);
	kfree(tbl);
#endif
}

static inline bool atomic_inc_below(atomic_t *v, int u)
{
	int c, old;
	c = atomic_read(v);
	for (;;) {
		if (unlikely(c >= u))
			return false;
		old = atomic_cmpxchg(v, c, c+1);
		if (likely(old == c))
			return true;
		c = old;
	}
}

bool inc_user_namespaces(struct user_namespace *ns)
{
	struct user_namespace *pos, *bad;
	for (pos = ns; pos; pos = pos->parent) {
		int max = READ_ONCE(pos->max_user_namespaces);
		if (!atomic_inc_below(&pos->user_namespaces, max))
			goto fail;
	}
	return true;
fail:
	bad = pos;
	for (pos = ns; pos != bad; pos = pos->parent)
		atomic_dec(&pos->user_namespaces);

	return false;
}

void dec_user_namespaces(struct user_namespace *ns)
{
	struct user_namespace *pos;
	for (pos = ns; pos; pos = pos->parent) {
		int dec = atomic_dec_if_positive(&pos->user_namespaces);
		WARN_ON_ONCE(dec < 0);
	}
}

static __init int user_namespace_sysctl_init(void)
{
#ifdef CONFIG_SYSCTL
	static struct ctl_table_header *userns_header;
	static struct ctl_table empty[1];
	/*
	 * It is necessary to register the userns directory in the
	 * default set so that registrations in the child sets work
	 * properly.
	 */
	userns_header = register_sysctl("userns", empty);
	BUG_ON(!userns_header);
	BUG_ON(!setup_userns_sysctls(&init_user_ns));
#endif
	return 0;
}
subsys_initcall(user_namespace_sysctl_init);