Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ do.
mask); the host scheduler picks the actual CPU.
- `/proc`, `/dev`, and mount data are synthetic compatibility views,
not host pass-throughs.
- `uname` and `/proc/version` report Linux 6.18 LTS, a floor for
version-gated userspace; `src/syscall/dispatch.tbl` states what is
implemented.

## License

Expand Down
41 changes: 41 additions & 0 deletions docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,47 @@ under `/proc`, `/dev`, and a few Linux-expected compatibility files:
- Guest cwd handling preserves a virtual `/proc` working directory even
though the host operates on synthetic backing directories.

### Reported Kernel Identity

Two surfaces tell the guest which kernel it is running on: `uname(2)`, served
from a static `linux_utsname_t` in `src/syscall/sys.c`, and `/proc/version`,
emitted as a literal by `src/runtime/procemu.c`. Both spell the release
through `GUEST_KERNEL_RELEASE` and `GUEST_KERNEL_VERSION` in
`src/syscall/sys.h`, so the two cannot disagree; `tests/test-proc.c` asserts
that the release `uname` reports appears in the `/proc/version` banner.

The reported release is `6.18.0`, the 6.18 LTS baseline; it tracks no test
image. `elfuse` emits no `/proc/sys/kernel/osrelease`, so glibc's
`dl_discover_osversion` falls back to `uname(2)` and reads this value at
every dynamic startup. It refuses a kernel only when the release is below its
build-time floor, so a high baseline is the safe side.

The release feeds version-gated feature detection. `src/syscall/dispatch.tbl`
is the implemented set with two exceptions: `pidfd_getfd` and `userfaultfd`
are registered there, but their handlers in `src/syscall/syscall.c` return
`-ENOSYS`, and `tests/test-pidfd.c` and `tests/test-userfaultfd.c` pin them
at that stub. Anything absent from the table gets `-ENOSYS` from the dispatch
default, whatever number `uname` prints. Calls a 6.18 kernel provides that
are absent: `io_uring_*`, `mseal`, `cachestat`, `process_madvise`,
`listmount`/`statmount`, `landlock_*`, and `file_getattr`/`file_setattr`.
Guests probe for a syscall and handle `ENOSYS`.

The `LINUX_2.6.39` tag in the synthetic vDSO (`src/core/vdso.c`) is the
frozen ELF symbol-version name the real arm64 vDSO exports; a genuine 6.18
kernel emits it too.

Three things read the release automatically. `tests/test-proc.c` runs on both
the elfuse and the qemu aarch64 lane and checks only that the banner and
`uname` agree, so on the qemu lane it pins the real Alpine kernel to itself.
`tests/test-comprehensive.c` asserts the release is at least 6.18 behind a
`uts.nodename == "elfuse"` gate, which holds on the elfuse lanes and not on
the qemu lane, whose initramfs hostname is `elfuse-qemu`. The third reader is
silent: `build/bench-hot-guard-glibc` is the one aarch64 glibc binary in
`make check`, built only when the cross-toolchain sysroot is present, and a
release below glibc's floor would abort its startup rather than fail an
assertion. The Alpine tree `tests/fetch-fixtures.sh` builds is musl
throughout and does no version gating.

`/proc/self/smaps` and `/proc/<pid>/smaps` are generated from the same tracked
VMA list as `/proc/self/maps`. The complete field set currently emitted for
each VMA is the maps header followed by these 24 fields, in this order:
Expand Down
4 changes: 4 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ from targeted ABI translation and emulation at the syscall boundary.
That has a few direct implications:

- `/proc` and `/dev` are compatibility surfaces, not passthrough mounts.
- `uname` and `/proc/version` report Linux 6.18 LTS, a stable floor for
version-gated feature detection; the implemented syscall set is
`src/syscall/dispatch.tbl`. See [internals.md](internals.md), section
"Reported Kernel Identity".
- macOS and Linux file, socket, and signal semantics are normalized in the host
syscall layer.
- Behavior is strongest for normal command-line tools, language runtimes, test
Expand Down
4 changes: 2 additions & 2 deletions src/core/rosetta.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
* VZ environment. Without affirmative responses, rosetta prints "Rosetta is
* only intended to run on Apple Silicon ..." and exits.
*
* Reverse-engineered from the rosetta binary; values match what the Lima-on-VZ
* Linux VM observes via strace.
* Reverse-engineered from the rosetta binary; values captured via strace in
* a VZ Linux VM.
*/
#define ROSETTA_VZ_CHECK 0x80456125 /* Returns 69-byte signature */
#define ROSETTA_VZ_CAPS 0x80806123 /* Returns 128-byte capability blob */
Expand Down
11 changes: 5 additions & 6 deletions src/runtime/procemu.c
Original file line number Diff line number Diff line change
Expand Up @@ -2733,14 +2733,13 @@ int proc_intercept_open(const guest_t *g,
if (!strcmp(path, "/proc/sys/kernel/randomize_va_space"))
return proc_emit_literal("2\n");

/* /proc/version -> synthetic kernel version string */
/* /proc/version -> synthetic banner in the kernel's proc_version_show
* format; the builder and compiler fields are fixed strings.
*/
if (!strcmp(path, "/proc/version")) {
return proc_emit_literal(
"Linux version 6.17.0-20-generic "
"(buildd@bos03-arm64-051) "
"(aarch64-linux-gnu-gcc (Ubuntu 15.2.0-4ubuntu4) "
"15.2.0, GNU ld (GNU Binutils for Ubuntu) 2.45) "
"#20-Ubuntu SMP PREEMPT_DYNAMIC\n");
"Linux version " GUEST_KERNEL_RELEASE
" (elfuse@elfuse) (elfuse) " GUEST_KERNEL_VERSION "\n");
}

