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
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ SRCS := \
syscall/translate.c \
syscall/mem.c \
syscall/path.c \
syscall/fuse.c \
syscall/sidecar.c \
syscall/fs.c \
syscall/fs-stat.c \
Expand Down Expand Up @@ -149,6 +150,11 @@ $(BUILD_DIR)/test-pthread: tests/test-pthread.c | $(BUILD_DIR)
@echo " CROSS $< (with -lpthread)"
$(Q)$(CROSS_COMPILE)gcc -D_GNU_SOURCE -static -O2 -o $@ $< -lpthread

# test-fuse-basic runs a guest daemon thread and consumer in one process
$(BUILD_DIR)/test-fuse-basic: tests/test-fuse-basic.c | $(BUILD_DIR)
@echo " CROSS $< (with -lpthread)"
$(Q)$(CROSS_COMPILE)gcc -D_GNU_SOURCE -static -O2 -o $@ $< -lpthread

# test-sched-policy spawns a pthread to verify per-thread TID lookup
$(BUILD_DIR)/test-sched-policy: tests/test-sched-policy.c | $(BUILD_DIR)
@echo " CROSS $< (with -lpthread)"
Expand Down
4 changes: 3 additions & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ What they do:
downloaded into `build/busybox` on first run.
- `make test-busybox`: just the BusyBox suite, useful when iterating on a
single applet failure without rerunning the unit suite
- `make test-fuse-alpine`: validate guest `/dev/fuse` + `mount("fuse")`
against the Alpine musl sysroot fixture
- `make test-gdbstub`: debugger integration checks against the built-in GDB stub
- `make test-matrix`: broader `elfuse` and QEMU cross-check
- `make lint`: static analysis through `clang-tidy`
Expand All @@ -61,7 +63,7 @@ make elfuse
make check
```

For changes that touch procfs, path handling, networking, dynamic linking, or
For changes that touch procfs, path handling, `/dev`, FUSE, networking, dynamic linking, or
guest process semantics, run the matrix as well:

```sh
Expand Down
12 changes: 11 additions & 1 deletion mk/tests.mk
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
test-full test-multi-vcpu test-rwx test-sysroot-rename \
test-case-collision test-case-collision-fallback test-sysroot-create-paths \
test-proctitle-low-stack \
test-sysroot-procfs-exec test-timeout-disable \
test-sysroot-procfs-exec test-timeout-disable test-fuse-alpine \
test-sysroot-nofollow test-sysroot-chdir perf

## Build and run the assembly hello world test
Expand All @@ -29,6 +29,8 @@ check: $(ELFUSE_BIN) $(TEST_DEPS) check-syscall-coverage
@$(MAKE) --no-print-directory test-busybox
@printf "\n$(BLUE)━━━ sysroot procfs exec validation ━━━$(RESET)\n"
@$(MAKE) --no-print-directory test-sysroot-procfs-exec
@printf "\n$(BLUE)━━━ Alpine sysroot FUSE validation ━━━$(RESET)\n"
@$(MAKE) --no-print-directory test-fuse-alpine
@printf "\n$(BLUE)━━━ timeout=0 validation ━━━$(RESET)\n"
@$(MAKE) --no-print-directory test-timeout-disable

Expand Down Expand Up @@ -297,6 +299,14 @@ test-dynamic: $(ELFUSE_BIN)
@printf "$(BLUE)▸ Running$(RESET) dynamic hello-dynamic (--sysroot)\n"
$(ELFUSE_BIN) --sysroot $(SYSROOT_DIR) $(GUEST_DYNAMIC_TESTS)/bin/hello-dynamic

## Run guest FUSE validation against the Alpine musl sysroot
test-fuse-alpine: $(ELFUSE_BIN) $(BUILD_DIR)/test-fuse-basic
@if [ -z "$(SYSROOT_DIR)" ] || [ ! -d "$(SYSROOT_DIR)" ]; then \
printf "$(YELLOW)SKIP$(RESET) Alpine sysroot not found. Set SYSROOT_DIR=/path/to/sysroot or run tests/fetch-fixtures.sh.\n"; \
exit 0; \
fi
@bash tests/test-fuse-alpine.sh $(ELFUSE_BIN) $(SYSROOT_DIR) $(BUILD_DIR)/test-fuse-basic

