xref: /freebsd/sys/compat/linux/linux_mib.c (revision d8a0fe102c0cfdfcd5b818f850eff09d8536c9bc)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1999 Marcel Moolenaar
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer
12  *    in this position and unchanged.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the author may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include <sys/param.h>
35 #include <sys/kernel.h>
36 #include <sys/sdt.h>
37 #include <sys/systm.h>
38 #include <sys/sysctl.h>
39 #include <sys/proc.h>
40 #include <sys/malloc.h>
41 #include <sys/mount.h>
42 #include <sys/jail.h>
43 #include <sys/lock.h>
44 #include <sys/sx.h>
45 
46 #include <compat/linux/linux_mib.h>
47 #include <compat/linux/linux_misc.h>
48 
49 struct linux_prison {
50 	char	pr_osname[LINUX_MAX_UTSNAME];
51 	char	pr_osrelease[LINUX_MAX_UTSNAME];
52 	int	pr_oss_version;
53 	int	pr_osrel;
54 };
55 
56 static struct linux_prison lprison0 = {
57 	.pr_osname =		"Linux",
58 	.pr_osrelease =		LINUX_VERSION_STR,
59 	.pr_oss_version =	0x030600,
60 	.pr_osrel =		LINUX_VERSION_CODE
61 };
62 
63 static unsigned linux_osd_jail_slot;
64 
65 SYSCTL_NODE(_compat, OID_AUTO, linux, CTLFLAG_RW, 0, "Linux mode");
66 
67 static int	linux_set_osname(struct thread *td, char *osname);
68 static int	linux_set_osrelease(struct thread *td, char *osrelease);
69 static int	linux_set_oss_version(struct thread *td, int oss_version);
70 
71 static int
72 linux_sysctl_osname(SYSCTL_HANDLER_ARGS)
73 {
74 	char osname[LINUX_MAX_UTSNAME];
75 	int error;
76 
77 	linux_get_osname(req->td, osname);
78 	error = sysctl_handle_string(oidp, osname, LINUX_MAX_UTSNAME, req);
79 	if (error != 0 || req->newptr == NULL)
80 		return (error);
81 	error = linux_set_osname(req->td, osname);
82 
83 	return (error);
84 }
85 
86 SYSCTL_PROC(_compat_linux, OID_AUTO, osname,
87 	    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE,
88 	    0, 0, linux_sysctl_osname, "A",
89 	    "Linux kernel OS name");
90 
91 static int
92 linux_sysctl_osrelease(SYSCTL_HANDLER_ARGS)
93 {
94 	char osrelease[LINUX_MAX_UTSNAME];
95 	int error;
96 
97 	linux_get_osrelease(req->td, osrelease);
98 	error = sysctl_handle_string(oidp, osrelease, LINUX_MAX_UTSNAME, req);
99 	if (error != 0 || req->newptr == NULL)
100 		return (error);
101 	error = linux_set_osrelease(req->td, osrelease);
102 
103 	return (error);
104 }
105 
106 SYSCTL_PROC(_compat_linux, OID_AUTO, osrelease,
107 	    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE,
108 	    0, 0, linux_sysctl_osrelease, "A",
109 	    "Linux kernel OS release");
110 
111 static int
112 linux_sysctl_oss_version(SYSCTL_HANDLER_ARGS)
113 {
114 	int oss_version;
115 	int error;
116 
117 	oss_version = linux_get_oss_version(req->td);
118 	error = sysctl_handle_int(oidp, &oss_version, 0, req);
119 	if (error != 0 || req->newptr == NULL)
120 		return (error);
121 	error = linux_set_oss_version(req->td, oss_version);
122 
123 	return (error);
124 }
125 
126 SYSCTL_PROC(_compat_linux, OID_AUTO, oss_version,
127 	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE,
128 	    0, 0, linux_sysctl_oss_version, "I",
129 	    "Linux OSS version");
130 
131 /*
132  * Map the osrelease into integer
133  */
134 static int
135 linux_map_osrel(char *osrelease, int *osrel)
136 {
137 	char *sep, *eosrelease;
138 	int len, v0, v1, v2, v;
139 
140 	len = strlen(osrelease);
141 	eosrelease = osrelease + len;
142 	v0 = strtol(osrelease, &sep, 10);
143 	if (osrelease == sep || sep + 1 >= eosrelease || *sep != '.')
144 		return (EINVAL);
145 	osrelease = sep + 1;
146 	v1 = strtol(osrelease, &sep, 10);
147 	if (osrelease == sep || sep + 1 >= eosrelease || *sep != '.')
148 		return (EINVAL);
149 	osrelease = sep + 1;
150 	v2 = strtol(osrelease, &sep, 10);
151 	if (osrelease == sep || sep != eosrelease)
152 		return (EINVAL);
153 
154 	v = v0 * 1000000 + v1 * 1000 + v2;
155 	if (v < 1000000)
156 		return (EINVAL);
157 
158 	if (osrel != NULL)
159 		*osrel = v;
160 
161 	return (0);
162 }
163 
164 /*
165  * Find a prison with Linux info.
166  * Return the Linux info and the (locked) prison.
167  */
168 static struct linux_prison *
169 linux_find_prison(struct prison *spr, struct prison **prp)
170 {
171 	struct prison *pr;
172 	struct linux_prison *lpr;
173 
174 	for (pr = spr;; pr = pr->pr_parent) {
175 		mtx_lock(&pr->pr_mtx);
176 		lpr = (pr == &prison0)
177 		    ? &lprison0
178 		    : osd_jail_get(pr, linux_osd_jail_slot);
179 		if (lpr != NULL)
180 			break;
181 		mtx_unlock(&pr->pr_mtx);
182 	}
183 	*prp = pr;
184 
185 	return (lpr);
186 }
187 
188 /*
189  * Ensure a prison has its own Linux info.  If lprp is non-null, point it to
190  * the Linux info and lock the prison.
191  */
192 static void
193 linux_alloc_prison(struct prison *pr, struct linux_prison **lprp)
194 {
195 	struct prison *ppr;
196 	struct linux_prison *lpr, *nlpr;
197 	void **rsv;
198 
199 	/* If this prison already has Linux info, return that. */
200 	lpr = linux_find_prison(pr, &ppr);
201 	if (ppr == pr)
202 		goto done;
203 	/*
204 	 * Allocate a new info record.  Then check again, in case something
205 	 * changed during the allocation.
206 	 */
207 	mtx_unlock(&ppr->pr_mtx);
208 	nlpr = malloc(sizeof(struct linux_prison), M_PRISON, M_WAITOK);
209 	rsv = osd_reserve(linux_osd_jail_slot);
210 	lpr = linux_find_prison(pr, &ppr);
211 	if (ppr == pr) {
212 		free(nlpr, M_PRISON);
213 		osd_free_reserved(rsv);
214 		goto done;
215 	}
216 	/* Inherit the initial values from the ancestor. */
217 	mtx_lock(&pr->pr_mtx);
218 	(void)osd_jail_set_reserved(pr, linux_osd_jail_slot, rsv, nlpr);
219 	bcopy(lpr, nlpr, sizeof(*lpr));
220 	lpr = nlpr;
221 	mtx_unlock(&ppr->pr_mtx);
222  done:
223 	if (lprp != NULL)
224 		*lprp = lpr;
225 	else
226 		mtx_unlock(&pr->pr_mtx);
227 }
228 
229 /*
230  * Jail OSD methods for Linux prison data.
231  */
232 static int
233 linux_prison_create(void *obj, void *data)
234 {
235 	struct prison *pr = obj;
236 	struct vfsoptlist *opts = data;
237 	int jsys;
238 
239 	if (vfs_copyopt(opts, "linux", &jsys, sizeof(jsys)) == 0 &&
240 	    jsys == JAIL_SYS_INHERIT)
241 		return (0);
242 	/*
243 	 * Inherit a prison's initial values from its parent
244 	 * (different from JAIL_SYS_INHERIT which also inherits changes).
245 	 */
246 	linux_alloc_prison(pr, NULL);
247 	return (0);
248 }
249 
250 static int
251 linux_prison_check(void *obj __unused, void *data)
252 {
253 	struct vfsoptlist *opts = data;
254 	char *osname, *osrelease;
255 	int error, jsys, len, oss_version;
256 
257 	/* Check that the parameters are correct. */
258 	error = vfs_copyopt(opts, "linux", &jsys, sizeof(jsys));
259 	if (error != ENOENT) {
260 		if (error != 0)
261 			return (error);
262 		if (jsys != JAIL_SYS_NEW && jsys != JAIL_SYS_INHERIT)
263 			return (EINVAL);
264 	}
265 	error = vfs_getopt(opts, "linux.osname", (void **)&osname, &len);
266 	if (error != ENOENT) {
267 		if (error != 0)
268 			return (error);
269 		if (len == 0 || osname[len - 1] != '\0')
270 			return (EINVAL);
271 		if (len > LINUX_MAX_UTSNAME) {
272 			vfs_opterror(opts, "linux.osname too long");
273 			return (ENAMETOOLONG);
274 		}
275 	}
276 	error = vfs_getopt(opts, "linux.osrelease", (void **)&osrelease, &len);
277 	if (error != ENOENT) {
278 		if (error != 0)
279 			return (error);
280 		if (len == 0 || osrelease[len - 1] != '\0')
281 			return (EINVAL);
282 		if (len > LINUX_MAX_UTSNAME) {
283 			vfs_opterror(opts, "linux.osrelease too long");
284 			return (ENAMETOOLONG);
285 		}
286 		error = linux_map_osrel(osrelease, NULL);
287 		if (error != 0) {
288 			vfs_opterror(opts, "linux.osrelease format error");
289 			return (error);
290 		}
291 	}
292 	error = vfs_copyopt(opts, "linux.oss_version", &oss_version,
293 	    sizeof(oss_version));
294 
295 	if (error == ENOENT)
296 		error = 0;
297 	return (error);
298 }
299 
300 static int
301 linux_prison_set(void *obj, void *data)
302 {
303 	struct linux_prison *lpr;
304 	struct prison *pr = obj;
305 	struct vfsoptlist *opts = data;
306 	char *osname, *osrelease;
307 	int error, gotversion, jsys, len, oss_version;
308 
309 	/* Set the parameters, which should be correct. */
310 	error = vfs_copyopt(opts, "linux", &jsys, sizeof(jsys));
311 	if (error == ENOENT)
312 		jsys = -1;
313 	error = vfs_getopt(opts, "linux.osname", (void **)&osname, &len);
314 	if (error == ENOENT)
315 		osname = NULL;
316 	else
317 		jsys = JAIL_SYS_NEW;
318 	error = vfs_getopt(opts, "linux.osrelease", (void **)&osrelease, &len);
319 	if (error == ENOENT)
320 		osrelease = NULL;
321 	else
322 		jsys = JAIL_SYS_NEW;
323 	error = vfs_copyopt(opts, "linux.oss_version", &oss_version,
324 	    sizeof(oss_version));
325 	if (error == ENOENT)
326 		gotversion = 0;
327 	else {
328 		gotversion = 1;
329 		jsys = JAIL_SYS_NEW;
330 	}
331 	switch (jsys) {
332 	case JAIL_SYS_INHERIT:
333 		/* "linux=inherit": inherit the parent's Linux info. */
334 		mtx_lock(&pr->pr_mtx);
335 		osd_jail_del(pr, linux_osd_jail_slot);
336 		mtx_unlock(&pr->pr_mtx);
337 		break;
338 	case JAIL_SYS_NEW:
339 		/*
340 		 * "linux=new" or "linux.*":
341 		 * the prison gets its own Linux info.
342 		 */
343 		linux_alloc_prison(pr, &lpr);
344 		if (osrelease) {
345 			(void)linux_map_osrel(osrelease, &lpr->pr_osrel);
346 			strlcpy(lpr->pr_osrelease, osrelease,
347 			    LINUX_MAX_UTSNAME);
348 		}
349 		if (osname)
350 			strlcpy(lpr->pr_osname, osname, LINUX_MAX_UTSNAME);
351 		if (gotversion)
352 			lpr->pr_oss_version = oss_version;
353 		mtx_unlock(&pr->pr_mtx);
354 	}
355 
356 	return (0);
357 }
358 
359 SYSCTL_JAIL_PARAM_SYS_NODE(linux, CTLFLAG_RW, "Jail Linux parameters");
360 SYSCTL_JAIL_PARAM_STRING(_linux, osname, CTLFLAG_RW, LINUX_MAX_UTSNAME,
361     "Jail Linux kernel OS name");
362 SYSCTL_JAIL_PARAM_STRING(_linux, osrelease, CTLFLAG_RW, LINUX_MAX_UTSNAME,
363     "Jail Linux kernel OS release");
364 SYSCTL_JAIL_PARAM(_linux, oss_version, CTLTYPE_INT | CTLFLAG_RW,
365     "I", "Jail Linux OSS version");
366 
367 static int
368 linux_prison_get(void *obj, void *data)
369 {
370 	struct linux_prison *lpr;
371 	struct prison *ppr;
372 	struct prison *pr = obj;
373 	struct vfsoptlist *opts = data;
374 	int error, i;
375 
376 	static int version0;
377 
378 	/* See if this prison is the one with the Linux info. */
379 	lpr = linux_find_prison(pr, &ppr);
380 	i = (ppr == pr) ? JAIL_SYS_NEW : JAIL_SYS_INHERIT;
381 	error = vfs_setopt(opts, "linux", &i, sizeof(i));
382 	if (error != 0 && error != ENOENT)
383 		goto done;
384 	if (i) {
385 		error = vfs_setopts(opts, "linux.osname", lpr->pr_osname);
386 		if (error != 0 && error != ENOENT)
387 			goto done;
388 		error = vfs_setopts(opts, "linux.osrelease", lpr->pr_osrelease);
389 		if (error != 0 && error != ENOENT)
390 			goto done;
391 		error = vfs_setopt(opts, "linux.oss_version",
392 		    &lpr->pr_oss_version, sizeof(lpr->pr_oss_version));
393 		if (error != 0 && error != ENOENT)
394 			goto done;
395 	} else {
396 		/*
397 		 * If this prison is inheriting its Linux info, report
398 		 * empty/zero parameters.
399 		 */
400 		error = vfs_setopts(opts, "linux.osname", "");
401 		if (error != 0 && error != ENOENT)
402 			goto done;
403 		error = vfs_setopts(opts, "linux.osrelease", "");
404 		if (error != 0 && error != ENOENT)
405 			goto done;
406 		error = vfs_setopt(opts, "linux.oss_version", &version0,
407 		    sizeof(lpr->pr_oss_version));
408 		if (error != 0 && error != ENOENT)
409 			goto done;
410 	}
411 	error = 0;
412 
413  done:
414 	mtx_unlock(&ppr->pr_mtx);
415 
416 	return (error);
417 }
418 
419 static void
420 linux_prison_destructor(void *data)
421 {
422 
423 	free(data, M_PRISON);
424 }
425 
426 void
427 linux_osd_jail_register(void)
428 {
429 	struct prison *pr;
430 	osd_method_t methods[PR_MAXMETHOD] = {
431 	    [PR_METHOD_CREATE] =	linux_prison_create,
432 	    [PR_METHOD_GET] =		linux_prison_get,
433 	    [PR_METHOD_SET] =		linux_prison_set,
434 	    [PR_METHOD_CHECK] =		linux_prison_check
435 	};
436 
437 	linux_osd_jail_slot =
438 	    osd_jail_register(linux_prison_destructor, methods);
439 	/* Copy the system linux info to any current prisons. */
440 	sx_slock(&allprison_lock);
441 	TAILQ_FOREACH(pr, &allprison, pr_list)
442 		linux_alloc_prison(pr, NULL);
443 	sx_sunlock(&allprison_lock);
444 }
445 
446 void
447 linux_osd_jail_deregister(void)
448 {
449 
450 	osd_jail_deregister(linux_osd_jail_slot);
451 }
452 
453 void
454 linux_get_osname(struct thread *td, char *dst)
455 {
456 	struct prison *pr;
457 	struct linux_prison *lpr;
458 
459 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
460 	bcopy(lpr->pr_osname, dst, LINUX_MAX_UTSNAME);
461 	mtx_unlock(&pr->pr_mtx);
462 }
463 
464 static int
465 linux_set_osname(struct thread *td, char *osname)
466 {
467 	struct prison *pr;
468 	struct linux_prison *lpr;
469 
470 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
471 	strlcpy(lpr->pr_osname, osname, LINUX_MAX_UTSNAME);
472 	mtx_unlock(&pr->pr_mtx);
473 
474 	return (0);
475 }
476 
477 void
478 linux_get_osrelease(struct thread *td, char *dst)
479 {
480 	struct prison *pr;
481 	struct linux_prison *lpr;
482 
483 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
484 	bcopy(lpr->pr_osrelease, dst, LINUX_MAX_UTSNAME);
485 	mtx_unlock(&pr->pr_mtx);
486 }
487 
488 int
489 linux_kernver(struct thread *td)
490 {
491 	struct prison *pr;
492 	struct linux_prison *lpr;
493 	int osrel;
494 
495 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
496 	osrel = lpr->pr_osrel;
497 	mtx_unlock(&pr->pr_mtx);
498 
499 	return (osrel);
500 }
501 
502 static int
503 linux_set_osrelease(struct thread *td, char *osrelease)
504 {
505 	struct prison *pr;
506 	struct linux_prison *lpr;
507 	int error;
508 
509 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
510 	error = linux_map_osrel(osrelease, &lpr->pr_osrel);
511 	if (error == 0)
512 		strlcpy(lpr->pr_osrelease, osrelease, LINUX_MAX_UTSNAME);
513 	mtx_unlock(&pr->pr_mtx);
514 
515 	return (error);
516 }
517 
518 int
519 linux_get_oss_version(struct thread *td)
520 {
521 	struct prison *pr;
522 	struct linux_prison *lpr;
523 	int version;
524 
525 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
526 	version = lpr->pr_oss_version;
527 	mtx_unlock(&pr->pr_mtx);
528 
529 	return (version);
530 }
531 
532 static int
533 linux_set_oss_version(struct thread *td, int oss_version)
534 {
535 	struct prison *pr;
536 	struct linux_prison *lpr;
537 
538 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
539 	lpr->pr_oss_version = oss_version;
540 	mtx_unlock(&pr->pr_mtx);
541 
542 	return (0);
543 }
544