/* /proc/filesystems, /proc/self/mountinfo, /proc/mounts, /etc/mtab. */
Expand Down
4 changes: 2 additions & 2 deletions src/syscall/proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -3169,8 +3169,8 @@ static void vcpu_handle_mrs_trap(hv_vcpu_t vcpu,

uint64_t value = 0;

/* ID register emulation: return VZ-sanitized values matching a real VZ
* (Lima) VM BEFORE trying HVF. HVF's hv_vcpu_get_sys_reg succeeds for ID
/* ID register emulation: return VZ-sanitized values BEFORE trying HVF.
* HVF's hv_vcpu_get_sys_reg succeeds for ID
* registers but returns raw hardware values, which include features the
* hypervisor does not actually virtualize.
*
Expand Down
11 changes: 4 additions & 7 deletions src/syscall/sys.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,8 @@ static int cached_ngroups = -1;
static const linux_utsname_t cached_uname = {
.sysname = "Linux",
.nodename = "elfuse",

/* Kernel version: match the lima aarch64 VM kernel to avoid version-gated
* feature detection mismatches in userspace.
*/
.release = "6.17.0-20-generic",
.version = "#20-Ubuntu SMP PREEMPT_DYNAMIC",
.release = GUEST_KERNEL_RELEASE,
.version = GUEST_KERNEL_VERSION,
.machine = "aarch64",
.domainname = "(none)",
};
Expand Down Expand Up @@ -108,7 +104,8 @@ static void sysinfo_init_cached_host_state(void)
size_t ms_len = sizeof(memsize);
int mib_mem[2] = {CTL_HW, HW_MEMSIZE};
if (sysctl(mib_mem, 2, &memsize, &ms_len, NULL, 0) == 0) {
const uint64_t vm_ram_cap = 4094595072ULL; /* Match Lima VZ 4GiB VM */
const uint64_t vm_ram_cap =
4094595072ULL; /* totalram cap captured from a 4 GiB VZ VM */
cached_real_memsize = memsize;
cached_totalram = (memsize > vm_ram_cap) ? vm_ram_cap : memsize;
}
Expand Down
9 changes: 9 additions & 0 deletions src/syscall/sys.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@
#include <stdint.h>
#include "core/guest.h"

/* Guest-visible kernel identity for uname(2) and /proc/version;
* tests/test-proc.c pins the two surfaces to each other. The release names
* the 6.18 LTS baseline and tracks no test image; syscall/dispatch.tbl
* states what elfuse implements. glibc rejects only a release below its
* build-time floor, so a high floor is the safe side.
*/
#define GUEST_KERNEL_RELEASE "6.18.0"
#define GUEST_KERNEL_VERSION "#1 SMP PREEMPT_DYNAMIC"

/* System info syscall handlers. */

int64_t sys_uname(guest_t *g, uint64_t buf_gva);
Expand Down
19 changes: 16 additions & 3 deletions tests/fetch-fixtures.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
#
# Environment:
# FORCE=1 Rebuild every stage from scratch.
# KERNEL_ALPINE_VERSION=<rel>
# Alpine release the oracle kernel comes from, default
# 3.23 (Linux 6.18 LTS). Userland stays on
# ALPINE_VERSION.
# INCLUDE_X86_64=1 Also fetch x86_64 userspace for the elfuse-x86_64
# test-matrix mode. Default off; adds ~80 MiB of
# downloads but reuses the same Alpine version pin
Expand All @@ -36,6 +40,12 @@ set -euo pipefail
. "$(dirname "$0")/lib/bash-compat.sh"

ALPINE_VERSION="${ALPINE_VERSION:-3.21}"

