Skip to content

Add OFD lock support to fcntl and fix l_pid on conflict#183

Merged
jserv merged 1 commit into
sysprog21:mainfrom
Max042004:fix-ofd-lock-pid
Jul 9, 2026
Merged

Add OFD lock support to fcntl and fix l_pid on conflict#183
jserv merged 1 commit into
sysprog21:mainfrom
Max042004:fix-ofd-lock-pid

Conversation

@Max042004

@Max042004 Max042004 commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Adds F_OFD_GETLK/F_OFD_SETLK/F_OFD_SETLKW (Linux cmd 36/37/38) to sys_fcntl, which previously fell through to EINVAL. The three cases are guarded by #ifdef F_OFD_GETLK etc. so hosts with older SDKs lacking these constants still build and fall back to EINVAL. Since OFD locks belong to the open file description rather than a process, F_OFD_GETLK now forces the returned l_pid to -1 on a conflict instead of leaking the host PID, matching Linux.

The Linux<->macOS struct flock translation is shared with the existing F_GETLK/F_SETLK/F_SETLKW path via a new fcntl_flock_op() helper rather than duplicated.

Fixes #129

Test plan

  • Added tests/test-ofd-lock.c (registered in tests/manifest.txt), covering cross-fd OFD conflict detection, l_pid == -1 on conflict, unlock clearing the conflict, and F_OFD_SETLKW.
  • make check green, including the new test and the existing test-flock.
  • clang-format --dry-run --Werror clean.

Summary by cubic

