1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2007 |
4 | * |
5 | * Author: Eric Biederman <ebiederm@xmision.com> |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/ipc.h> |
10 | #include <linux/nsproxy.h> |
11 | #include <linux/sysctl.h> |
12 | #include <linux/uaccess.h> |
13 | #include <linux/capability.h> |
14 | #include <linux/ipc_namespace.h> |
15 | #include <linux/msg.h> |
16 | #include <linux/slab.h> |
17 | #include <linux/cred.h> |
18 | #include "util.h" |
19 | |
20 | static int proc_ipc_dointvec_minmax_orphans(struct ctl_table *table, int write, |
21 | void *buffer, size_t *lenp, loff_t *ppos) |
22 | { |
23 | struct ipc_namespace *ns = |
24 | container_of(table->data, struct ipc_namespace, shm_rmid_forced); |
25 | int err; |
26 | |
27 | err = proc_dointvec_minmax(table, write, buffer, lenp, ppos); |
28 | |
29 | if (err < 0) |
30 | return err; |
31 | if (ns->shm_rmid_forced) |
32 | shm_destroy_orphaned(ns); |
33 | return err; |
34 | } |
35 | |
36 | static int proc_ipc_auto_msgmni(struct ctl_table *table, int write, |
37 | void *buffer, size_t *lenp, loff_t *ppos) |
38 | { |
39 | struct ctl_table ipc_table; |
40 | int dummy = 0; |
41 | |
42 | memcpy(&ipc_table, table, sizeof(ipc_table)); |
43 | ipc_table.data = &dummy; |
44 | |
45 | if (write) |
46 | pr_info_once("writing to auto_msgmni has no effect" ); |
47 | |
48 | return proc_dointvec_minmax(&ipc_table, write, buffer, lenp, ppos); |
49 | } |
50 | |
51 | static int proc_ipc_sem_dointvec(struct ctl_table *table, int write, |
52 | void *buffer, size_t *lenp, loff_t *ppos) |
53 | { |
54 | struct ipc_namespace *ns = |
55 | container_of(table->data, struct ipc_namespace, sem_ctls); |
56 | int ret, semmni; |
57 | |
58 | semmni = ns->sem_ctls[3]; |
59 | ret = proc_dointvec(table, write, buffer, lenp, ppos); |
60 | |
61 | if (!ret) |
62 | ret = sem_check_semmni(ns); |
63 | |
64 | /* |
65 | * Reset the semmni value if an error happens. |
66 | */ |
67 | if (ret) |
68 | ns->sem_ctls[3] = semmni; |
69 | return ret; |
70 | } |
71 | |
72 | int ipc_mni = IPCMNI; |
73 | int ipc_mni_shift = IPCMNI_SHIFT; |
74 | int ipc_min_cycle = RADIX_TREE_MAP_SIZE; |
75 | |
76 | static struct ctl_table ipc_sysctls[] = { |
77 | { |
78 | .procname = "shmmax" , |
79 | .data = &init_ipc_ns.shm_ctlmax, |
80 | .maxlen = sizeof(init_ipc_ns.shm_ctlmax), |
81 | .mode = 0644, |
82 | .proc_handler = proc_doulongvec_minmax, |
83 | }, |
84 | { |
85 | .procname = "shmall" , |
86 | .data = &init_ipc_ns.shm_ctlall, |
87 | .maxlen = sizeof(init_ipc_ns.shm_ctlall), |
88 | .mode = 0644, |
89 | .proc_handler = proc_doulongvec_minmax, |
90 | }, |
91 | { |
92 | .procname = "shmmni" , |
93 | .data = &init_ipc_ns.shm_ctlmni, |
94 | .maxlen = sizeof(init_ipc_ns.shm_ctlmni), |
95 | .mode = 0644, |
96 | .proc_handler = proc_dointvec_minmax, |
97 | .extra1 = SYSCTL_ZERO, |
98 | .extra2 = &ipc_mni, |
99 | }, |
100 | { |
101 | .procname = "shm_rmid_forced" , |
102 | .data = &init_ipc_ns.shm_rmid_forced, |
103 | .maxlen = sizeof(init_ipc_ns.shm_rmid_forced), |
104 | .mode = 0644, |
105 | .proc_handler = proc_ipc_dointvec_minmax_orphans, |
106 | .extra1 = SYSCTL_ZERO, |
107 | .extra2 = SYSCTL_ONE, |
108 | }, |
109 | { |
110 | .procname = "msgmax" , |
111 | .data = &init_ipc_ns.msg_ctlmax, |
112 | .maxlen = sizeof(init_ipc_ns.msg_ctlmax), |
113 | .mode = 0644, |
114 | .proc_handler = proc_dointvec_minmax, |
115 | .extra1 = SYSCTL_ZERO, |
116 | .extra2 = SYSCTL_INT_MAX, |
117 | }, |
118 | { |
119 | .procname = "msgmni" , |
120 | .data = &init_ipc_ns.msg_ctlmni, |
121 | .maxlen = sizeof(init_ipc_ns.msg_ctlmni), |
122 | .mode = 0644, |
123 | .proc_handler = proc_dointvec_minmax, |
124 | .extra1 = SYSCTL_ZERO, |
125 | .extra2 = &ipc_mni, |
126 | }, |
127 | { |
128 | .procname = "auto_msgmni" , |
129 | .data = NULL, |
130 | .maxlen = sizeof(int), |
131 | .mode = 0644, |
132 | .proc_handler = proc_ipc_auto_msgmni, |
133 | .extra1 = SYSCTL_ZERO, |
134 | .extra2 = SYSCTL_ONE, |
135 | }, |
136 | { |
137 | .procname = "msgmnb" , |
138 | .data = &init_ipc_ns.msg_ctlmnb, |
139 | .maxlen = sizeof(init_ipc_ns.msg_ctlmnb), |
140 | .mode = 0644, |
141 | .proc_handler = proc_dointvec_minmax, |
142 | .extra1 = SYSCTL_ZERO, |
143 | .extra2 = SYSCTL_INT_MAX, |
144 | }, |
145 | { |
146 | .procname = "sem" , |
147 | .data = &init_ipc_ns.sem_ctls, |
148 | .maxlen = 4*sizeof(int), |
149 | .mode = 0644, |
150 | .proc_handler = proc_ipc_sem_dointvec, |
151 | }, |
152 | #ifdef CONFIG_CHECKPOINT_RESTORE |
153 | { |
154 | .procname = "sem_next_id" , |
155 | .data = &init_ipc_ns.ids[IPC_SEM_IDS].next_id, |
156 | .maxlen = sizeof(init_ipc_ns.ids[IPC_SEM_IDS].next_id), |
157 | .mode = 0444, |
158 | .proc_handler = proc_dointvec_minmax, |
159 | .extra1 = SYSCTL_ZERO, |
160 | .extra2 = SYSCTL_INT_MAX, |
161 | }, |
162 | { |
163 | .procname = "msg_next_id" , |
164 | .data = &init_ipc_ns.ids[IPC_MSG_IDS].next_id, |
165 | .maxlen = sizeof(init_ipc_ns.ids[IPC_MSG_IDS].next_id), |
166 | .mode = 0444, |
167 | .proc_handler = proc_dointvec_minmax, |
168 | .extra1 = SYSCTL_ZERO, |
169 | .extra2 = SYSCTL_INT_MAX, |
170 | }, |
171 | { |
172 | .procname = "shm_next_id" , |
173 | .data = &init_ipc_ns.ids[IPC_SHM_IDS].next_id, |
174 | .maxlen = sizeof(init_ipc_ns.ids[IPC_SHM_IDS].next_id), |
175 | .mode = 0444, |
176 | .proc_handler = proc_dointvec_minmax, |
177 | .extra1 = SYSCTL_ZERO, |
178 | .extra2 = SYSCTL_INT_MAX, |
179 | }, |
180 | #endif |
181 | {} |
182 | }; |
183 | |
184 | static struct ctl_table_set *set_lookup(struct ctl_table_root *root) |
185 | { |
186 | return ¤t->nsproxy->ipc_ns->ipc_set; |
187 | } |
188 | |
189 | static int set_is_seen(struct ctl_table_set *set) |
190 | { |
191 | return ¤t->nsproxy->ipc_ns->ipc_set == set; |
192 | } |
193 | |
194 | static void ipc_set_ownership(struct ctl_table_header *head, |
195 | struct ctl_table *table, |
196 | kuid_t *uid, kgid_t *gid) |
197 | { |
198 | struct ipc_namespace *ns = |
199 | container_of(head->set, struct ipc_namespace, ipc_set); |
200 | |
201 | kuid_t ns_root_uid = make_kuid(from: ns->user_ns, uid: 0); |
202 | kgid_t ns_root_gid = make_kgid(from: ns->user_ns, gid: 0); |
203 | |
204 | *uid = uid_valid(uid: ns_root_uid) ? ns_root_uid : GLOBAL_ROOT_UID; |
205 | *gid = gid_valid(ns_root_gid) ? ns_root_gid : GLOBAL_ROOT_GID; |
206 | } |
207 | |
208 | static int ipc_permissions(struct ctl_table_header *head, struct ctl_table *table) |
209 | { |
210 | int mode = table->mode; |
211 | |
212 | #ifdef CONFIG_CHECKPOINT_RESTORE |
213 | struct ipc_namespace *ns = |
214 | container_of(head->set, struct ipc_namespace, ipc_set); |
215 | |
216 | if (((table->data == &ns->ids[IPC_SEM_IDS].next_id) || |
217 | (table->data == &ns->ids[IPC_MSG_IDS].next_id) || |
218 | (table->data == &ns->ids[IPC_SHM_IDS].next_id)) && |
219 | checkpoint_restore_ns_capable(ns: ns->user_ns)) |
220 | mode = 0666; |
221 | else |
222 | #endif |
223 | { |
224 | kuid_t ns_root_uid; |
225 | kgid_t ns_root_gid; |
226 | |
227 | ipc_set_ownership(head, table, uid: &ns_root_uid, gid: &ns_root_gid); |
228 | |
229 | if (uid_eq(current_euid(), right: ns_root_uid)) |
230 | mode >>= 6; |
231 | |
232 | else if (in_egroup_p(ns_root_gid)) |
233 | mode >>= 3; |
234 | } |
235 | |
236 | mode &= 7; |
237 | |
238 | return (mode << 6) | (mode << 3) | mode; |
239 | } |
240 | |
241 | static struct ctl_table_root set_root = { |
242 | .lookup = set_lookup, |
243 | .permissions = ipc_permissions, |
244 | .set_ownership = ipc_set_ownership, |
245 | }; |
246 | |
247 | bool setup_ipc_sysctls(struct ipc_namespace *ns) |
248 | { |
249 | struct ctl_table *tbl; |
250 | |
251 | setup_sysctl_set(p: &ns->ipc_set, root: &set_root, is_seen: set_is_seen); |
252 | |
253 | tbl = kmemdup(p: ipc_sysctls, size: sizeof(ipc_sysctls), GFP_KERNEL); |
254 | if (tbl) { |
255 | int i; |
256 | |
257 | for (i = 0; i < ARRAY_SIZE(ipc_sysctls); i++) { |
258 | if (tbl[i].data == &init_ipc_ns.shm_ctlmax) |
259 | tbl[i].data = &ns->shm_ctlmax; |
260 | |
261 | else if (tbl[i].data == &init_ipc_ns.shm_ctlall) |
262 | tbl[i].data = &ns->shm_ctlall; |
263 | |
264 | else if (tbl[i].data == &init_ipc_ns.shm_ctlmni) |
265 | tbl[i].data = &ns->shm_ctlmni; |
266 | |
267 | else if (tbl[i].data == &init_ipc_ns.shm_rmid_forced) |
268 | tbl[i].data = &ns->shm_rmid_forced; |
269 | |
270 | else if (tbl[i].data == &init_ipc_ns.msg_ctlmax) |
271 | tbl[i].data = &ns->msg_ctlmax; |
272 | |
273 | else if (tbl[i].data == &init_ipc_ns.msg_ctlmni) |
274 | tbl[i].data = &ns->msg_ctlmni; |
275 | |
276 | else if (tbl[i].data == &init_ipc_ns.msg_ctlmnb) |
277 | tbl[i].data = &ns->msg_ctlmnb; |
278 | |
279 | else if (tbl[i].data == &init_ipc_ns.sem_ctls) |
280 | tbl[i].data = &ns->sem_ctls; |
281 | #ifdef CONFIG_CHECKPOINT_RESTORE |
282 | else if (tbl[i].data == &init_ipc_ns.ids[IPC_SEM_IDS].next_id) |
283 | tbl[i].data = &ns->ids[IPC_SEM_IDS].next_id; |
284 | |
285 | else if (tbl[i].data == &init_ipc_ns.ids[IPC_MSG_IDS].next_id) |
286 | tbl[i].data = &ns->ids[IPC_MSG_IDS].next_id; |
287 | |
288 | else if (tbl[i].data == &init_ipc_ns.ids[IPC_SHM_IDS].next_id) |
289 | tbl[i].data = &ns->ids[IPC_SHM_IDS].next_id; |
290 | #endif |
291 | else |
292 | tbl[i].data = NULL; |
293 | } |
294 | |
295 | ns->ipc_sysctls = __register_sysctl_table(set: &ns->ipc_set, path: "kernel" , table: tbl, |
296 | ARRAY_SIZE(ipc_sysctls)); |
297 | } |
298 | if (!ns->ipc_sysctls) { |
299 | kfree(objp: tbl); |
300 | retire_sysctl_set(set: &ns->ipc_set); |
301 | return false; |
302 | } |
303 | |
304 | return true; |
305 | } |
306 | |
307 | void retire_ipc_sysctls(struct ipc_namespace *ns) |
308 | { |
309 | struct ctl_table *tbl; |
310 | |
311 | tbl = ns->ipc_sysctls->ctl_table_arg; |
312 | unregister_sysctl_table(table: ns->ipc_sysctls); |
313 | retire_sysctl_set(set: &ns->ipc_set); |
314 | kfree(objp: tbl); |
315 | } |
316 | |
317 | static int __init ipc_sysctl_init(void) |
318 | { |
319 | if (!setup_ipc_sysctls(&init_ipc_ns)) { |
320 | pr_warn("ipc sysctl registration failed\n" ); |
321 | return -ENOMEM; |
322 | } |
323 | return 0; |
324 | } |
325 | |
326 | device_initcall(ipc_sysctl_init); |
327 | |
328 | static int __init ipc_mni_extend(char *str) |
329 | { |
330 | ipc_mni = IPCMNI_EXTEND; |
331 | ipc_mni_shift = IPCMNI_EXTEND_SHIFT; |
332 | ipc_min_cycle = IPCMNI_EXTEND_MIN_CYCLE; |
333 | pr_info("IPCMNI extended to %d.\n" , ipc_mni); |
334 | return 0; |
335 | } |
336 | early_param("ipcmni_extend" , ipc_mni_extend); |
337 | |