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