# The oracle kernel resolves from its own Alpine release: 3.23 ships
# linux-virt 6.18, the LTS line elfuse reports to the guest, while the
# userland pins stay on ALPINE_VERSION. The linux-virt apk carries the
# kernel and its lib/modules tree, so both move together.
KERNEL_ALPINE_VERSION="${KERNEL_ALPINE_VERSION:-3.23}"
# Empty by default -- the exact point release is resolved from the live releases
# listing at fetch time (see resolve_minirootfs), then written back here so the
# x86_64 path reuses the same patch. Set explicitly to pin one.
Expand All @@ -46,6 +56,7 @@ CDN_BASE="https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}"
RELEASES="${CDN_BASE}/releases/${ALPINE_ARCH}"
MAIN_REPO="${CDN_BASE}/main/${ALPINE_ARCH}"
COMMUNITY_REPO="${CDN_BASE}/community/${ALPINE_ARCH}"
KERNEL_REPO="https://dl-cdn.alpinelinux.org/alpine/v${KERNEL_ALPINE_VERSION}/main/${ALPINE_ARCH}"

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
FIXTURES="${REPO_ROOT}/externals/test-fixtures"
Expand All @@ -65,7 +76,7 @@ INITRAMFS="${FIXTURES}/initramfs.cpio.gz"
# Tuples (not associative arrays) keep this working on bash 3.2 hosts (stock
# macOS /bin/bash) -- see tests/lib/bash-compat.sh.
PKGS=(
"main:linux-virt"
"kernel:linux-virt"
"main:busybox-static"
"main:dropbear"
"main:zlib"
Expand Down Expand Up @@ -189,6 +200,7 @@ apk_url()
case "$repo" in
main) echo "${MAIN_REPO}/${name}-${version}.apk" ;;
community) echo "${COMMUNITY_REPO}/${name}-${version}.apk" ;;
kernel) echo "${KERNEL_REPO}/${name}-${version}.apk" ;;
*)
echo "unknown repo: $repo" >&2
return 1
Expand All @@ -207,6 +219,7 @@ repo_url()
case "$1" in
main) echo "$MAIN_REPO" ;;
community) echo "$COMMUNITY_REPO" ;;
kernel) echo "$KERNEL_REPO" ;;
*)
echo "unknown repo: $1" >&2
return 1
Expand Down Expand Up @@ -384,7 +397,7 @@ main()

# Extract just the kernel-modules subtree from linux-virt.
local modstage linux_virt_ver
linux_virt_ver="$(pkg_version "main:linux-virt")"
linux_virt_ver="$(pkg_version "kernel:linux-virt")"
modstage="$(mktemp -d)"
tar xzf "$(apk_path linux-virt "$linux_virt_ver")" \
-C "$modstage" 'lib/modules' 2> /dev/null
Expand Down Expand Up @@ -472,7 +485,7 @@ EOF
if [ ! -s "${KERNEL_DIR}/vmlinuz-virt" ] || [ "${FORCE:-0}" = "1" ] || [ "$REBUILD" = 1 ]; then
log "extract kernel"
local linux_virt_ver
linux_virt_ver="$(pkg_version "main:linux-virt")"
linux_virt_ver="$(pkg_version "kernel:linux-virt")"
rm -rf "${KERNEL_DIR}/work"
mkdir -p "${KERNEL_DIR}/work"
tar xzf "$(apk_path linux-virt "$linux_virt_ver")" \
Expand Down
9 changes: 9 additions & 0 deletions tests/test-comprehensive.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ int main(int argc, char *argv[])
/* x86_64 test binary: uname returns "x86_64" */
CHECK(!strcmp(uts.machine, "x86_64"), "machine == x86_64");
#endif
/* elfuse pins its reported release to the 6.18 LTS baseline. Under the
* qemu reference lane the kernel is a real one and reports its own.
*/
if (!strcmp(uts.nodename, "elfuse")) {
int major = 0, minor = 0;
CHECK(sscanf(uts.release, "%d.%d", &major, &minor) == 2,
"release parses");
CHECK(major > 6 || (major == 6 && minor >= 18), "release >= 6.18");
}

/* 4. PID */
CHECK(getpid() > 0, "getpid > 0");
Expand Down
3 changes: 2 additions & 1 deletion tests/test-poll.c
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ int main(void)
if (setpgid(0, 0) == -1 && errno == EPERM)
PASS();
else if (setpgid(0, 0) == 0)
PASS(); /* Also acceptable (lima/native may behave differently) */
PASS(); /* not the session leader when a launcher owns the session
*/
else
FAIL("setpgid unexpected result");
}
Expand Down
15 changes: 15 additions & 0 deletions tests/test-proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <errno.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/utsname.h>

#include "test-harness.h"
#include "test-util.h"
Expand Down Expand Up @@ -110,6 +111,20 @@ int main(void)
FAIL("read failed");
}

/* /proc/version and uname(2) describe one kernel, so the release the
* banner carries has to be the release uname reports.
*/
TEST("/proc/version matches uname");
{
char buf[512];
struct utsname uts;
ssize_t n = read_file_nul("/proc/version", buf, sizeof(buf));
if (n > 0 && uname(&uts) == 0)
EXPECT_TRUE(strstr(buf, uts.release), "release disagrees");
else
FAIL("read or uname failed");
}

/* /proc/filesystems: should contain at least one fs type */
TEST("/proc/filesystems");
{
Expand Down
Loading