Merge nucleic/gentle-willow-quail-cjp3 into dev

This commit is contained in:
2026-07-21 04:23:04 -07:00
parent 51f1af7749
commit b0cd84245d
12 changed files with 259 additions and 1 deletions
+5
View File
@@ -44,6 +44,10 @@ echo "$ROLE" > "$R/etc/naros/role"
# Capability manifest (NAROS.md §6.3). Base fields here; toolchain entries are appended
# by the tiers that install them (agent-tier hook, N2). Versions of Nucleic packages are
# queryable via dpkg, listed here for one-stop reads.
#
# kernel_tag is the suffix naros-identity's preloaded shim appends to uname(2)'s release
# (§2.3) — recorded so a reader can tell the narOS marker apart from the host kernel's own
# version, and strip it. narOS builds no kernel; the tag is identity, not a kernel build.
nash_ver="$(chroot "$R" dpkg-query -W -f '${Version}' nash 2>/dev/null || echo null)"
[ "$nash_ver" = null ] || nash_ver="\"$nash_ver\""
cat > "$R/etc/naros/manifest.json" <<EOF
@@ -55,6 +59,7 @@ cat > "$R/etc/naros/manifest.json" <<EOF
"variant": "$VARIANT",
"flavor": "$FLAVOR",
"arch": "$NAROS_ARCH",
"kernel_tag": "-naros$NAROS_VERSION",
"debian": { "suite": "$NAROS_SUITE", "snapshot": "$NAROS_SNAPSHOT" },
"nash": $nash_ver,
"toolchains": {},
+1
View File
@@ -4,4 +4,5 @@
nash
naros-init
naros
naros-identity
naros-keyring
+1
View File
@@ -7,6 +7,7 @@
nash
naros-init
naros
naros-identity
naros-keyring
nucleic-linux-agent
nucleic-a11y-agent
+1
View File
@@ -5,4 +5,5 @@
nash
naros-init
naros
naros-identity
naros-keyring
+17
View File
@@ -0,0 +1,17 @@
Package: naros-identity
Version: @VERSION@
Architecture: @ARCH@
Maintainer: Nucleic <[email protected]>
Section: utils
Priority: optional
Depends: libc6
Description: narOS kernel identity shim (NAROS.md §2.3)
narOS ships no kernel of its own — containers share the host's and the VM tiers
boot an externally-fetched vmlinux — so uname(2) reports a kernel with no narOS
in it, and every consumer that derives an OS string from it (notably the
`OS Version:` line agent harnesses build from os.type()+os.release()) misses the
identity that /etc/os-release carries. This package preloads a small interposer
via /etc/ld.so.preload that appends the narOS release tag to utsname.release,
the way a distro kernel package does. sysname stays "Linux" so build tooling
that switches on it is unaffected; NAROS_UNAME_PASSTHROUGH=1 disables the tag
for callers that resolve /lib/modules/`uname -r`.
+35
View File
@@ -0,0 +1,35 @@
#!/bin/sh
# Register the identity shim in /etc/ld.so.preload (NAROS.md §2.3).
#
# Idempotent, and additive rather than authoritative: the file is rewritten preserving any
# other entries, so this package never owns unrelated preloads. The write goes through a
# temp file + rename because /etc/ld.so.preload is read by the loader on EVERY exec — a
# partially written list would be observed by whatever runs during the write.
set -e
LIB=/usr/lib/naros/libnaros-uname.so
PRELOAD=/etc/ld.so.preload
TMP="$PRELOAD.naros-tmp"
case "$1" in
configure)
# Belt and braces: never point the loader at a library that is not on disk.
if [ ! -f "$LIB" ]; then
echo "naros-identity: $LIB missing, not registering preload" >&2
exit 0
fi
if [ -f "$PRELOAD" ] && grep -qxF "$LIB" "$PRELOAD"; then
exit 0
fi
if [ -f "$PRELOAD" ]; then
cat "$PRELOAD" > "$TMP"
else
: > "$TMP"
fi
echo "$LIB" >> "$TMP"
chmod 0644 "$TMP"
mv "$TMP" "$PRELOAD"
;;
esac
exit 0
+28
View File
@@ -0,0 +1,28 @@
#!/bin/sh
# Deregister the identity shim from /etc/ld.so.preload before its files are removed
# (NAROS.md §2.3), so the loader never names a library that is no longer on disk.
#
# Runs on remove/deconfigure only: on `upgrade` the entry must persist, since the
# replacement .so lands at the same path and the new postinst is a no-op.
set -e
LIB=/usr/lib/naros/libnaros-uname.so
PRELOAD=/etc/ld.so.preload
TMP="$PRELOAD.naros-tmp"
case "$1" in
remove | deconfigure)
[ -f "$PRELOAD" ] || exit 0
# grep exits 1 when nothing survives the filter; that is the empty case, not an error.
grep -vxF "$LIB" "$PRELOAD" > "$TMP" || true
if [ -s "$TMP" ]; then
chmod 0644 "$TMP"
mv "$TMP" "$PRELOAD"
else
# An empty ld.so.preload is legal but pointless — drop the file entirely.
rm -f "$TMP" "$PRELOAD"
fi
;;
esac
exit 0
+25
View File
@@ -0,0 +1,25 @@
# Stage the prebuilt narOS kernel identity shim (os/src/naros-identity, built per-arch
# into dist/bin by that dir's build.sh / the CI binaries job).
#
# /etc/ld.so.preload is written by the postinst rather than shipped in files/: dpkg
# unpacks a package's files in no guaranteed order, so a shipped preload file could land
# before the .so it names and make every binary exec'd for the rest of that transaction —
# including dpkg's own maintainer scripts — emit a loader warning. The postinst runs
# after the whole package is on disk, so the reference is never dangling.
# The /lib/modules alias unit rides along with a static enable symlink (rather than a
# `systemctl enable` in the postinst) so it takes effect inside the mmdebstrap chroot,
# where no systemd is running — the same pattern nucleic-linux-agent uses.
stage() {
local dest="$1" arch="$2"
local lib="$OS_DIR/dist/bin/libnaros-uname-$arch.so"
local unit="$OS_DIR/src/naros-identity/naros-identity-modules.service"
if [ ! -f "$lib" ]; then
echo "prebuilt shim missing: dist/bin/libnaros-uname-$arch.so" > "$dest/.skip-reason"
return 1
fi
install -D -m 0644 "$lib" "$dest/usr/lib/naros/libnaros-uname.so"
install -D -m 0644 "$unit" "$dest/usr/lib/systemd/system/naros-identity-modules.service"
install -d "$dest/etc/systemd/system/sysinit.target.wants"
ln -sf /usr/lib/systemd/system/naros-identity-modules.service \
"$dest/etc/systemd/system/sysinit.target.wants/naros-identity-modules.service"
}
+1 -1
View File
@@ -4,7 +4,7 @@ Architecture: all
Maintainer: Nucleic <[email protected]>
Section: metapackages
Priority: optional
Depends: nash, nash-default-shell, naros-init, naros, ca-certificates, curl, git, openssh-client, iproute2
Depends: nash, nash-default-shell, naros-init, naros, naros-identity, ca-certificates, curl, git, openssh-client, iproute2
Recommends: naros-keyring
Description: narOS base tier (NAROS.md §4)
The minimal narOS surface: nash forced as the default shell, naros-init, the
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Build the narOS kernel identity shim into os/dist/bin/libnaros-uname-<arch>.so
# (NAROS.md §2.3), the artifact os/packages/naros-identity/stage.sh packages.
#
# build.sh [arch] arch: arm64|amd64 (default: this host's)
#
# glibc, dynamically linked, on purpose: the shim only ever loads into glibc processes
# via /etc/ld.so.preload, and a static or musl build could not interpose them. CI builds
# arm64 with the gcc-aarch64-linux-gnu cross toolchain rather than zig (which the musl
# static binaries in this tree use) because a glibc shared object is exactly what the
# stock cross-gcc is for.
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OS_DIR="$(cd "$here/../.." && pwd)"
VERSION="$(cat "$OS_DIR/VERSION")"
case "${1:-$(dpkg --print-architecture 2>/dev/null || uname -m)}" in
arm64 | aarch64) arch=arm64 ;;
amd64 | x86_64) arch=amd64 ;;
*) echo "unsupported arch: ${1:-}" >&2; exit 2 ;;
esac
# Cross only when the target differs from the host; a native build wants plain cc.
host="$(uname -m)"
cc=cc
if [ "$arch" = arm64 ] && [ "$host" != aarch64 ]; then cc=aarch64-linux-gnu-gcc; fi
if [ "$arch" = amd64 ] && [ "$host" != x86_64 ]; then cc=x86_64-linux-gnu-gcc; fi
command -v "$cc" > /dev/null || { echo "compiler not found: $cc" >&2; exit 2; }
out="$OS_DIR/dist/bin/libnaros-uname-$arch.so"
mkdir -p "$(dirname "$out")"
"$cc" -shared -fPIC -O2 -Wall -Wextra \
-DNAROS_KERNEL_TAG="\"-naros$VERSION\"" \
-o "$out" "$here/uname.c"
# Match strip to the compiler: the host's strip cannot touch a cross-built object.
strip_bin=strip
[ "$cc" = cc ] || strip_bin="${cc%gcc}strip"
"$strip_bin" "$out" 2> /dev/null || true
echo "built $out (tag -naros$VERSION, $cc)"
@@ -0,0 +1,31 @@
[Unit]
Description=narOS: alias /lib/modules for the identity-tagged kernel release
Documentation=https://github.com/abkslm/nucleic/blob/main/docs/NAROS.md
# /lib/modules/`uname -r` is a real path: kmod, depmod and udev all resolve modules
# through it. Since naros-identity tags utsname.release (NAROS.md §2.3), the tagged name
# has no directory and module autoloading would fail on the tiers that load modules at all
# — the VM tiers, which boot an external kernel whose modules the payload bakes in under
# the UNTAGGED release. Symlinking the tagged name onto the real directory fixes every
# consumer at once, which env-var plumbing into each caller could not.
#
# Inert on the container tiers: naros-init is PID 1 there, so no unit ever runs, and
# containers never load modules anyway.
DefaultDependencies=no
Before=systemd-modules-load.service sysinit.target
After=systemd-remount-fs.service
[Service]
Type=oneshot
RemainAfterExit=yes
# Only $(...) substitutions, never bare $var: systemd expands $NAME in ExecStart itself,
# so shell variables here would arrive empty.
#
# Both guards matter. The first requires the real module directory to exist, so a kernel
# that ships no modules is left alone. The second requires the tagged path to be ABSENT,
# which is what makes this safe when the tag is not in effect (shim not installed, or
# passthrough): there tagged == real, the path exists, and we must not replace a real
# module directory with a symlink to itself.
ExecStart=/bin/sh -c '[ -d "/lib/modules/$(NAROS_UNAME_PASSTHROUGH=1 uname -r)" ] && [ ! -e "/lib/modules/$(uname -r)" ] && ln -sfnT "/lib/modules/$(NAROS_UNAME_PASSTHROUGH=1 uname -r)" "/lib/modules/$(uname -r)"; true'
[Install]
WantedBy=sysinit.target
+73
View File
@@ -0,0 +1,73 @@
/* narOS kernel identity shim (NAROS.md §2.3).
*
* narOS does not build its own kernel: containers share the host's (on Cloudflare
* Containers that is a Firecracker microVM kernel, whose release string carries a
* `-cloudflare-firecracker` suffix) and the VM tiers boot an externally-fetched
* vmlinux (scripts/fetch-kernel.sh). So there is no CONFIG_LOCALVERSION to set, and
* every uname(2)-derived identity — including the `OS Version:` line agent harnesses
* put in their environment block, which is os.type() + os.release() — reports the
* host kernel with no narOS in it at all, no matter what /etc/os-release says.
*
* This interposer, preloaded via /etc/ld.so.preload, appends the narOS release tag to
* utsname.release the way a distro kernel package does (Debian's own kernels report
* `6.1.0-18-amd64`), so the identity is true at the syscall layer rather than only in
* files a caller has to know to read.
*
* Deliberately narrow: `sysname` stays "Linux". It is the single most-switched-on
* uname field in build tooling — autoconf's config.guess, CMAKE_SYSTEM_NAME, node-gyp,
* the Go and rustup installers all compare it against "Linux" and fall through to
* "unsupported platform" otherwise. `version` is likewise untouched.
*
* Truth comes from syscall(SYS_uname) rather than dlsym(RTLD_NEXT): a library in
* /etc/ld.so.preload is loaded into *every* dynamically-linked process on the system,
* including early boot and dpkg's own maintainer scripts, so it must not depend on
* libdl being resolvable or risk recursing through an interposed symbol.
*
* Escape hatch: NAROS_UNAME_PASSTHROUGH=1 returns the kernel's answer verbatim. This
* exists because /lib/modules/`uname -r` is a real path — kmod, depmod and udev resolve
* modules through it, so the VM tiers need the untagged release to find their external
* kernel's modules (see nucleic-modsetup.service, which also symlinks the tagged name
* onto the real one so autoloading works without the env var).
*
* Statically linked binaries, and Go programs that issue the raw syscall themselves,
* bypass this by construction — the tag is an identity marker, never a security or
* correctness boundary.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/utsname.h>
#include <unistd.h>
/* Baked at build time from os/VERSION, e.g. "-naros26.07". */
#ifndef NAROS_KERNEL_TAG
#define NAROS_KERNEL_TAG "-naros"
#endif
static int naros_passthrough(void)
{
const char *v = getenv("NAROS_UNAME_PASSTHROUGH");
return v != NULL && *v != '\0' && strcmp(v, "0") != 0;
}
int uname(struct utsname *buf)
{
long rc = syscall(SYS_uname, buf);
if (rc != 0 || buf == NULL)
return (int)rc;
if (naros_passthrough())
return 0;
/* Idempotent: a re-exec through another preloading process must not stack tags. */
if (strstr(buf->release, NAROS_KERNEL_TAG) != NULL)
return 0;
size_t have = strnlen(buf->release, sizeof(buf->release));
size_t tag = sizeof(NAROS_KERNEL_TAG) - 1;
/* utsname.release is a fixed 65-byte field; leave it untagged rather than truncate
* the real kernel release, which callers parse for version comparisons. */
if (have + tag + 1 > sizeof(buf->release))
return 0;
memcpy(buf->release + have, NAROS_KERNEL_TAG, tag + 1);
return 0;
}