diff --git a/liblfi/arch/x64/arch_sys.h b/liblfi/arch/x64/arch_sys.h index 8094ca67..4f0d4f1e 100644 --- a/liblfi/arch/x64/arch_sys.h +++ b/liblfi/arch/x64/arch_sys.h @@ -25,6 +25,9 @@ enum { TUX_SYS_readv = 19, TUX_SYS_writev = 20, TUX_SYS_access = 21, + TUX_SYS_dup = 32, + TUX_SYS_dup2 = 33, + TUX_SYS_dup3 = 292, TUX_SYS_sched_yield = 24, TUX_SYS_mremap = 25, TUX_SYS_madvise = 28, @@ -82,6 +85,7 @@ enum { TUX_SYS_unlinkat = 263, TUX_SYS_renameat = 264, TUX_SYS_readlinkat = 267, + TUX_SYS_fchmodat = 268, TUX_SYS_faccessat = 269, TUX_SYS_set_robust_list = 273, TUX_SYS_utimensat = 280, diff --git a/liblfi/sys.c b/liblfi/sys.c index 0b3e424d..0ca03a4d 100644 --- a/liblfi/sys.c +++ b/liblfi/sys.c @@ -27,6 +27,8 @@ syshandle(struct TuxThread* p, uintptr_t sysno, uintptr_t a0, uintptr_t a1, SYS(brk, sys_brk(proc, a0)) SYS(openat, sys_openat(proc, a0, a1, a2, a3)) SYS(close, sys_close(proc, a0)) + SYS(dup, sys_dup(proc, a0)) + SYS(dup3, sys_dup3(proc, a0, a1, a2)) SYS(writev, sys_writev(proc, a0, a1, a2)) SYS(readv, sys_readv(proc, a0, a1, a2)) SYS(pread64, sys_pread64(proc, a0, a1, a2, a3)) @@ -37,6 +39,7 @@ syshandle(struct TuxThread* p, uintptr_t sysno, uintptr_t a0, uintptr_t a1, SYS(newfstatat, sys_newfstatat(proc, a0, a1, a2, a3)) SYS(fstat, sys_newfstatat(proc, a0, 0, a1, TUX_AT_EMPTY_PATH)) SYS(fchmod, sys_fchmod(proc, a0, a1)) + SYS(fchmodat, sys_fchmodat(proc, a0, a1, a2, a3)) SYS(truncate, sys_truncate(proc, a0, a1)) SYS(ftruncate, sys_ftruncate(proc, a0, a1)) SYS(fchown, sys_fchown(proc, a0, a1, a2)) diff --git a/liblfi/syscalls/strace.c b/liblfi/syscalls/strace.c index f4d32959..6ef7af85 100644 --- a/liblfi/syscalls/strace.c +++ b/liblfi/syscalls/strace.c @@ -14,6 +14,8 @@ sysname(uint64_t sysno) STRACE_CASE(brk) STRACE_CASE(openat) STRACE_CASE(close) + STRACE_CASE(dup) + STRACE_CASE(dup3) STRACE_CASE(writev) STRACE_CASE(readv) STRACE_CASE(pread64) @@ -25,6 +27,7 @@ sysname(uint64_t sysno) STRACE_CASE(getrandom) STRACE_CASE(clock_gettime) STRACE_CASE(ioctl) + STRACE_CASE(fcntl) STRACE_CASE(set_tid_address) STRACE_CASE(set_robust_list) STRACE_CASE(uname) diff --git a/liblfi/syscalls/sys_fcntl.c b/liblfi/syscalls/sys_fcntl.c index bba07435..562422ac 100644 --- a/liblfi/syscalls/sys_fcntl.c +++ b/liblfi/syscalls/sys_fcntl.c @@ -1,11 +1,61 @@ #include +#include +#include +#include #include "syscalls/syscalls.h" +#include "fd.h" +#include "file.h" +#include "host.h" +#include "host/posix/file.h" int sys_fcntl(struct TuxProc* p, int fd, int cmd, uintptr_t va0, uintptr_t va1, uintptr_t va2, uintptr_t va3) { - WARN(p->tux, "fcntl ignored"); - return 0; + switch (cmd) { + case F_DUPFD: { + int minfd = va0; + struct FDFile* FD_DEFER(f) = fdget(&p->fdtable, fd); + if (!f) + return -TUX_EBADF; + + // Find the lowest available fd >= minfd + for (int newfd = minfd; newfd < TUX_NOFILE; newfd++) { + if (!fdhas(&p->fdtable, newfd)) { + fdassign(&p->fdtable, newfd, f); + return newfd; + } + } + return -TUX_EMFILE; + } + + case F_DUPFD_CLOEXEC: + // Close-on-exec not supported yet + return -TUX_ENOSYS; + + case F_GETFD: + case F_SETFD: + case F_GETFL: + case F_SETFL: { + struct FDFile* FD_DEFER(f) = fdget(&p->fdtable, fd); + if (!f) + return -TUX_EBADF; + + // Get the underlying host file descriptor + struct HostFile* hf = f->file ? f->file(f->dev) : NULL; + if (!hf) + return -TUX_EBADF; + + // Pass through to system fcntl + int result = fcntl(hf->fd, cmd, va0); + if (result < 0) + return -errno; + return result; + } + + default: + WARN(p->tux, "fcntl command %d not implemented", cmd); + return -TUX_ENOSYS; + } } diff --git a/liblfi/syscalls/sys_file.c b/liblfi/syscalls/sys_file.c index bd6aeba1..76abe3a7 100644 --- a/liblfi/syscalls/sys_file.c +++ b/liblfi/syscalls/sys_file.c @@ -142,6 +142,42 @@ sys_close(struct TuxProc* p, int fd) return 0; } +int +sys_dup(struct TuxProc* p, int oldfd) +{ + struct FDFile* FD_DEFER(f) = fdget(&p->fdtable, oldfd); + if (!f) + return -TUX_EBADF; + + int newfd = fdalloc(&p->fdtable); + if (newfd < 0) + return -TUX_EMFILE; + + fdassign(&p->fdtable, newfd, f); + return newfd; +} + +int +sys_dup3(struct TuxProc* p, int oldfd, int newfd, int flags) +{ + if (oldfd == newfd) + return -TUX_EINVAL; + + struct FDFile* FD_DEFER(f) = fdget(&p->fdtable, oldfd); + if (!f) + return -TUX_EBADF; + + if (newfd < 0 || newfd >= TUX_NOFILE) + return -TUX_EBADF; + + if (flags != 0 && flags != TUX_O_CLOEXEC) + return -TUX_EINVAL; + + fdremove(&p->fdtable, newfd); + fdassign(&p->fdtable, newfd, f); + return newfd; +} + ssize_t sys_readlinkat(struct TuxProc* p, int dirfd, lfiptr_t pathp, lfiptr_t bufp, size_t size) { @@ -322,6 +358,18 @@ sys_fchmod(struct TuxProc* p, int fd, tux_mode_t mode) return f->chmod(f->dev, mode); } +int +sys_fchmodat(struct TuxProc* p, int dirfd, lfiptr_t pathp, tux_mode_t mode, int flags) +{ + struct HostFile* dir = getfdir(p, dirfd); + if (dirfd != TUX_AT_FDCWD && !dir) + return -TUX_EBADF; + const char* path = procpath(p, pathp); + if (!path) + return -TUX_EFAULT; + return host_fchmodat(dir, path, mode, flags); +} + int sys_fsync(struct TuxProc* p, int fd) { diff --git a/liblfi/syscalls/syscalls.h b/liblfi/syscalls/syscalls.h index b0d97ec2..09fb378d 100644 --- a/liblfi/syscalls/syscalls.h +++ b/liblfi/syscalls/syscalls.h @@ -104,6 +104,10 @@ ssize_t sys_readlink(struct TuxProc* p, lfiptr_t pathp, lfiptr_t bufp, size_t si int sys_close(struct TuxProc* p, int fd); +int sys_dup(struct TuxProc* p, int oldfd); + +int sys_dup3(struct TuxProc* p, int oldfd, int newfd, int flags); + ssize_t sys_read(struct TuxProc* p, int fd, lfiptr_t bufp, size_t size); ssize_t sys_readv(struct TuxProc* p, int fd, lfiptr_t iovp, size_t iovcnt); @@ -122,6 +126,8 @@ int sys_fchmod(struct TuxProc* p, int fd, tux_mode_t mode); int sys_chmod(struct TuxProc* p, uintptr_t pathp, tux_mode_t mode); +int sys_fchmodat(struct TuxProc* p, int dirfd, lfiptr_t pathp, tux_mode_t mode, int flags); + int sys_fchown(struct TuxProc* p, int fd, tux_uid_t owner, tux_gid_t group); int sys_chown(struct TuxProc* p, uintptr_t pathp, tux_uid_t owner, tux_gid_t group); diff --git a/liblfi/test/meson.build b/liblfi/test/meson.build index 6b8941a2..b4f762db 100644 --- a/liblfi/test/meson.build +++ b/liblfi/test/meson.build @@ -11,7 +11,12 @@ test('test_init', executable( dependencies: [liblfi_dep] ), suite: ['liblfi']) -lficc = find_program(cpu + '-lfi-linux-musl-clang', required: false) +lficc = find_program([ + cpu + '-lfi-linux-musl-clang', + '/opt/aarch64-lfi-clang-new/lfi-clang/' + cpu + '-lfi-linux-musl-clang', + '/opt/aarch64-lfi-clang/lfi-clang/' + cpu + '-lfi-linux-musl-clang', + '/opt/lfi/bin/' + cpu + '-lfi-linux-musl-clang' +], required: false) if not lficc.found() warning('lficc not found: cannot test liblfi') subdir_done() @@ -49,6 +54,8 @@ testdata = [ {'bin': test_tux, 'file': 'tux/file.c', 'inputs': ['tux/inputs/file.txt'], 'cflags': []}, {'bin': test_tux, 'file': 'tux/chdir.c', 'inputs': ['tux/inputs', 'file.txt'], 'cflags': []}, {'bin': test_tux, 'file': 'tux/thread.c', 'inputs': [], 'cflags': []}, + {'bin': test_tux, 'file': 'tux/dup.c', 'inputs': [], 'cflags': []}, + {'bin': test_tux, 'file': 'tux/fcntl.c', 'inputs': [], 'cflags': []}, {'bin': test_fs, 'file': 'tux/fs.c', 'inputs': '/proc/self/test.txt', 'cflags': []} ] diff --git a/liblfi/test/tux/dup.c b/liblfi/test/tux/dup.c new file mode 100644 index 00000000..5df2c16f --- /dev/null +++ b/liblfi/test/tux/dup.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +int main() { + // Test dup() syscall + int fd1 = open("/dev/null", O_WRONLY); + if (fd1 < 0) { + printf("failed to open /dev/null\n"); + return 1; + } + + int fd2 = dup(fd1); + if (fd2 < 0) { + printf("dup() failed\n"); + return 1; + } + + printf("dup: original fd=%d, dup fd=%d\n", fd1, fd2); + + // Test dup3() syscall + int fd3 = 10; // arbitrary target fd + int fd4 = dup3(fd1, fd3, 0); + if (fd4 != fd3) { + printf("dup3() failed: expected %d, got %d\n", fd3, fd4); + return 1; + } + + printf("dup3: original fd=%d, target fd=%d\n", fd1, fd3); + + // Test writing to all fds to verify they work + const char* msg = "test\n"; + write(fd1, msg, strlen(msg)); + write(fd2, msg, strlen(msg)); + write(fd3, msg, strlen(msg)); + + printf("dup test passed\n"); + return 0; +} \ No newline at end of file diff --git a/liblfi/test/tux/dup.c.out b/liblfi/test/tux/dup.c.out new file mode 100644 index 00000000..c1cc119e --- /dev/null +++ b/liblfi/test/tux/dup.c.out @@ -0,0 +1,3 @@ +dup: original fd=3, dup fd=4 +dup3: original fd=3, target fd=10 +dup test passed diff --git a/liblfi/test/tux/fchmodat_test.c b/liblfi/test/tux/fchmodat_test.c new file mode 100644 index 00000000..8798f69c --- /dev/null +++ b/liblfi/test/tux/fchmodat_test.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include +#include + +int main() { + const char* testfile = "fchmodat_test_file.txt"; + const char* testdir = "fchmodat_test_dir"; + + // Create a test directory + if (mkdir(testdir, 0755) != 0 && errno != EEXIST) { + fprintf(stderr, "Failed to create test directory: %s\n", strerror(errno)); + return 1; + } + + // Create a test file in the directory + char filepath[256]; + snprintf(filepath, sizeof(filepath), "%s/%s", testdir, testfile); + FILE* f = fopen(filepath, "w"); + if (!f) { + fprintf(stderr, "Failed to create test file: %s\n", strerror(errno)); + rmdir(testdir); + return 1; + } + fprintf(f, "test content\n"); + fclose(f); + + // Open the directory for fchmodat + int dirfd = open(testdir, O_RDONLY); + if (dirfd < 0) { + fprintf(stderr, "Failed to open test directory: %s\n", strerror(errno)); + unlink(filepath); + rmdir(testdir); + return 1; + } + + // Test fchmodat - make file read-only using directory fd + printf("Testing fchmodat to make file read-only...\n"); + if (fchmodat(dirfd, testfile, 0444, 0) != 0) { + fprintf(stderr, "fchmodat failed: %s\n", strerror(errno)); + close(dirfd); + unlink(filepath); + rmdir(testdir); + return 1; + } + + // Verify the permissions changed + struct stat st; + if (stat(filepath, &st) != 0) { + fprintf(stderr, "stat failed: %s\n", strerror(errno)); + close(dirfd); + unlink(filepath); + rmdir(testdir); + return 1; + } + + if ((st.st_mode & 0777) == 0444) { + printf("fchmodat test PASSED - file is now read-only\n"); + } else { + printf("fchmodat test FAILED - expected mode 0444, got %o\n", st.st_mode & 0777); + close(dirfd); + unlink(filepath); + rmdir(testdir); + return 1; + } + + // Test fchmodat with AT_FDCWD + printf("Testing fchmodat with AT_FDCWD...\n"); + if (fchmodat(AT_FDCWD, filepath, 0644, 0) != 0) { + fprintf(stderr, "fchmodat with AT_FDCWD failed: %s\n", strerror(errno)); + close(dirfd); + unlink(filepath); + rmdir(testdir); + return 1; + } + + // Verify the permissions changed back + if (stat(filepath, &st) != 0) { + fprintf(stderr, "stat failed: %s\n", strerror(errno)); + close(dirfd); + unlink(filepath); + rmdir(testdir); + return 1; + } + + if ((st.st_mode & 0777) == 0644) { + printf("fchmodat with AT_FDCWD test PASSED - file is now writable\n"); + } else { + printf("fchmodat with AT_FDCWD test FAILED - expected mode 0644, got %o\n", st.st_mode & 0777); + close(dirfd); + unlink(filepath); + rmdir(testdir); + return 1; + } + + // Clean up + close(dirfd); + unlink(filepath); + rmdir(testdir); + printf("All fchmodat tests PASSED\n"); + return 0; +} \ No newline at end of file diff --git a/liblfi/test/tux/fchmodat_test.c.out b/liblfi/test/tux/fchmodat_test.c.out new file mode 100644 index 00000000..63780ea5 --- /dev/null +++ b/liblfi/test/tux/fchmodat_test.c.out @@ -0,0 +1,5 @@ +Testing fchmodat to make file read-only... +fchmodat test PASSED - file is now read-only +Testing fchmodat with AT_FDCWD... +fchmodat with AT_FDCWD test PASSED - file is now writable +All fchmodat tests PASSED diff --git a/liblfi/test/tux/fcntl.c b/liblfi/test/tux/fcntl.c new file mode 100644 index 00000000..2595574c --- /dev/null +++ b/liblfi/test/tux/fcntl.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +int main() { + // Test fcntl() syscall + int fd1 = open("/dev/null", O_WRONLY); + if (fd1 < 0) { + printf("failed to open /dev/null\n"); + return 1; + } + + // Test F_DUPFD - duplicate fd with minimum fd number + int fd2 = fcntl(fd1, F_DUPFD, 5); + if (fd2 < 0) { + printf("fcntl F_DUPFD failed\n"); + return 1; + } + if (fd2 < 5) { + printf("fcntl F_DUPFD returned fd %d, expected >= 5\n", fd2); + return 1; + } + + printf("fcntl F_DUPFD: original fd=%d, dup fd=%d\n", fd1, fd2); + + // Test F_GETFL - get file status flags + int flags = fcntl(fd1, F_GETFL); + if (flags < 0) { + printf("fcntl F_GETFL failed\n"); + return 1; + } + + printf("fcntl F_GETFL: fd=%d has flags=0x%x\n", fd1, flags); + + // Test F_GETFD - get file descriptor flags + int fd_flags = fcntl(fd1, F_GETFD); + if (fd_flags < 0) { + printf("fcntl F_GETFD failed\n"); + return 1; + } + + printf("fcntl F_GETFD: fd=%d has fd_flags=0x%x\n", fd1, fd_flags); + + // Test F_SETFD - set file descriptor flags (should work) + int result = fcntl(fd1, F_SETFD, FD_CLOEXEC); + if (result < 0) { + printf("fcntl F_SETFD failed\n"); + return 1; + } + + // Verify the flag was set + fd_flags = fcntl(fd1, F_GETFD); + if (fd_flags < 0) { + printf("fcntl F_GETFD after F_SETFD failed\n"); + return 1; + } + + printf("fcntl F_SETFD: fd=%d now has fd_flags=0x%x\n", fd1, fd_flags); + + // Test F_DUPFD_CLOEXEC - should return ENOSYS + int fd3 = fcntl(fd1, F_DUPFD_CLOEXEC, 8); + if (fd3 >= 0) { + printf("fcntl F_DUPFD_CLOEXEC should have failed but returned fd=%d\n", fd3); + return 1; + } + + printf("fcntl F_DUPFD_CLOEXEC correctly returned error (not supported)\n"); + + // Test writing to duplicated fd to verify it works + const char* msg = "test\n"; + write(fd1, msg, strlen(msg)); + write(fd2, msg, strlen(msg)); + + close(fd1); + close(fd2); + + printf("fcntl test passed\n"); + return 0; +} \ No newline at end of file diff --git a/liblfi/test/tux/fcntl.c.out b/liblfi/test/tux/fcntl.c.out new file mode 100644 index 00000000..80ad22c1 --- /dev/null +++ b/liblfi/test/tux/fcntl.c.out @@ -0,0 +1,6 @@ +fcntl F_DUPFD: original fd=3, dup fd=5 +fcntl F_GETFL: fd=3 has flags=0x20001 +fcntl F_GETFD: fd=3 has fd_flags=0x0 +fcntl F_SETFD: fd=3 now has fd_flags=0x1 +fcntl F_DUPFD_CLOEXEC correctly returned error (not supported) +fcntl test passed