Adds OFD lock support to sys_fcntl and fixes PID reporting for lock conflicts to match Linux. Restores SQLite WAL and other OFD users; fixes #129.

  • New Features

    • Support F_OFD_GETLK/F_OFD_SETLK/F_OFD_SETLKW (guarded by #ifdef).
    • Share Linux↔macOS struct flock translation via fcntl_flock_op().
  • Bug Fixes

    • For OFD GETLK, return l_pid = -1 on conflict and reject nonzero request l_pid with EINVAL.
    • For classic F_GETLK, report the conflicting owner as the guest PID via proc_host_to_guest_pid().
    • Tests added: tests/test-ofd-lock.c, updated tests/test-flock.c, and tests/test-matrix.sh updated to run it.

Written for commit bb90715. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

@jserv jserv requested a review from doanbaotrung July 9, 2026 08:51
@doanbaotrung

Copy link
Copy Markdown
Collaborator

1. Executed python test script

test_ofd_lock.py file contents:

import fcntl
import os
import struct

# Linux OFD Locking Constants
F_OFD_GETLK = 36
F_OFD_SETLK = 37
F_WRLCK = 1

path = '/tmp/test-ofd-python.db'

# Open two distinct open file descriptions for the same file
fd1 = os.open(path, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o644)
fd2 = os.open(path, os.O_RDWR, 0o644)

# Pack a Linux struct flock: hhxxxxqqii (32 bytes)
# l_type = F_WRLCK (1), l_whence = SEEK_SET (0), l_start = 0, l_len = 16, l_pid = 0
lock_data = struct.pack('hhxxxxqqii', F_WRLCK, 0, 0, 16, 0, 0)

print("Taking OFD write lock on fd1...")
fcntl.fcntl(fd1, F_OFD_SETLK, lock_data)

print("Querying conflicting lock on fd2...")
query_data = struct.pack('hhxxxxqqii', F_WRLCK, 0, 0, 16, 0, 0)
res = fcntl.fcntl(fd2, F_OFD_GETLK, query_data)

# Unpack the response
l_type, l_whence, l_start, l_len, l_pid, _ = struct.unpack('hhxxxxqqii', res)

print(f"Conflicting lock type: {l_type} (Expected: 1 for F_WRLCK)")
print(f"Conflicting lock PID: {l_pid} (Expected: -1 for OFD conflict)")

if l_type == F_WRLCK and l_pid == -1:
    print("RESULT: PASS")
else:
    print("RESULT: FAIL")

os.close(fd1)
os.close(fd2)
os.unlink(path)

Command:

elfuse/build/elfuse --sysroot ~/prefixes/ubuntu-arm64/rootfs /usr/bin/python3 test_ofd_lock.py

Output:

Taking OFD write lock on fd1...
Querying conflicting lock on fd2...
Conflicting lock type: 1 (Expected: 1 for F_WRLCK)
Conflicting lock PID: -1 (Expected: -1 for OFD conflict)
RESULT: PASS

2. Executed native host script

clang -Ielfuse/tests third_party/elfuse/tests/test-ofd-lock.c -o /tmp/host_test_ofd
/tmp/host_test_ofd
rm /tmp/host_test_ofd

Output Log:

  F_OFD_SETLK F_WRLCK            OK
  F_OFD_GETLK reports conflict from other OFD OK
  F_OFD_GETLK l_pid is -1 on conflict OK
  F_OFD_SETLK F_UNLCK            OK
  F_OFD_GETLK reports no conflict after unlock OK
  F_OFD_SETLKW F_WRLCK           OK
OK

test-ofd-lock: 7 passed, 0 failed - PASS

@Max042004

Copy link
Copy Markdown
Collaborator Author

Confirmed — verified against fs/locks.c (fcntl_getlk/fcntl_setlk both reject a nonzero request l_pid with EINVAL for F_OFD_GETLK/SETLK/SETLKW). Fixed in 1180fa2: fcntl_flock_op now reads the guest's l_pid and returns -EINVAL when is_ofd and l_pid != 0, with 3 new regression cases in test-ofd-lock.c.

Comment thread src/syscall/fs.c
Comment on lines +1034 to +1074
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 */

@doanbaotrung doanbaotrung Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Traditional F_GETLK Leaks Host PID to the Guest**

  • Location: fcntl_flock_op inside src/syscall/fs.c

  • Problem:
    For OFD locks (F_OFD_GETLK), the implementation correctly overrides the conflict PID with -1 (per Linux specification) to avoid leaking host PIDs.

    However, for traditional locks (F_GETLK), it reports:

    rp = mac_fl.l_pid;

    This passes the macOS host process ID directly to the guest. Because elfuse translates and virtualizes guest PIDs, the guest program (e.g., SQLite or other locking monitors) receives a raw host PID (e.g., 40548) instead of the child's guest PID (e.g., 2).

    If the guest tries to manage or check that PID (for example, via kill(conflict_pid, 0)), the PID translation gate in sc_kill looks for the host PID in the guest table, fails to resolve it, and returns ESRCH, even though the process is alive.

  • Proposed Fix:
    Add a proc_host_to_guest_pid helper to map the host PID back to its corresponding guest PID via the child process table and cross-process registry.

@Max042004

Copy link
Copy Markdown
Collaborator Author

Good catch — fixed in da96047. Added proc_host_to_guest_pid() (mirror of the existing proc_guest_to_host_pid()) to translate the conflicting lock's host PID back to a guest PID via the local child table + cross-process registry, so traditional F_GETLK now reports a PID the guest can actually resolve instead of a raw host PID. Also rebased onto main's test-matrix.sh restructuring and added a fork+pipe regression test in test-flock.c that fails without the fix.

@Max042004 Max042004 marked this pull request as draft July 9, 2026 14:59
@Max042004 Max042004 marked this pull request as ready for review July 9, 2026 15:04

@cubic-dev-ai cubic-dev-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.

1 issue found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/syscall/proc.c">

<violation number="1" location="src/syscall/proc.c:849">
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 `proc_registry_reset_if_owner(path)` before opening the file.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/syscall/proc.c
char path[PATH_MAX];
if (!process_registry_path(path, sizeof(path)))
return -1;
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 proc_registry_reset_if_owner(path) before opening the file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/syscall/proc.c, line 849:

<comment>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 `proc_registry_reset_if_owner(path)` before opening the file.</comment>

<file context>
@@ -805,6 +834,46 @@ int proc_get_namespace_targets(proc_signal_target_t *out,
+    char path[PATH_MAX];
+    if (!process_registry_path(path, sizeof(path)))
+        return -1;
+    int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
+    if (fd < 0)
+        return -1;
</file context>
Suggested change
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
proc_registry_reset_if_owner(path);
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);

@doanbaotrung

Copy link
Copy Markdown
Collaborator

I think it good now.

F_OFD_GETLK/SETLK/SETLKW (Linux cmd 36/37/38) fell through to the
default EINVAL case, so guest software relying on open file
description locks (e.g. SQLite's WAL mode) had no working lock path.

Add the three cases, guarded by #ifdef so hosts whose SDK predates
OFD lock support still build and fall back to EINVAL. Since OFD locks
belong to the open file description rather than a process, force the
returned l_pid to -1 on a conflicting F_OFD_GETLK instead of leaking
the host's raw PID, and reject a nonzero request l_pid with EINVAL,
matching fs/locks.c fcntl_getlk/fcntl_setlk on Linux.

Traditional (non-OFD) F_GETLK had the same PID leak: a conflicting
lock's l_pid was passed straight from the host's raw PID, which guest
code cannot resolve (e.g. a kill(pid, 0) liveness check hits the wrong
process). Add proc_host_to_guest_pid(), the mirror of the existing
proc_guest_to_host_pid(), to translate the lock holder back to a guest
PID via the local child table and the cross-process registry. The
registry fallback uses a dedicated single-target scan rather than the
existing registry_read_locked() path, since that helper's per-record
kill(2) liveness probe (needed for group-signal delivery) is redundant
here -- the target pid was just observed holding a live lock -- and
scales O(n) with registry size for no benefit; measured 44us -> 16us
at 53 live registry entries.

The struct flock translation is shared with the existing F_GETLK/
F_SETLK/F_SETLKW path via a new fcntl_flock_op() helper rather than
duplicated, since the two paths differ only in the macOS cmd values
and in how l_pid is validated/reported.

Fixes sysprog21#129

Rebased onto main's test-matrix.sh restructuring (portable tests moved
out of tests/manifest.txt into test-matrix.sh's run_unit_tests); added
test-ofd-lock there alongside test-flock and bumped EXPECTED_BASELINES.
@jserv jserv merged commit e16d91b into sysprog21:main Jul 9, 2026
9 checks passed
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.

Incorrect l_pid behavior in OFD file-lock support

3 participants