-
Notifications
You must be signed in to change notification settings - Fork 15
Add OFD lock support to fcntl and fix l_pid on conflict #183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1017,6 +1017,130 @@ int64_t sys_dup3(int oldfd, int newfd, int linux_flags) | |
| return newfd; | ||
| } | ||
|
|
||
| /* Translate a Linux struct flock (aarch64) at `arg` to macOS layout, run | ||
| * fcntl(host_fd, mac_cmd, ...), and for a GETLK command write the result | ||
| * back translated to Linux layout. Shared by the traditional (F_GETLK/ | ||
| * F_SETLK/F_SETLKW) and OFD (F_OFD_GETLK/F_OFD_SETLK/F_OFD_SETLKW) lock | ||
| * commands, which differ only in the macOS cmd values and in how l_pid is | ||
| * reported back for a GETLK conflict. | ||
| * | ||
| * Linux aarch64 layout: {short l_type, short l_whence, | ||
| * long l_start, long l_len, int l_pid, pad[4]} | ||
| * macOS layout: {off_t l_start, off_t l_len, pid_t l_pid, | ||
| * short l_type, short l_whence} | ||
| * Use guest_read/guest_write (not guest_ptr) to safely handle structs that | ||
| * span 2MiB page table block boundaries. | ||
| */ | ||
| static int64_t fcntl_flock_op(guest_t *g, | ||
| host_fd_ref_t *host_ref, | ||
| uint64_t arg, | ||
| int mac_cmd, | ||
| bool is_getlk, | ||
| bool is_ofd) | ||
| { | ||
| uint8_t lflock[32]; /* Linux struct flock is 32 bytes on aarch64 */ | ||
| if (guest_read_small(g, arg, lflock, sizeof(lflock)) < 0) | ||
| return -LINUX_EFAULT; | ||
|
|
||
| int16_t l_type, l_whence; | ||
| int64_t l_start, l_len; | ||
| int32_t l_pid; | ||
| memcpy(&l_type, lflock + 0, 2); | ||
| memcpy(&l_whence, lflock + 2, 2); | ||
| memcpy(&l_start, lflock + 8, 8); /* offset 8 due to padding */ | ||
| memcpy(&l_len, lflock + 16, 8); | ||
| memcpy(&l_pid, lflock + 24, 4); | ||
|
|
||
| /* Linux rejects F_OFD_GETLK/SETLK/SETLKW requests with a nonzero l_pid: | ||
| * OFD locks are owned by the open file description, not a process, so | ||
| * the field is reserved on input (fs/locks.c fcntl_getlk/fcntl_setlk | ||
| * both `return -EINVAL` on a nonzero request l_pid for these commands). | ||
| */ | ||
| if (is_ofd && l_pid != 0) | ||
| return -LINUX_EINVAL; | ||
|
|
||
| /* l_type constants differ between Linux and macOS/BSD: | ||
| * Linux: F_RDLCK=0, F_WRLCK=1, F_UNLCK=2 | ||
| * macOS: F_RDLCK=1, F_UNLCK=2, F_WRLCK=3 | ||
| * Passing the Linux value straight through makes a Linux F_RDLCK (0) an | ||
| * invalid type on macOS, which fcntl() rejects with EINVAL. This is the | ||
| * lock POSIX databases (e.g. SQLite) take first, so it must map. | ||
| */ | ||
| short mac_type; | ||
| switch (l_type) { | ||
| case 0: /* LINUX_F_RDLCK */ | ||
| mac_type = F_RDLCK; | ||
| break; | ||
| case 1: /* LINUX_F_WRLCK */ | ||
|
Comment on lines
+1034
to
+1074
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Traditional
|
||
| mac_type = F_WRLCK; | ||
| break; | ||
| case 2: /* LINUX_F_UNLCK */ | ||
| mac_type = F_UNLCK; | ||
| break; | ||
| default: | ||
| return -LINUX_EINVAL; | ||
| } | ||
|
|
||
| struct flock mac_fl = { | ||
| .l_start = l_start, | ||
| .l_len = l_len, | ||
| .l_pid = 0, | ||
| .l_type = mac_type, | ||
| .l_whence = l_whence, /* SEEK_SET=0, SEEK_CUR=1, SEEK_END=2 same */ | ||
| }; | ||
|
|
||
| if (fcntl(host_ref->fd, mac_cmd, &mac_fl) < 0) | ||
| return linux_errno(); | ||
|
|
||
| if (!is_getlk) | ||
| return 0; | ||
|
|
||
| /* Map macOS l_type back to Linux constants (see above). */ | ||
| int16_t rt; | ||
| switch (mac_fl.l_type) { | ||
| case F_RDLCK: | ||
| rt = 0; /* LINUX_F_RDLCK */ | ||
| break; | ||
| case F_WRLCK: | ||
| rt = 1; /* LINUX_F_WRLCK */ | ||
| break; | ||
| default: | ||
| rt = 2; /* LINUX_F_UNLCK */ | ||
| break; | ||
| } | ||
| int16_t rw = mac_fl.l_whence; | ||
| int64_t rs = mac_fl.l_start, rl = mac_fl.l_len; | ||
| int32_t rp; | ||
| if (is_ofd) { | ||
| /* OFD locks are owned by the open file description, not a single | ||
| * process, so Linux always reports l_pid=-1 on a conflicting | ||
| * F_OFD_GETLK lock instead of leaking a host PID to the guest. | ||
| */ | ||
| rp = (rt == 2) ? 0 : -1; | ||
| } else if (rt == 2) { | ||
| rp = (int32_t) mac_fl.l_pid; /* F_UNLCK: no conflict to translate */ | ||
| } else { | ||
| /* mac_fl.l_pid is a raw host PID, meaningless to guest code that | ||
| * treats it as a real PID (e.g. a liveness check via kill(pid, 0)). | ||
| * Translate it to the conflicting process's guest PID when it is | ||
| * part of this guest's fork family; fall back to the host PID only | ||
| * when the lock holder cannot be resolved (e.g. an unrelated host | ||
| * process), since no guest identity exists for it to report. | ||
| */ | ||
| int64_t gpid = proc_host_to_guest_pid((pid_t) mac_fl.l_pid); | ||
| rp = (gpid > 0) ? (int32_t) gpid : (int32_t) mac_fl.l_pid; | ||
| } | ||
| memset(lflock, 0, sizeof(lflock)); | ||
| memcpy(lflock + 0, &rt, 2); | ||
| memcpy(lflock + 2, &rw, 2); | ||
| memcpy(lflock + 8, &rs, 8); | ||
| memcpy(lflock + 16, &rl, 8); | ||
| memcpy(lflock + 24, &rp, 4); | ||
| if (guest_write_small(g, arg, lflock, sizeof(lflock)) < 0) | ||
| return -LINUX_EFAULT; | ||
| return 0; | ||
| } | ||
|
|
||
| int64_t sys_fcntl(guest_t *g, int fd, int cmd, uint64_t arg) | ||
| { | ||
| if (!RANGE_CHECK(fd, 0, FD_TABLE_SIZE)) | ||
|
|
@@ -1196,95 +1320,28 @@ int64_t sys_fcntl(guest_t *g, int fd, int cmd, uint64_t arg) | |
| host_fd_ref_t host_ref; | ||
| if (host_fd_ref_open(fd, &host_ref) < 0) | ||
| return -LINUX_EBADF; | ||
| /* Translate Linux struct flock (aarch64) to macOS struct flock. Linux | ||
| * aarch64 layout: {short l_type, short l_whence, | ||
| * long l_start, long l_len, int l_pid, pad[4]} | ||
| * macOS layout: {off_t l_start, off_t l_len, pid_t l_pid, | ||
| * short l_type, short l_whence} | ||
| * Use guest_read/guest_write (not guest_ptr) to safely handle structs | ||
| * that span 2MiB page table block boundaries. | ||
| */ | ||
| uint8_t lflock[32]; /* Linux struct flock is 32 bytes on aarch64 */ | ||
| if (guest_read_small(g, arg, lflock, sizeof(lflock)) < 0) | ||
| return -LINUX_EFAULT; | ||
|
|
||
| /* Read Linux flock fields */ | ||
| int16_t l_type, l_whence; | ||
| int64_t l_start, l_len; | ||
| memcpy(&l_type, lflock + 0, 2); | ||
| memcpy(&l_whence, lflock + 2, 2); | ||
| memcpy(&l_start, lflock + 8, 8); /* offset 8 due to padding */ | ||
| memcpy(&l_len, lflock + 16, 8); | ||
|
|
||
| /* l_type constants differ between Linux and macOS/BSD: | ||
| * Linux: F_RDLCK=0, F_WRLCK=1, F_UNLCK=2 | ||
| * macOS: F_RDLCK=1, F_UNLCK=2, F_WRLCK=3 | ||
| * Passing the Linux value straight through makes a Linux F_RDLCK (0) an | ||
| * invalid type on macOS, which fcntl() rejects with EINVAL. This is the | ||
| * lock POSIX databases (e.g. SQLite) take first, so it must map. | ||
| */ | ||
| short mac_type; | ||
| switch (l_type) { | ||
| case 0: /* LINUX_F_RDLCK */ | ||
| mac_type = F_RDLCK; | ||
| break; | ||
| case 1: /* LINUX_F_WRLCK */ | ||
| mac_type = F_WRLCK; | ||
| break; | ||
| case 2: /* LINUX_F_UNLCK */ | ||
| mac_type = F_UNLCK; | ||
| break; | ||
| default: | ||
| host_fd_ref_close(&host_ref); | ||
| return -LINUX_EINVAL; | ||
| } | ||
|
|
||
| struct flock mac_fl = { | ||
| .l_start = l_start, | ||
| .l_len = l_len, | ||
| .l_pid = 0, | ||
| .l_type = mac_type, | ||
| .l_whence = l_whence, /* SEEK_SET=0, SEEK_CUR=1, SEEK_END=2 same */ | ||
| }; | ||
|
|
||
| int mac_cmd = (cmd == 5) ? F_GETLK : (cmd == 6) ? F_SETLK : F_SETLKW; | ||
| if (fcntl(host_ref.fd, mac_cmd, &mac_fl) < 0) { | ||
| host_fd_ref_close(&host_ref); | ||
| return linux_errno(); | ||
| } | ||
|
|
||
| /* For F_GETLK, write back the result */ | ||
| if (cmd == 5) { | ||
| /* Map macOS l_type back to Linux constants (see above). */ | ||
| int16_t rt; | ||
| switch (mac_fl.l_type) { | ||
| case F_RDLCK: | ||
| rt = 0; /* LINUX_F_RDLCK */ | ||
| break; | ||
| case F_WRLCK: | ||
| rt = 1; /* LINUX_F_WRLCK */ | ||
| break; | ||
| default: | ||
| rt = 2; /* LINUX_F_UNLCK */ | ||
| break; | ||
| } | ||
| int16_t rw = mac_fl.l_whence; | ||
| int64_t rs = mac_fl.l_start, rl = mac_fl.l_len; | ||
| int32_t rp = mac_fl.l_pid; | ||
| memset(lflock, 0, sizeof(lflock)); | ||
| memcpy(lflock + 0, &rt, 2); | ||
| memcpy(lflock + 2, &rw, 2); | ||
| memcpy(lflock + 8, &rs, 8); | ||
| memcpy(lflock + 16, &rl, 8); | ||
| memcpy(lflock + 24, &rp, 4); | ||
| if (guest_write_small(g, arg, lflock, sizeof(lflock)) < 0) { | ||
| host_fd_ref_close(&host_ref); | ||
| return -LINUX_EFAULT; | ||
| } | ||
| } | ||
| int64_t rc = | ||
| fcntl_flock_op(g, &host_ref, arg, mac_cmd, cmd == 5, false); | ||
| host_fd_ref_close(&host_ref); | ||
| return 0; | ||
| return rc; | ||
| } | ||
| #if defined(F_OFD_GETLK) && defined(F_OFD_SETLK) && defined(F_OFD_SETLKW) | ||
| case 36: /* F_OFD_GETLK */ | ||
| case 37: /* F_OFD_SETLK */ | ||
| case 38: { /* F_OFD_SETLKW */ | ||
| host_fd_ref_t host_ref; | ||
| if (host_fd_ref_open(fd, &host_ref) < 0) | ||
| return -LINUX_EBADF; | ||
| int mac_cmd = (cmd == 36) ? F_OFD_GETLK | ||
| : (cmd == 37) ? F_OFD_SETLK | ||
| : F_OFD_SETLKW; | ||
| int64_t rc = | ||
| fcntl_flock_op(g, &host_ref, arg, mac_cmd, cmd == 36, true); | ||
| host_fd_ref_close(&host_ref); | ||
| return rc; | ||
| } | ||
| #endif | ||
| case 8: { /* F_SETOWN */ | ||
| /* SIGIO/SIGURG delivery owner. The arg is a signed value passed by | ||
| * value: pid > 0 targets a process, pid < 0 targets a process group, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -514,6 +514,35 @@ static void registry_parse_cb(char *rec, void *vctx) | |||||||
| c->entries[idx].pgid = (int64_t) pg; | ||||||||
| } | ||||||||
|
|
||||||||
| typedef struct { | ||||||||
| pid_t target; | ||||||||
| int64_t guest_pid; | ||||||||
| bool found; | ||||||||
| } registry_find_ctx_t; | ||||||||
|
|
||||||||
| /* Locate @target's guest pid without registry_parse_cb's per-record | ||||||||
| * kill(2) liveness probe: that check exists to build a filtered live- | ||||||||
| * membership list for group-signal delivery, but a host_pid ->guest_pid | ||||||||
| * lookup is only ever done for a pid the caller just observed to be alive | ||||||||
| * (e.g. it holds a conflicting file lock right now), so it is redundant | ||||||||
| * here. proc_host_to_guest_pid still verifies the match via proc_pidpath | ||||||||
| * to guard against the pid having been recycled. | ||||||||
| */ | ||||||||
| static void registry_find_by_host_cb(char *rec, void *vctx) | ||||||||
| { | ||||||||
| registry_find_ctx_t *c = vctx; | ||||||||
| long hp; | ||||||||
| long long gp, pg; | ||||||||
| if (sscanf(rec, "%ld %lld %lld", &hp, &gp, &pg) != 3) | ||||||||
| return; | ||||||||
| if (hp <= 0 || hp > INT_MAX || pg < 0 || pg > INT_MAX) | ||||||||
| return; | ||||||||
| if ((pid_t) hp != c->target) | ||||||||
| return; | ||||||||
| c->guest_pid = (int64_t) gp; | ||||||||
| c->found = true; | ||||||||
| } | ||||||||
|
|
||||||||
| /* Parse the whole registry from @fd (caller holds an flock) into @entries, | ||||||||
| * keeping one record per live host pid. | ||||||||
| * | ||||||||
|
|
@@ -805,6 +834,46 @@ int proc_get_namespace_targets(proc_signal_target_t *out, | |||||||
| return count; | ||||||||
| } | ||||||||
|
|
||||||||
| int64_t proc_host_to_guest_pid(pid_t host_pid) | ||||||||
| { | ||||||||
| pthread_mutex_lock(&pid_lock); | ||||||||
| proc_entry_t *entry = proc_find_host_entry(host_pid); | ||||||||
| int64_t result = entry ? entry->guest_pid : -1; | ||||||||
| pthread_mutex_unlock(&pid_lock); | ||||||||
| if (result != -1) | ||||||||
| return result; | ||||||||
|
|
||||||||
| char path[PATH_MAX]; | ||||||||
| if (!process_registry_path(path, sizeof(path))) | ||||||||
| return -1; | ||||||||
| int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: A first lock-conflict PID lookup in a recycled namespace-owner process can read a stale process registry and return the wrong guest PID. This new registry reader should mirror the existing registry readers by calling Prompt for AI agents
Suggested change
|
||||||||
| if (fd < 0) | ||||||||
| return -1; | ||||||||
| if (flock_retry(fd, LOCK_SH) != 0) { | ||||||||
| close(fd); | ||||||||
| return -1; | ||||||||
| } | ||||||||
| registry_find_ctx_t ctx = {.target = host_pid}; | ||||||||
| for_each_record(fd, registry_find_by_host_cb, &ctx); | ||||||||
| flock_retry(fd, LOCK_UN); | ||||||||
| close(fd); | ||||||||
| if (!ctx.found) | ||||||||
| return -1; | ||||||||
|
|
||||||||
| /* Guard against host pid reuse: only trust the hit if the pid still | ||||||||
| * runs this elfuse binary, same check as proc_get_namespace_targets. | ||||||||
| */ | ||||||||
| char our_path[PROC_PIDPATHINFO_MAXSIZE]; | ||||||||
| int our_len = proc_pidpath(getpid(), our_path, sizeof(our_path)); | ||||||||
| if (our_len <= 0) | ||||||||
| return -1; | ||||||||
| char ppath[PROC_PIDPATHINFO_MAXSIZE]; | ||||||||
| int plen = proc_pidpath(host_pid, ppath, sizeof(ppath)); | ||||||||
| if (plen != our_len || memcmp(ppath, our_path, (size_t) our_len)) | ||||||||
| return -1; | ||||||||
| return ctx.guest_pid; | ||||||||
| } | ||||||||
|
|
||||||||
| int proc_get_child_pids(pid_t *out, int max_pids) | ||||||||
| { | ||||||||
| /* Seed with direct children from the process table */ | ||||||||
|
|
||||||||
Uh oh!
There was an error while loading. Please reload this page.