Skip to content

libc, rtld: translate osreldate to a FreeBSD version for ABI checks - #644

Merged
laffer1 merged 2 commits into
masterfrom
libc-osreldate-freebsd
Nov 11, 2026
Merged

laffer1 merged 2 commits into
masterfrom
libc-osreldate-freebsd

Conversation

@laffer1

@laffer1 laffer1 commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Fixes the userland half of #641.

Problem

The kernel reports __MidnightBSD_version (402005) as kern.osreldate and in AT_OSRELDATE. Every caller of the private __getosreldate() compares that value against FreeBSD thresholds, so on every architecture they all conclude the kernel is older than FreeBSD 12:

Check Threshold Result before this change
stat, lstat, fstat, fstatat, statfs, fstatfs, getfsstat, getdirentries INO64_FIRST 1200031 freebsd11_* compat syscalls, with 32-bit inode numbers
closefrom CLOSE_RANGE_OSREL close_range(2) never used
realpath 1300080 __realpathat(2) never used
getentropy GETRANDOM_FIRST 1200061 sysctl fallback instead of getrandom(2)
sched_{get,set}affinity P_OSREL_TIDPID{,_13} old TID/PID guessing instead of CPU_WHICH_TIDPID
rtld map_object P_OSREL_MAP_GUARD no MAP_GUARD
amd64 fs/gs base (libc and rtld) P_OSREL_WRFSBASE WRFSBASE path never used

Change

  • New lib/libc/gen/__osrel_freebsd.c provides __osrel_to_freebsd(), which maps a MidnightBSD version to the __FreeBSD_version of the tree it was built from:
    • The table comes from the history of sys/sys/param.h.
    • Where one MidnightBSD version spanned several FreeBSD versions, it uses the lowest one, so a feature is never assumed before it existed.
    • A kernel at or above the libc's own __MidnightBSD_version maps to the libc's __FreeBSD_version.
    • Values of 700000 and up are already FreeBSD versions (for example, a jail osreldate) and pass through unchanged.
  • libc's __getosreldate() and rtld's override and aux-vector path now return the translated value. rtld builds the same file through rtld-libc.
  • Unchanged: the public getosreldate(3), which still returns the MidnightBSD version that mports and other software use.
MidnightBSD maps to FreeBSD
< 100001 unchanged
100001 (1.0) 1004501
200000 (2.0) 1103507
300003 (3.0) 1203507
301006 (3.1) 1204500
400001 → current 1305500

I checked that the current kernel has every gated feature: the ino64 syscalls, close_range, __realpathat, getrandom, MAP_GUARD, the amd64 fs/gs base sysarch calls, and CPU_WHICH_TIDPID.

Testing (amd64, native)

  • Build: libc and rtld build cleanly, and checkstyle9 is clean.
  • Before/after: ktrace ls -ld / with the system libc and rtld makes 19 compat11 syscalls. With the patched rtld (direct exec) and libc, the ls process makes 0. It uses native fstat/fstatat/fstatfs/getdirentries, and rtld maps with MAP_GUARD.
  • Table boundaries: checked with a small harness (0, -1, 100000/100001, 300002/300003, 301005/301006, 400000/400001, 402005, 402010, 699999/700000, 1305500, 1400000).
  • Not tested: a full buildworld/installworld, other architectures, i386/lib32, or running new userland on a pre-4.0 kernel.

Notes for review

AI-Assisted-by: Claude Opus 5.5

🤖 Generated with Claude Code

Summary by Sourcery

Translate kernel release values for private ABI checks so libc and rtld can use the native kernel interfaces supported by MidnightBSD.

New Features:

  • Add translation of MidnightBSD kernel release values to corresponding FreeBSD ABI versions for libc and rtld feature checks.

Bug Fixes:

  • Enable userland to select native ABI features instead of incorrectly falling back to legacy FreeBSD compatibility paths on MidnightBSD kernels.

Enhancements:

  • Keep the public getosreldate(3) MidnightBSD value unchanged while translating only private libc and rtld checks.
  • Share the release translation logic between libc and rtld, including pass-through handling for existing FreeBSD release values.

Tests:

  • Add coverage for release translation pass-through behavior, mapping boundaries, current releases, and the running kernel.

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 <[email protected]>
Signed-off-by: Lucas Holt <[email protected]>
@sourcery-ai

sourcery-ai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

API

Declare the private osreldate translation interface for libc and rtld callers.

  • lib/libc/include/libc_private.h L321-326
  • libexec/rtld-elf/rtld-libc/rtld_libc.h L65

Uses of the API

Translate aux-vector and sysctl osreldate values in libc and rtld before ABI threshold checks use them.

  • lib/libc/gen/__getosreldate.c L43-45, L59-64, L72
  • libexec/rtld-elf/rtld.c L2427, L6358

Implementation

Implement the MidnightBSD-to-FreeBSD release mapping and pass-through rules that underpin internal ABI checks.

  • lib/libc/gen/__osrel_freebsd.c L1-87

Configuration

Build the shared translation implementation into libc and rtld, and compile it into the focused ATF test.

  • lib/libc/gen/Makefile.inc L11
  • lib/libc/tests/gen/Makefile L16, L98-102
  • libexec/rtld-elf/rtld-libc/Makefile.inc L36

Tests

Exercise translation pass-throughs, mapping boundaries, current-release behavior, and the running kernel's ino64 threshold.

  • lib/libc/tests/gen/osrel_freebsd_test.c L1-111

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="lib/libc/gen/__osrel_freebsd.c" line_range="61" />
<code_context>
+ * version (for example, a jail created with a FreeBSD osreldate) and is
+ * returned unchanged.
+ */
+#define	OSREL_FREEBSD_MIN	700000
+
+int
</code_context>
<issue_to_address>
**Older FreeBSD jails get newer ABI paths**

