xref: /freebsd/sys/compat/linux/linux_mib.c (revision 2d4e511ca269f1908d27f4e5779c53475527391d)
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$");
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 | CTLFLAG_MPSAFE, 0,
64     "Linux mode");
65 
66 int linux_ignore_ip_recverr = 1;
67 SYSCTL_INT(_compat_linux, OID_AUTO, ignore_ip_recverr, CTLFLAG_RWTUN,
68     &linux_ignore_ip_recverr, 0, "Ignore enabling IP_RECVERR");
69 
70 int linux_preserve_vstatus = 0;
71 SYSCTL_INT(_compat_linux, OID_AUTO, preserve_vstatus, CTLFLAG_RWTUN,
72     &linux_preserve_vstatus, 0, "Preserve VSTATUS termios(4) flag");
73 
74 static int	linux_set_osname(struct thread *td, char *osname);
75 static int	linux_set_osrelease(struct thread *td, char *osrelease);
76 static int	linux_set_oss_version(struct thread *td, int oss_version);
77 
78 static int
79 linux_sysctl_osname(SYSCTL_HANDLER_ARGS)
80 {
81 	char osname[LINUX_MAX_UTSNAME];
82 	int error;
83 
84 	linux_get_osname(req->td, osname);
85 	error = sysctl_handle_string(oidp, osname, LINUX_MAX_UTSNAME, req);
86 	if (error != 0 || req->newptr == NULL)
87 		return (error);
88 	error = linux_set_osname(req->td, osname);
89 
90 	return (error);
91 }
92 
93 SYSCTL_PROC(_compat_linux, OID_AUTO, osname,
94 	    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE,
95 	    0, 0, linux_sysctl_osname, "A",
96 	    "Linux kernel OS name");
97 
98 static int
99 linux_sysctl_osrelease(SYSCTL_HANDLER_ARGS)
100 {
101 	char osrelease[LINUX_MAX_UTSNAME];
102 	int error;
103 
104 	linux_get_osrelease(req->td, osrelease);
105 	error = sysctl_handle_string(oidp, osrelease, LINUX_MAX_UTSNAME, req);
106 	if (error != 0 || req->newptr == NULL)
107 		return (error);
108 	error = linux_set_osrelease(req->td, osrelease);
109 
110 	return (error);
111 }
112 
113 SYSCTL_PROC(_compat_linux, OID_AUTO, osrelease,
114 	    CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE,
115 	    0, 0, linux_sysctl_osrelease, "A",
116 	    "Linux kernel OS release");
117 
118 static int
119 linux_sysctl_oss_version(SYSCTL_HANDLER_ARGS)
120 {
121 	int oss_version;
122 	int error;
123 
124 	oss_version = linux_get_oss_version(req->td);
125 	error = sysctl_handle_int(oidp, &oss_version, 0, req);
126 	if (error != 0 || req->newptr == NULL)
127 		return (error);
128 	error = linux_set_oss_version(req->td, oss_version);
129 
130 	return (error);
131 }
132 
133 SYSCTL_PROC(_compat_linux, OID_AUTO, oss_version,
134 	    CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE,
135 	    0, 0, linux_sysctl_oss_version, "I",
136 	    "Linux OSS version");
137 
138 /*
139  * Map the osrelease into integer
140  */
141 static int
142 linux_map_osrel(char *osrelease, int *osrel)
143 {
144 	char *sep, *eosrelease;
145 	int len, v0, v1, v2, v;
146 
147 	len = strlen(osrelease);
148 	eosrelease = osrelease + len;
149 	v0 = strtol(osrelease, &sep, 10);
150 	if (osrelease == sep || sep + 1 >= eosrelease || *sep != '.')
151 		return (EINVAL);
152 	osrelease = sep + 1;
153 	v1 = strtol(osrelease, &sep, 10);
154 	if (osrelease == sep || sep + 1 >= eosrelease || *sep != '.')
155 		return (EINVAL);
156 	osrelease = sep + 1;
157 	v2 = strtol(osrelease, &sep, 10);
158 	if (osrelease == sep ||
159 	    (sep != eosrelease && (sep + 1 >= eosrelease || *sep != '-')))
160 		return (EINVAL);
161 
162 	v = LINUX_KERNVER(v0, v1, v2);
163 	if (v < LINUX_KERNVER(1, 0, 0))
164 		return (EINVAL);
165 
166 	if (osrel != NULL)
167 		*osrel = v;
168 
169 	return (0);
170 }
171 
172 /*
173  * Find a prison with Linux info.
174  * Return the Linux info and the (locked) prison.
175  */
176 static struct linux_prison *
177 linux_find_prison(struct prison *spr, struct prison **prp)
178 {
179 	struct prison *pr;
180 	struct linux_prison *lpr;
181 
182 	for (pr = spr;; pr = pr->pr_parent) {
183 		mtx_lock(&pr->pr_mtx);
184 		lpr = (pr == &prison0)
185 		    ? &lprison0
186 		    : osd_jail_get(pr, linux_osd_jail_slot);
187 		if (lpr != NULL)
188 			break;
189 		mtx_unlock(&pr->pr_mtx);
190 	}
191 	*prp = pr;
192 
193 	return (lpr);
194 }
195 
196 /*
197  * Ensure a prison has its own Linux info.  If lprp is non-null, point it to
198  * the Linux info and lock the prison.
199  */
200 static void
201 linux_alloc_prison(struct prison *pr, struct linux_prison **lprp)
202 {
203 	struct prison *ppr;
204 	struct linux_prison *lpr, *nlpr;
205 	void **rsv;
206 
207 	/* If this prison already has Linux info, return that. */
208 	lpr = linux_find_prison(pr, &ppr);
209 	if (ppr == pr)
210 		goto done;
211 	/*
212 	 * Allocate a new info record.  Then check again, in case something
213 	 * changed during the allocation.
214 	 */
215 	mtx_unlock(&ppr->pr_mtx);
216 	nlpr = malloc(sizeof(struct linux_prison), M_PRISON, M_WAITOK);
217 	rsv = osd_reserve(linux_osd_jail_slot);
218 	lpr = linux_find_prison(pr, &ppr);
219 	if (ppr == pr) {
220 		free(nlpr, M_PRISON);
221 		osd_free_reserved(rsv);
222 		goto done;
223 	}
224 	/* Inherit the initial values from the ancestor. */
225 	mtx_lock(&pr->pr_mtx);
226 	(void)osd_jail_set_reserved(pr, linux_osd_jail_slot, rsv, nlpr);
227 	bcopy(lpr, nlpr, sizeof(*lpr));
228 	lpr = nlpr;
229 	mtx_unlock(&ppr->pr_mtx);
230  done:
231 	if (lprp != NULL)
232 		*lprp = lpr;
233 	else
234 		mtx_unlock(&pr->pr_mtx);
235 }
236 
237 /*
238  * Jail OSD methods for Linux prison data.
239  */
240 static int
241 linux_prison_create(void *obj, void *data)
242 {
243 	struct prison *pr = obj;
244 	struct vfsoptlist *opts = data;
245 	int jsys;
246 
247 	if (vfs_copyopt(opts, "linux", &jsys, sizeof(jsys)) == 0 &&
248 	    jsys == JAIL_SYS_INHERIT)
249 		return (0);
250 	/*
251 	 * Inherit a prison's initial values from its parent
252 	 * (different from JAIL_SYS_INHERIT which also inherits changes).
253 	 */
254 	linux_alloc_prison(pr, NULL);
255 	return (0);
256 }
257 
258 static int
259 linux_prison_check(void *obj __unused, void *data)
260 {
261 	struct vfsoptlist *opts = data;
262 	char *osname, *osrelease;
263 	int error, jsys, len, oss_version;
264 
265 	/* Check that the parameters are correct. */
266 	error = vfs_copyopt(opts, "linux", &jsys, sizeof(jsys));
267 	if (error != ENOENT) {
268 		if (error != 0)
269 			return (error);
270 		if (jsys != JAIL_SYS_NEW && jsys != JAIL_SYS_INHERIT)
271 			return (EINVAL);
272 	}
273 	error = vfs_getopt(opts, "linux.osname", (void **)&osname, &len);
274 	if (error != ENOENT) {
275 		if (error != 0)
276 			return (error);
277 		if (len == 0 || osname[len - 1] != '\0')
278 			return (EINVAL);
279 		if (len > LINUX_MAX_UTSNAME) {
280 			vfs_opterror(opts, "linux.osname too long");
281 			return (ENAMETOOLONG);
282 		}
283 	}
284 	error = vfs_getopt(opts, "linux.osrelease", (void **)&osrelease, &len);
285 	if (error != ENOENT) {
286 		if (error != 0)
287 			return (error);
288 		if (len == 0 || osrelease[len - 1] != '\0')
289 			return (EINVAL);
290 		if (len > LINUX_MAX_UTSNAME) {
291 			vfs_opterror(opts, "linux.osrelease too long");
292 			return (ENAMETOOLONG);
293 		}
294 		error = linux_map_osrel(osrelease, NULL);
295 		if (error != 0) {
296 			vfs_opterror(opts, "linux.osrelease format error");
297 			return (error);
298 		}
299 	}
300 	error = vfs_copyopt(opts, "linux.oss_version", &oss_version,
301 	    sizeof(oss_version));
302 
303 	if (error == ENOENT)
304 		error = 0;
305 	return (error);
306 }
307 
308 static int
309 linux_prison_set(void *obj, void *data)
310 {
311 	struct linux_prison *lpr;
312 	struct prison *pr = obj;
313 	struct vfsoptlist *opts = data;
314 	char *osname, *osrelease;
315 	int error, gotversion, jsys, len, oss_version;
316 
317 	/* Set the parameters, which should be correct. */
318 	error = vfs_copyopt(opts, "linux", &jsys, sizeof(jsys));
319 	if (error == ENOENT)
320 		jsys = -1;
321 	error = vfs_getopt(opts, "linux.osname", (void **)&osname, &len);
322 	if (error == ENOENT)
323 		osname = NULL;
324 	else
325 		jsys = JAIL_SYS_NEW;
326 	error = vfs_getopt(opts, "linux.osrelease", (void **)&osrelease, &len);
327 	if (error == ENOENT)
328 		osrelease = NULL;
329 	else
330 		jsys = JAIL_SYS_NEW;
331 	error = vfs_copyopt(opts, "linux.oss_version", &oss_version,
332 	    sizeof(oss_version));
333 	if (error == ENOENT)
334 		gotversion = 0;
335 	else {
336 		gotversion = 1;
337 		jsys = JAIL_SYS_NEW;
338 	}
339 	switch (jsys) {
340 	case JAIL_SYS_INHERIT:
341 		/* "linux=inherit": inherit the parent's Linux info. */
342 		mtx_lock(&pr->pr_mtx);
343 		osd_jail_del(pr, linux_osd_jail_slot);
344 		mtx_unlock(&pr->pr_mtx);
345 		break;
346 	case JAIL_SYS_NEW:
347 		/*
348 		 * "linux=new" or "linux.*":
349 		 * the prison gets its own Linux info.
350 		 */
351 		linux_alloc_prison(pr, &lpr);
352 		if (osrelease) {
353 			(void)linux_map_osrel(osrelease, &lpr->pr_osrel);
354 			strlcpy(lpr->pr_osrelease, osrelease,
355 			    LINUX_MAX_UTSNAME);
356 		}
357 		if (osname)
358 			strlcpy(lpr->pr_osname, osname, LINUX_MAX_UTSNAME);
359 		if (gotversion)
360 			lpr->pr_oss_version = oss_version;
361 		mtx_unlock(&pr->pr_mtx);
362 	}
363 
364 	return (0);
365 }
366 
367 SYSCTL_JAIL_PARAM_SYS_NODE(linux, CTLFLAG_RW, "Jail Linux parameters");
368 SYSCTL_JAIL_PARAM_STRING(_linux, osname, CTLFLAG_RW, LINUX_MAX_UTSNAME,
369     "Jail Linux kernel OS name");
370 SYSCTL_JAIL_PARAM_STRING(_linux, osrelease, CTLFLAG_RW, LINUX_MAX_UTSNAME,
371     "Jail Linux kernel OS release");
372 SYSCTL_JAIL_PARAM(_linux, oss_version, CTLTYPE_INT | CTLFLAG_RW,
373     "I", "Jail Linux OSS version");
374 
375 static int
376 linux_prison_get(void *obj, void *data)
377 {
378 	struct linux_prison *lpr;
379 	struct prison *ppr;
380 	struct prison *pr = obj;
381 	struct vfsoptlist *opts = data;
382 	int error, i;
383 
384 	static int version0;
385 
386 	/* See if this prison is the one with the Linux info. */
387 	lpr = linux_find_prison(pr, &ppr);
388 	i = (ppr == pr) ? JAIL_SYS_NEW : JAIL_SYS_INHERIT;
389 	error = vfs_setopt(opts, "linux", &i, sizeof(i));
390 	if (error != 0 && error != ENOENT)
391 		goto done;
392 	if (i) {
393 		error = vfs_setopts(opts, "linux.osname", lpr->pr_osname);
394 		if (error != 0 && error != ENOENT)
395 			goto done;
396 		error = vfs_setopts(opts, "linux.osrelease", lpr->pr_osrelease);
397 		if (error != 0 && error != ENOENT)
398 			goto done;
399 		error = vfs_setopt(opts, "linux.oss_version",
400 		    &lpr->pr_oss_version, sizeof(lpr->pr_oss_version));
401 		if (error != 0 && error != ENOENT)
402 			goto done;
403 	} else {
404 		/*
405 		 * If this prison is inheriting its Linux info, report
406 		 * empty/zero parameters.
407 		 */
408 		error = vfs_setopts(opts, "linux.osname", "");
409 		if (error != 0 && error != ENOENT)
410 			goto done;
411 		error = vfs_setopts(opts, "linux.osrelease", "");
412 		if (error != 0 && error != ENOENT)
413 			goto done;
414 		error = vfs_setopt(opts, "linux.oss_version", &version0,
415 		    sizeof(lpr->pr_oss_version));
416 		if (error != 0 && error != ENOENT)
417 			goto done;
418 	}
419 	error = 0;
420 
421  done:
422 	mtx_unlock(&ppr->pr_mtx);
423 
424 	return (error);
425 }
426 
427 static void
428 linux_prison_destructor(void *data)
429 {
430 
431 	free(data, M_PRISON);
432 }
433 
434 void
435 linux_osd_jail_register(void)
436 {
437 	struct prison *pr;
438 	osd_method_t methods[PR_MAXMETHOD] = {
439 	    [PR_METHOD_CREATE] =	linux_prison_create,
440 	    [PR_METHOD_GET] =		linux_prison_get,
441 	    [PR_METHOD_SET] =		linux_prison_set,
442 	    [PR_METHOD_CHECK] =		linux_prison_check
443 	};
444 
445 	linux_osd_jail_slot =
446 	    osd_jail_register(linux_prison_destructor, methods);
447 	/* Copy the system Linux info to any current prisons. */
448 	sx_slock(&allprison_lock);
449 	TAILQ_FOREACH(pr, &allprison, pr_list)
450 		linux_alloc_prison(pr, NULL);
451 	sx_sunlock(&allprison_lock);
452 }
453 
454 void
455 linux_osd_jail_deregister(void)
456 {
457 
458 	osd_jail_deregister(linux_osd_jail_slot);
459 }
460 
461 void
462 linux_get_osname(struct thread *td, char *dst)
463 {
464 	struct prison *pr;
465 	struct linux_prison *lpr;
466 
467 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
468 	bcopy(lpr->pr_osname, dst, LINUX_MAX_UTSNAME);
469 	mtx_unlock(&pr->pr_mtx);
470 }
471 
472 static int
473 linux_set_osname(struct thread *td, char *osname)
474 {
475 	struct prison *pr;
476 	struct linux_prison *lpr;
477 
478 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
479 	strlcpy(lpr->pr_osname, osname, LINUX_MAX_UTSNAME);
480 	mtx_unlock(&pr->pr_mtx);
481 
482 	return (0);
483 }
484 
485 void
486 linux_get_osrelease(struct thread *td, char *dst)
487 {
488 	struct prison *pr;
489 	struct linux_prison *lpr;
490 
491 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
492 	bcopy(lpr->pr_osrelease, dst, LINUX_MAX_UTSNAME);
493 	mtx_unlock(&pr->pr_mtx);
494 }
495 
496 int
497 linux_kernver(struct thread *td)
498 {
499 	struct prison *pr;
500 	struct linux_prison *lpr;
501 	int osrel;
502 
503 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
504 	osrel = lpr->pr_osrel;
505 	mtx_unlock(&pr->pr_mtx);
506 
507 	return (osrel);
508 }
509 
510 static int
511 linux_set_osrelease(struct thread *td, char *osrelease)
512 {
513 	struct prison *pr;
514 	struct linux_prison *lpr;
515 	int error;
516 
517 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
518 	error = linux_map_osrel(osrelease, &lpr->pr_osrel);
519 	if (error == 0)
520 		strlcpy(lpr->pr_osrelease, osrelease, LINUX_MAX_UTSNAME);
521 	mtx_unlock(&pr->pr_mtx);
522 
523 	return (error);
524 }
525 
526 int
527 linux_get_oss_version(struct thread *td)
528 {
529 	struct prison *pr;
530 	struct linux_prison *lpr;
531 	int version;
532 
533 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
534 	version = lpr->pr_oss_version;
535 	mtx_unlock(&pr->pr_mtx);
536 
537 	return (version);
538 }
539 
540 static int
541 linux_set_oss_version(struct thread *td, int oss_version)
542 {
543 	struct prison *pr;
544 	struct linux_prison *lpr;
545 
546 	lpr = linux_find_prison(td->td_ucred->cr_prison, &pr);
547 	lpr->pr_oss_version = oss_version;
548 	mtx_unlock(&pr->pr_mtx);
549 
550 	return (0);
551 }
552