## Run dynamically-linked coreutils tests (--sysroot)
test-dynamic-coreutils: $(ELFUSE_BIN)
@if [ -z "$(SYSROOT_DIR)" ] || [ ! -d "$(SYSROOT_DIR)" ]; then \
Expand Down
6 changes: 3 additions & 3 deletions src/core/guest.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,9 @@ static inline void guest_pt_gen_bump(guest_t *g)
* a synchronous IPI into a sibling vCPU thread, so the window remains.
* The guest is responsible for serializing concurrent PT mutations
* against concurrent accesses (futex / pthread_mutex), which is the same
* contract real Linux requires of well-behaved multi-threaded code. See
* TODO.md "Bounded retry on stale TLB data abort" (P3 hardening) for the
* tracked follow-up if a workload ever surfaces an actual reproducer.
* contract real Linux requires of well-behaved multi-threaded code. A
* bounded-retry on stale-TLB data aborts is a known hardening direction
* if a workload ever surfaces an actual reproducer.
*/
extern _Thread_local tlbi_request_t cpu_tlbi_req;

Expand Down
38 changes: 29 additions & 9 deletions src/runtime/procemu.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@

#include "syscall/abi.h"
#include "syscall/fd.h"
#include "syscall/fuse.h"
#include "syscall/internal.h"
#include "syscall/proc.h"
#include "syscall/sys.h"
Expand Down Expand Up @@ -2000,6 +2001,7 @@ int proc_intercept_open(const guest_t *g,
"\tproc\n"
"\tsysfs\n"
"\tdevtmpfs\n"
"\tfuse\n"
"\text4\n"
"\tvfat\n");
}
Expand All @@ -2009,23 +2011,35 @@ int proc_intercept_open(const guest_t *g,
* - type source super_options
*/
if (!strcmp(path, "/proc/self/mountinfo")) {
return proc_emit_literal(
char buf[8192];
size_t off = (size_t) snprintf(
buf, sizeof(buf),
"1 0 0:1 / / rw,relatime - ext4 /dev/root rw\n"
"2 1 0:2 / /proc rw,nosuid,nodev,noexec - proc proc rw\n"
"3 1 0:3 / /tmp rw,nosuid,nodev - tmpfs tmpfs rw\n"
"4 1 0:4 / /dev rw,nosuid - devtmpfs devtmpfs rw\n"
"5 4 0:5 / /dev/shm rw,nosuid,nodev - tmpfs tmpfs rw\n");
if (off >= sizeof(buf) ||
fuse_append_mountinfo(buf, sizeof(buf), &off) < 0)
return -1;
return proc_synthetic_fd(buf, off);
}

/* /proc/mounts, /etc/mtab -> synthetic mount table */
if (!strcmp(path, "/proc/mounts") || !strcmp(path, "/proc/self/mounts") ||
!strcmp(path, "/etc/mtab")) {
return proc_emit_literal(
"/ / ext4 rw,relatime 0 0\n"
"proc /proc proc rw,nosuid,nodev,noexec 0 0\n"
"tmpfs /tmp tmpfs rw,nosuid,nodev 0 0\n"
"devtmpfs /dev devtmpfs rw,nosuid 0 0\n"
"tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0\n");
char buf[8192];
size_t off =
(size_t) snprintf(buf, sizeof(buf),
"/ / ext4 rw,relatime 0 0\n"
"proc /proc proc rw,nosuid,nodev,noexec 0 0\n"
"tmpfs /tmp tmpfs rw,nosuid,nodev 0 0\n"
"devtmpfs /dev devtmpfs rw,nosuid 0 0\n"
"tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0\n");
if (off >= sizeof(buf) ||
fuse_append_mounts(buf, sizeof(buf), &off) < 0)
return -1;
return proc_synthetic_fd(buf, off);
}

/* OOM nodes share one stored adjustment.
Expand Down Expand Up @@ -2114,12 +2128,15 @@ int proc_intercept_open(const guest_t *g,
}
}

int mnt_id = 0;
if (fuse_fd_mnt_id(n, &mnt_id) < 0)
mnt_id = 0;
return proc_emit_fmt(
"pos:\t%lld\n"
"flags:\t0%o\n"
"mnt_id:\t0\n"
"mnt_id:\t%d\n"
"%s",
(long long) pos, snap.linux_flags, extra);
(long long) pos, snap.linux_flags, mnt_id, extra);
}

/* /proc/self/fdinfo -> directory listing. Each open gets its own scratch
Expand Down Expand Up @@ -2367,6 +2384,9 @@ int proc_intercept_stat(const char *path, struct stat *st)
* irrelevant here; callers need stat to succeed before opening the
* synthetic file.
*/
if (!strcmp(path, "/dev/fuse"))
return fuse_proc_stat(st);

/* /dev/shm is a directory */
if (!strcmp(path, "/dev/shm") || !strcmp(path, "/dev/shm/")) {
stat_fill_proc_dir(st, 01777, 2,
Expand Down
6 changes: 6 additions & 0 deletions src/syscall/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#define SYS_symlinkat 36
#define SYS_linkat 37
#define SYS_renameat 38
#define SYS_mount 40
#define SYS_truncate 45
#define SYS_statfs 43
#define SYS_fstatfs 44
Expand Down Expand Up @@ -278,12 +279,14 @@ typedef struct {
#define LINUX_EBUSY 16
#define LINUX_EEXIST 17
#define LINUX_EXDEV 18
#define LINUX_ENODEV 19
#define LINUX_ENOTDIR 20
#define LINUX_EINVAL 22
#define LINUX_EMFILE 24
#define LINUX_ENOTTY 25
#define LINUX_EFBIG 27
#define LINUX_ENOSPC 28
#define LINUX_ESPIPE 29
#define LINUX_ERANGE 34
#define LINUX_EDEADLK 35
#define LINUX_ENAMETOOLONG 36
Expand Down Expand Up @@ -629,6 +632,9 @@ typedef struct {
#define FD_PATH 11
#define FD_NETLINK 12
#define FD_PIDFD 13
#define FD_FUSE_DEV 14
#define FD_FUSE_FILE 15
#define FD_FUSE_DIR 16
#define FD_VIRTUAL_PATH_MAX 64

/* File sealing flags (F_SEAL_*) for memfd_create. Tracked per-FD. */
Expand Down
1 change: 1 addition & 0 deletions src/syscall/dispatch.tbl
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ SYS_symlinkat sc_symlinkat 1
SYS_linkat sc_linkat 1
SYS_renameat sc_renameat 1
SYS_renameat2 sc_renameat2 1
SYS_mount sc_mount 1
SYS_readlinkat sc_readlinkat 1
SYS_newfstatat sc_newfstatat 1
SYS_fstat sc_fstat 0
Expand Down
45 changes: 39 additions & 6 deletions src/syscall/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include "syscall/abi.h"
#include "syscall/exec.h"
#include "syscall/fuse.h"
#include "syscall/internal.h"
#include "syscall/path.h"
#include "syscall/proc.h"
Expand Down Expand Up @@ -118,6 +119,9 @@ int64_t sys_execve(hv_vcpu_t vcpu,

char path_host_buf[LINUX_PATH_MAX];
const char *path_host = path;
bool path_host_temp = false;
char interp_host_buf[LINUX_PATH_MAX];
bool interp_host_temp = false;

#define MAX_ARGS 256
#define MAX_ENVS 4096
Expand Down Expand Up @@ -172,7 +176,15 @@ int64_t sys_execve(hv_vcpu_t vcpu,
err = linux_errno();
goto fail;
}
str_copy_trunc(path_host_buf, tx.host_path, sizeof(path_host_buf));
if (tx.fuse_path) {
err = fuse_materialize_path(tx.intercept_path, path_host_buf,
sizeof(path_host_buf));
if (err < 0)
goto fail;
path_host_temp = true;
} else {
str_copy_trunc(path_host_buf, tx.host_path, sizeof(path_host_buf));
}
path_host = path_host_buf;
}
if (!path_host) {
Expand Down Expand Up @@ -304,9 +316,23 @@ int64_t sys_execve(hv_vcpu_t vcpu,
err = linux_errno();
goto fail;
}
str_copy_trunc(path_host_buf, interp_tx.host_path,
sizeof(path_host_buf));
path_host = path_host_buf;
if (path_host_temp) {
unlink(path_host_buf);
path_host_temp = false;
}
if (interp_tx.fuse_path) {
err =
fuse_materialize_path(interp_tx.intercept_path, interp_host_buf,
sizeof(interp_host_buf));
if (err < 0)
goto fail;
interp_host_temp = true;
path_host = interp_host_buf;
} else {
str_copy_trunc(path_host_buf, interp_tx.host_path,
sizeof(path_host_buf));
path_host = path_host_buf;
}

if (elf_load(path_host, &elf_info) < 0) {
err = -LINUX_ENOENT;
Expand Down Expand Up @@ -383,6 +409,10 @@ int64_t sys_execve(hv_vcpu_t vcpu,
*/
if (0) {
fail:
if (path_host_temp)
unlink(path_host_buf);
if (interp_host_temp)
unlink(interp_host_buf);
free(argv_buf);
free(envp_buf);
return err;
Expand Down Expand Up @@ -693,8 +723,7 @@ int64_t sys_execve(hv_vcpu_t vcpu,
elf_info.segments[i].gpa + elf_info.segments[i].memsz +
elf_load_base,
elf_pf_to_prot(elf_info.segments[i].flags),
LINUX_MAP_PRIVATE, elf_info.segments[i].offset,
path_host);
LINUX_MAP_PRIVATE, elf_info.segments[i].offset, path);
}
/* interp_resolved was computed before guest_reset so no filesystem lookup
* is needed after the point of no return.
Expand Down Expand Up @@ -803,6 +832,10 @@ int64_t sys_execve(hv_vcpu_t vcpu,
log_debug("execve: loaded %s, entry=0x%llx sp=0x%llx", path_host,
(unsigned long long) entry_ipa, (unsigned long long) sp_ipa);

if (path_host_temp)
unlink(path_host_buf);
if (interp_host_temp)
unlink(interp_host_buf);
free(argv_buf);
free(envp_buf);

Expand Down
29 changes: 27 additions & 2 deletions src/syscall/fs-stat.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "runtime/procemu.h"

#include "syscall/abi.h"
#include "syscall/fuse.h"
#include "syscall/fs.h"
#include "syscall/internal.h"
#include "syscall/path.h"
Expand Down Expand Up @@ -182,13 +183,27 @@ static int64_t stat_at_path(guest_t *g,
sizeof(path), &pathp) < 0)
return -LINUX_EFAULT;

if (pathp[0] == '/' && fuse_path_matches_mount(pathp)) {
int frc = fuse_stat_path(pathp, mac_st, flags);
if (frc < 0)
return frc;
return 0;
}

path_translation_t tx;
if (path_translate_at(dirfd, pathp,
(flags & LINUX_AT_SYMLINK_NOFOLLOW) ? PATH_TR_NOFOLLOW
: PATH_TR_NONE,
&tx) < 0)
return linux_errno();

if (tx.fuse_path) {
int frc = fuse_stat_path(tx.intercept_path, mac_st, flags);
if (frc < 0)
return frc;
return 0;
}

if (tx.proc_resolved == 0 && dirfd == LINUX_AT_FDCWD && pathp[0] != '/' &&
pathp[0] != '\0' && !proc_get_sysroot()) {
int mac_flags = translate_at_flags(flags);
Expand Down Expand Up @@ -244,13 +259,21 @@ static int64_t stat_at_path(guest_t *g,

int64_t sys_fstat(guest_t *g, int fd, uint64_t stat_gva)
{
struct stat mac_st;
int frc = fuse_fstat_fd(fd, &mac_st);
if (frc == 0) {
if (write_linux_stat(g, stat_gva, &mac_st) < 0)
return -LINUX_EFAULT;
return 0;
}
if (frc != -LINUX_EBADF)
return frc;

host_fd_ref_t host_ref;
if (host_fd_ref_open(fd, &host_ref) < 0) {
log_debug("fstat(%d): invalid guest fd", fd);
return -LINUX_EBADF;
}

struct stat mac_st;
if (fstat(host_ref.fd, &mac_st) < 0) {
log_debug("fstat(%d->%d): host fstat failed errno=%d", fd, host_ref.fd,
errno);
Expand Down Expand Up @@ -297,6 +320,8 @@ int64_t sys_statfs(guest_t *g, uint64_t path_gva, uint64_t buf_gva)
path_translation_t tx;
if (path_translate_at(LINUX_AT_FDCWD, path, PATH_TR_NONE, &tx) < 0)
return linux_errno();
if (tx.fuse_path)
return -LINUX_ENOSYS;

struct statfs mac_st;
if (statfs(tx.host_path, &mac_st) < 0)
Expand Down
Loading
Loading