When a jail uses a FreeBSD 4–6 `osreldate` in the overlapping 400001–699999 range, `__osrel_to_freebsd()` treats the value as a MidnightBSD release and maps it to a newer FreeBSD ABI level, so libc and rtld select paths the jail’s configured ABI does not permit.

Distinguish pre-FreeBSD-7 osreldates from MidnightBSD release values before applying the mapping table.

Also at `lib/libc/gen/__osrel_freebsd.c:68-73`.
</issue_to_address>

### Comment 2
<location path="lib/libc/gen/__getosreldate.c" line_range="62" />
<code_context>
+	osrel = 0;
+	error = _elf_aux_info(AT_OSRELDATE, &osrel, sizeof(osrel));
+	if (error == 0 && osrel != 0) {
+		osreldate = __osrel_to_freebsd(osrel);
 		return (osreldate);
+	}
</code_context>
<issue_to_address>
**Jail changes leave stale ABI version**

When a process first initializes either cache in one prison and then attaches to a prison with a different `osreldate`, the cached translated release remains the value from the original prison, so libc and rtld keep selecting ABI paths for that prison after the process moves to another one, rather than using the new prison’s `kern.osreldate`.

Re-read and translate the current prison’s release when prison context can change, or invalidate these caches when the process changes prisons.

Also at `libexec/rtld-elf/rtld.c:2427`.
</issue_to_address>

### Comment 3
<location path="lib/libc/gen/__osrel_freebsd.c" line_range="48-53" />
<code_context>
+	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 */
+};
+
+/*
</code_context>
<issue_to_address>
**Release mapping lacks regression tests**

The new release mapping changes which ABI paths libc and rtld select, but no repository test covers its version boundaries or translated result. The boundary harness mentioned in the description is not part of the tree, so later mapping regressions can pass the checked-in test suite unnoticed.

Add checked-in regression tests for the translation boundaries and the libc/rtld paths that consume the translated value.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread lib/libc/gen/__osrel_freebsd.c Resolved
Comment thread lib/libc/gen/__getosreldate.c Resolved
Comment thread lib/libc/gen/__osrel_freebsd.c Resolved
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 <[email protected]>
Signed-off-by: Lucas Holt <[email protected]>
@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Claude finished @laffer1's task in 22s —— View job


Review of PR #644

I read the full diff (git diff origin/master...HEAD) and found no blocking issues. I did not build or run anything. This is a read-only review, and the sandbox has no bmake/atf toolchain for it.

Correctness

  • __osrel_to_freebsd() is correct. The table is sorted in descending order, so the first match is the highest mnbsd that is <= the input. The loop is bounded by nitems(). There are no allocations, so there is no malloc/free concern.
  • The osrel >= __MidnightBSD_version clause runs before the table lookup. That means a kernel newer than the libc maps to the libc's __FreeBSD_version, which is what the PR description says.
  • __getosreldate.c: the AT_OSRELDATE path now reads into the local osrel and only writes the cache with the translated value. On failure it falls through to the sysctl path. osrel was already declared there for the sysctl call, so this compiles.
  • rtld: both the aux-vector path (rtld.c:2427) and rtld's own __getosreldate() override (rtld.c:6358) now translate. The rtld-libc build picks up __osrel_freebsd.c and declares the prototype in rtld_libc.h. No other osreldate consumers in rtld bypass the translation, as far as I could see in the diff.

Sourcery's comments

  1. Jails with FreeBSD 4–6 osreldates (400001–699999): I don't think this needs a code change. The comment above OSREL_FREEBSD_MIN already covers it. This code only runs from MidnightBSD libc/rtld, and a FreeBSD 4–6 jail would use its own userland. The one assumption is that no MidnightBSD binary ever runs in such a jail.
  2. Stale cache after a process changes jails: this is not a regression. The cache already existed, and upstream __getosreldate() behaves the same way. Fixing it would be a separate change.
  3. Missing regression tests: this is already fixed. lib/libc/tests/gen/osrel_freebsd_test.c covers the passthrough values, every table boundary, the current-release mapping, and the running kernel (>= INO64_FIRST). It is wired into lib/libc/tests/gen/Makefile by building __osrel_freebsd.c into the test. Sourcery's comment predates that commit.

Minor, non-blocking

  • osrel_freebsd_test.c hardcodes INO64_FIRST with a "from compat-ino64.h" comment. If that header's value changes, the test won't notice. This is acceptable, but a check against the real header would be better if it can be included cleanly.
  • The table_boundaries test hardcodes the FreeBSD values, so it will need updating together with the table. The comment in the source already says to keep the table in sync.
  • current_release asserts 699999 -> __FreeBSD_version. That is correct because 699999 >= __MidnightBSD_version. It also pins the OSREL_FREEBSD_MIN boundary, so the assertion is deliberate and useful.

Process notes

  • No assembly or contrib/mksh files are touched.
  • I checked the style(9) formatting (tabs, line width) by eye only. I did not run checkstyle9.pl; the PR says it is clean.
  • The commits carry an AI-Assisted-by trailer, as the AI policy requires. This is a C change, so it still needs the manual audit that the PR description says is done.

Overall the change is sound and ready from my side, pending a real buildworld on other architectures (including i386/lib32) as the PR itself notes.

@laffer1
laffer1 merged commit f28f07c into master Nov 11, 2026
8 of 12 checks passed
@laffer1
laffer1 deleted the libc-osreldate-freebsd branch November 11, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant