From 6328ed1aac7b825d2fcc8e971998b1650d14f574 Mon Sep 17 00:00:00 2001 From: Lucas Holt Date: Wed, 11 Nov 2026 16:12:00 -0500 Subject: [PATCH 1/2] libc, rtld: translate osreldate to a FreeBSD version for ABI checks The kernel reports __MidnightBSD_version (402005) as its osreldate, but every caller of __getosreldate() compares the result with FreeBSD version thresholds: INO64_FIRST, CLOSE_RANGE_OSREL, GETRANDOM_FIRST, P_OSREL_MAP_GUARD, P_OSREL_WRFSBASE, P_OSREL_TIDPID, and the __realpathat check. All of them failed on every architecture, so libc always used the freebsd11 stat and dirent compat syscalls (32-bit inode numbers), and close_range, getrandom, __realpathat, MAP_GUARD, WRFSBASE and CPU_WHICH_TIDPID were never used. Add __osrel_to_freebsd(), which maps a MidnightBSD version to the __FreeBSD_version of the tree it was built from (taken from the history of sys/sys/param.h, using the lowest value when a MidnightBSD version spanned several), and apply it in libc's __getosreldate() and in rtld's override and aux vector handling. Values of 700000 and up are already FreeBSD versions and pass through unchanged. getosreldate(3) still returns the MidnightBSD version. On amd64, ls -ld / goes from 19 compat11 syscalls to none, and rtld now maps objects with MAP_GUARD. See issue #641. AI-Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Lucas Holt --- lib/libc/gen/Makefile.inc | 1 + lib/libc/gen/__getosreldate.c | 12 +++- lib/libc/gen/__osrel_freebsd.c | 82 +++++++++++++++++++++++++ lib/libc/include/libc_private.h | 6 ++ libexec/rtld-elf/rtld-libc/Makefile.inc | 2 +- libexec/rtld-elf/rtld-libc/rtld_libc.h | 1 + libexec/rtld-elf/rtld.c | 4 +- 7 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 lib/libc/gen/__osrel_freebsd.c diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc index fb25b977b1e..9a2e88a31bc 100644 --- a/lib/libc/gen/Makefile.inc +++ b/lib/libc/gen/Makefile.inc @@ -8,6 +8,7 @@ CONFSMODE_master.passwd= 600 CONFSPACKAGE= runtime SRCS+= __getosreldate.c \ + __osrel_freebsd.c \ __pthread_mutex_init_calloc_cb_stub.c \ __xuname.c \ _once_stub.c \ diff --git a/lib/libc/gen/__getosreldate.c b/lib/libc/gen/__getosreldate.c index f882f0a8203..7ea06301d63 100644 --- a/lib/libc/gen/__getosreldate.c +++ b/lib/libc/gen/__getosreldate.c @@ -40,6 +40,9 @@ * * Use getosreldate(3) for public use as it respects the $OSVERSION environment * variable. + * + * The kernel reports a MidnightBSD version, but callers compare the result + * against FreeBSD versions, so it is translated by __osrel_to_freebsd(). */ int @@ -53,9 +56,12 @@ __getosreldate(void) if (osreldate != 0) return (osreldate); - error = _elf_aux_info(AT_OSRELDATE, &osreldate, sizeof(osreldate)); - if (error == 0 && osreldate != 0) + osrel = 0; + error = _elf_aux_info(AT_OSRELDATE, &osrel, sizeof(osrel)); + if (error == 0 && osrel != 0) { + osreldate = __osrel_to_freebsd(osrel); return (osreldate); + } oid[0] = CTL_KERN; oid[1] = KERN_OSRELDATE; @@ -63,6 +69,6 @@ __getosreldate(void) len = sizeof(osrel); error = sysctl(oid, 2, &osrel, &len, NULL, 0); if (error == 0 && osrel > 0 && len == sizeof(osrel)) - osreldate = osrel; + osreldate = __osrel_to_freebsd(osrel); return (osreldate); } diff --git a/lib/libc/gen/__osrel_freebsd.c b/lib/libc/gen/__osrel_freebsd.c new file mode 100644 index 00000000000..27f10d2c0f9 --- /dev/null +++ b/lib/libc/gen/__osrel_freebsd.c @@ -0,0 +1,82 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 The MidnightBSD Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include "libc_private.h" + +/* + * The kernel reports __MidnightBSD_version as its osreldate, but libc and + * rtld compare the value returned by __getosreldate() against FreeBSD + * version thresholds (INO64_FIRST, P_OSREL_*, ...). Translate a + * MidnightBSD version to the __FreeBSD_version of the tree that release + * was built from, so those checks see the features the kernel really has. + * + * Each entry is the first MidnightBSD version with that FreeBSD version. + * Where one MidnightBSD version spanned several FreeBSD versions, the + * lowest is used, so a feature is never assumed before it existed. Keep + * this table in sync with sys/sys/param.h when the FreeBSD base changes. + */ +static const struct { + int mnbsd; + int fbsd; +} osrel_map[] = { + { 400001, 1305500 }, /* 4.0 */ + { 301006, 1204500 }, /* 3.1 */ + { 300003, 1203507 }, /* 3.0 */ + { 200000, 1103507 }, /* 2.0 */ + { 100001, 1004501 }, /* 1.0 */ +}; + +/* + * MidnightBSD versions are well below the oldest FreeBSD version userland + * still checks for. Anything at or above this is already a FreeBSD + * version (for example, a jail created with a FreeBSD osreldate) and is + * returned unchanged. + */ +#define OSREL_FREEBSD_MIN 700000 + +int +__osrel_to_freebsd(int osrel) +{ + size_t i; + + if (osrel <= 0 || osrel >= OSREL_FREEBSD_MIN) + return (osrel); + + /* A kernel at least as new as this libc has everything it knows. */ + if (osrel >= __MidnightBSD_version) + return (__FreeBSD_version); + + for (i = 0; i < nitems(osrel_map); i++) { + if (osrel >= osrel_map[i].mnbsd) + return (osrel_map[i].fbsd); + } + + /* Older than any release with a FreeBSD 10 base. */ + return (osrel); +} diff --git a/lib/libc/include/libc_private.h b/lib/libc/include/libc_private.h index b2ad24ff33f..c771027d340 100644 --- a/lib/libc/include/libc_private.h +++ b/lib/libc/include/libc_private.h @@ -318,6 +318,12 @@ extern void (*__cleanup)(void) __hidden; * ignores value of $OSVERSION and caches result. */ int __getosreldate(void); + +/* + * Translate a MidnightBSD osreldate to the matching __FreeBSD_version, for + * comparisons against FreeBSD version thresholds. + */ +int __osrel_to_freebsd(int); #include #include diff --git a/libexec/rtld-elf/rtld-libc/Makefile.inc b/libexec/rtld-elf/rtld-libc/Makefile.inc index 94f11e45940..2713f7a157b 100644 --- a/libexec/rtld-elf/rtld-libc/Makefile.inc +++ b/libexec/rtld-elf/rtld-libc/Makefile.inc @@ -33,7 +33,7 @@ CFLAGS.merge.c+=-Wno-error=null-pointer-arithmetic .endif .PATH: ${LIBC_SRCTOP}/gen SRCS+= errlst.c getcwd.c getprogname.c raise.c sigsetops.c sysctlnametomib.c \ - __xuname.c + __xuname.c __osrel_freebsd.c # errlst.c needs the errlst.h header from libc: CFLAGS.errlst.c+=-I${LIBC_SRCTOP}/include diff --git a/libexec/rtld-elf/rtld-libc/rtld_libc.h b/libexec/rtld-elf/rtld-libc/rtld_libc.h index 57a11af90e1..bb036e5596c 100644 --- a/libexec/rtld-elf/rtld-libc/rtld_libc.h +++ b/libexec/rtld-elf/rtld-libc/rtld_libc.h @@ -62,6 +62,7 @@ __ssize_t __sys_write(int, const void *, __size_t); extern char* __progname; const char *_getprogname(void); int __getosreldate(void); +int __osrel_to_freebsd(int); /* diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c index 70e20570b44..31b567a4d71 100644 --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -2424,7 +2424,7 @@ init_rtld(caddr_t mapbase, Elf_Auxinfo **aux_info) #endif if (aux_info[AT_OSRELDATE] != NULL) - osreldate = aux_info[AT_OSRELDATE]->a_un.a_val; + osreldate = __osrel_to_freebsd(aux_info[AT_OSRELDATE]->a_un.a_val); digest_dynamic2(&obj_rtld, dyn_rpath, dyn_soname, dyn_runpath); @@ -6355,7 +6355,7 @@ __getosreldate(void) len = sizeof(osrel); error = sysctl(oid, 2, &osrel, &len, NULL, 0); if (error == 0 && osrel > 0 && len == sizeof(osrel)) - osreldate = osrel; + osreldate = __osrel_to_freebsd(osrel); return (osreldate); } const char * From 677ddeb44b765c8e38258a774c6b9e5bc4ceb35f Mon Sep 17 00:00:00 2001 From: Lucas Holt Date: Wed, 11 Nov 2026 16:49:31 -0500 Subject: [PATCH 2/2] libc: add tests for __osrel_to_freebsd() and document the jail range Add osrel_freebsd_test, which builds the private translation into an ATF test and checks pass-through values, every table boundary, the current release, and that the running kernel's osreldate translates to at least INO64_FIRST. Explain why values below 700000 are always treated as MidnightBSD versions even though FreeBSD 4-6 used the same range: only MidnightBSD userland calls this, and a FreeBSD 4-6 jail runs its own libc and rtld. AI-Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Lucas Holt --- lib/libc/gen/__osrel_freebsd.c | 13 ++- lib/libc/tests/gen/Makefile | 6 ++ lib/libc/tests/gen/osrel_freebsd_test.c | 111 ++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 lib/libc/tests/gen/osrel_freebsd_test.c diff --git a/lib/libc/gen/__osrel_freebsd.c b/lib/libc/gen/__osrel_freebsd.c index 27f10d2c0f9..44103bd3dd6 100644 --- a/lib/libc/gen/__osrel_freebsd.c +++ b/lib/libc/gen/__osrel_freebsd.c @@ -53,10 +53,15 @@ static const struct { }; /* - * MidnightBSD versions are well below the oldest FreeBSD version userland - * still checks for. Anything at or above this is already a FreeBSD - * version (for example, a jail created with a FreeBSD osreldate) and is - * returned unchanged. + * MidnightBSD versions are below the oldest FreeBSD version userland still + * checks for. Anything at or above this is already a FreeBSD version (for + * example, a jail created with a FreeBSD osreldate) and is returned + * unchanged. + * + * Values below this are always treated as MidnightBSD versions, even + * though FreeBSD 4-6 used the same range. That is safe: this code only + * runs in MidnightBSD userland, and a FreeBSD 4-6 jail runs its own libc + * and rtld, which never call it. */ #define OSREL_FREEBSD_MIN 700000 diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile index 1f21f5ab215..8bbc4c7d812 100644 --- a/lib/libc/tests/gen/Makefile +++ b/lib/libc/tests/gen/Makefile @@ -13,6 +13,7 @@ ATF_TESTS_C+= getentropy_test ATF_TESTS_C+= getmntinfo_test ATF_TESTS_C+= glob2_test ATF_TESTS_C+= makecontext_test +ATF_TESTS_C+= osrel_freebsd_test ATF_TESTS_C+= popen_test ATF_TESTS_C+= posix_spawn_test ATF_TESTS_C+= realpath2_test @@ -94,6 +95,11 @@ LIBADD.syslog_test+= pthread CFLAGS+= -I${.CURDIR} +# __osrel_to_freebsd() is private to libc, so build its source into the test. +.PATH: ${SRCTOP}/lib/libc/gen +SRCS.osrel_freebsd_test= osrel_freebsd_test.c __osrel_freebsd.c +CFLAGS.__osrel_freebsd.c+= -I${SRCTOP}/lib/libc/include + SRCS.fmtcheck2_test= fmtcheck_test.c SRCS.fnmatch2_test= fnmatch_test.c diff --git a/lib/libc/tests/gen/osrel_freebsd_test.c b/lib/libc/tests/gen/osrel_freebsd_test.c new file mode 100644 index 00000000000..a71001a482d --- /dev/null +++ b/lib/libc/tests/gen/osrel_freebsd_test.c @@ -0,0 +1,111 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 The MidnightBSD Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Tests for __osrel_to_freebsd(), which libc and rtld use to turn the + * kernel's MidnightBSD osreldate into a FreeBSD version for ABI checks. + * The function is private to libc, so its source is built into this test. + */ + +#include +#include + +#include + +int __osrel_to_freebsd(int); + +/* From lib/libc/sys/compat-ino64.h. */ +#define INO64_FIRST 1200031 + +ATF_TC_WITHOUT_HEAD(passthrough); +ATF_TC_BODY(passthrough, tc) +{ + /* Unset, invalid, and values that are already FreeBSD versions. */ + ATF_CHECK_EQ(__osrel_to_freebsd(0), 0); + ATF_CHECK_EQ(__osrel_to_freebsd(-1), -1); + ATF_CHECK_EQ(__osrel_to_freebsd(700000), 700000); + ATF_CHECK_EQ(__osrel_to_freebsd(1305500), 1305500); + ATF_CHECK_EQ(__osrel_to_freebsd(1400000), 1400000); + + /* Older than any release with a FreeBSD 10 base. */ + ATF_CHECK_EQ(__osrel_to_freebsd(9005), 9005); + ATF_CHECK_EQ(__osrel_to_freebsd(100000), 100000); +} + +ATF_TC_WITHOUT_HEAD(table_boundaries); +ATF_TC_BODY(table_boundaries, tc) +{ + ATF_CHECK_EQ(__osrel_to_freebsd(100001), 1004501); + ATF_CHECK_EQ(__osrel_to_freebsd(199999), 1004501); + ATF_CHECK_EQ(__osrel_to_freebsd(200000), 1103507); + ATF_CHECK_EQ(__osrel_to_freebsd(300002), 1103507); + ATF_CHECK_EQ(__osrel_to_freebsd(300003), 1203507); + ATF_CHECK_EQ(__osrel_to_freebsd(301005), 1203507); + ATF_CHECK_EQ(__osrel_to_freebsd(301006), 1204500); + ATF_CHECK_EQ(__osrel_to_freebsd(400000), 1204500); + ATF_CHECK_EQ(__osrel_to_freebsd(400001), 1305500); +} + +ATF_TC_WITHOUT_HEAD(current_release); +ATF_TC_BODY(current_release, tc) +{ + /* A kernel at least as new as this libc maps to its FreeBSD base. */ + ATF_CHECK_EQ(__osrel_to_freebsd(__MidnightBSD_version), + __FreeBSD_version); + ATF_CHECK_EQ(__osrel_to_freebsd(__MidnightBSD_version + 1), + __FreeBSD_version); + ATF_CHECK_EQ(__osrel_to_freebsd(699999), __FreeBSD_version); +} + +ATF_TC_WITHOUT_HEAD(running_kernel); +ATF_TC_BODY(running_kernel, tc) +{ + size_t len; + int osrel; + + len = sizeof(osrel); + ATF_REQUIRE(sysctlbyname("kern.osreldate", &osrel, &len, NULL, 0) == + 0); + + /* + * The running kernel has the ino64 syscalls, so libc must not fall + * back to the freebsd11 compat versions. + */ + ATF_CHECK_MSG(__osrel_to_freebsd(osrel) >= INO64_FIRST, + "kern.osreldate %d translates to %d, below INO64_FIRST", osrel, + __osrel_to_freebsd(osrel)); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, passthrough); + ATF_TP_ADD_TC(tp, table_boundaries); + ATF_TP_ADD_TC(tp, current_release); + ATF_TP_ADD_TC(tp, running_kernel); + + return (atf_no_error()); +}