-
Notifications
You must be signed in to change notification settings - Fork 17
Resolve /dev/shm host paths in one place #228
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
base: main
Are you sure you want to change the base?
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 | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1909,7 +1909,8 @@ int64_t sys_readlinkat(guest_t *g, | |||||||||||||
| return -LINUX_EBADF; | ||||||||||||||
|
|
||||||||||||||
| /* Apply sysroot redirect for absolute paths */ | ||||||||||||||
| ssize_t len = readlinkat(dir_ref.fd, tx.host_path, link, sizeof(link) - 1); | ||||||||||||||
| ssize_t len = readlinkat(path_tx_dirfd(&tx, &dir_ref), tx.host_path, link, | ||||||||||||||
| sizeof(link) - 1); | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| if (len < 0) | ||||||||||||||
| return linux_errno(); | ||||||||||||||
|
|
@@ -1947,31 +1948,19 @@ int64_t sys_unlinkat(guest_t *g, int dirfd, uint64_t path_gva, int flags) | |||||||||||||
| if (host_dirfd_ref_open(dirfd, &dir_ref) < 0) | ||||||||||||||
| return -LINUX_EBADF; | ||||||||||||||
|
|
||||||||||||||
| /* Rewrite /dev/shm/<name> to the host temp directory so shm_unlink works */ | ||||||||||||||
| const char *unlink_path; | ||||||||||||||
| char shm_host[LINUX_PATH_MAX]; | ||||||||||||||
| if (!strncmp(tx.guest_path, "/dev/shm/", 9)) { | ||||||||||||||
| if (proc_dev_shm_resolve(tx.guest_path + 9, shm_host, | ||||||||||||||
| sizeof(shm_host)) < 0) { | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
| unlink_path = shm_host; | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| dir_ref.fd = AT_FDCWD; | ||||||||||||||
| dir_ref.owned = 0; | ||||||||||||||
| } else { | ||||||||||||||
| unlink_path = tx.host_path; | ||||||||||||||
| } | ||||||||||||||
| /* path_translate_at rewrites /dev/shm/<name> to the absolute backing | ||||||||||||||
| * path, so shm_unlink works; path_tx_dirfd drops the guest dirfd there. | ||||||||||||||
| */ | ||||||||||||||
| host_fd_t unlink_dirfd = path_tx_dirfd(&tx, &dir_ref); | ||||||||||||||
|
|
||||||||||||||
| struct stat removed_st; | ||||||||||||||
| bool clear_removed_overlay = | ||||||||||||||
| fstatat(dir_ref.fd, unlink_path, &removed_st, AT_SYMLINK_NOFOLLOW) == | ||||||||||||||
| fstatat(unlink_dirfd, tx.host_path, &removed_st, AT_SYMLINK_NOFOLLOW) == | ||||||||||||||
| 0 && | ||||||||||||||
| (removed_st.st_nlink <= 1 || (flags & LINUX_AT_REMOVEDIR)); | ||||||||||||||
|
|
||||||||||||||
| int host_flags = translate_at_flags(flags); | ||||||||||||||
| if (unlinkat(dir_ref.fd, unlink_path, host_flags) < 0) { | ||||||||||||||
| if (unlinkat(unlink_dirfd, tx.host_path, host_flags) < 0) { | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -2006,7 +1995,8 @@ int64_t sys_mkdirat(guest_t *g, int dirfd, uint64_t path_gva, int mode) | |||||||||||||
| if (host_dirfd_ref_open(dirfd, &dir_ref) < 0) | ||||||||||||||
| return -LINUX_EBADF; | ||||||||||||||
|
|
||||||||||||||
| if (mkdirat(dir_ref.fd, tx.host_path, (mode_t) mode) < 0) { | ||||||||||||||
| if (mkdirat(path_tx_dirfd(&tx, &dir_ref), tx.host_path, (mode_t) mode) < | ||||||||||||||
| 0) { | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -2065,6 +2055,8 @@ int64_t sys_renameat2(guest_t *g, | |||||||||||||
| host_fd_ref_close(&olddir_ref); | ||||||||||||||
| return -LINUX_EBADF; | ||||||||||||||
| } | ||||||||||||||
| host_fd_t old_host_dirfd = path_tx_dirfd(&old_tx, &olddir_ref); | ||||||||||||||
| host_fd_t new_host_dirfd = path_tx_dirfd(&new_tx, &newdir_ref); | ||||||||||||||
|
|
||||||||||||||
| /* Apply sysroot resolution for absolute paths RENAME_NOREPLACE: fail if | ||||||||||||||
| * destination exists. macOS renamex_np supports RENAME_EXCL for the same | ||||||||||||||
|
|
@@ -2085,14 +2077,14 @@ int64_t sys_renameat2(guest_t *g, | |||||||||||||
| * requirement. This path still cannot handle directories because | ||||||||||||||
| * hardlinking directories is not allowed. | ||||||||||||||
| */ | ||||||||||||||
| if (linkat(olddir_ref.fd, old_tx.host_path, newdir_ref.fd, | ||||||||||||||
| if (linkat(old_host_dirfd, old_tx.host_path, new_host_dirfd, | ||||||||||||||
| new_tx.host_path, 0) < 0) { | ||||||||||||||
| return close_dir_refs_result(&olddir_ref, &newdir_ref, | ||||||||||||||
| linux_errno()); | ||||||||||||||
| } | ||||||||||||||
| if (unlinkat(olddir_ref.fd, old_tx.host_path, 0) < 0) { | ||||||||||||||
| if (unlinkat(old_host_dirfd, old_tx.host_path, 0) < 0) { | ||||||||||||||
| int err = errno; | ||||||||||||||
| (void) unlinkat(newdir_ref.fd, new_tx.host_path, 0); | ||||||||||||||
| (void) unlinkat(new_host_dirfd, new_tx.host_path, 0); | ||||||||||||||
| errno = err; | ||||||||||||||
| return close_dir_refs_result(&olddir_ref, &newdir_ref, | ||||||||||||||
| linux_errno()); | ||||||||||||||
|
|
@@ -2117,11 +2109,11 @@ int64_t sys_renameat2(guest_t *g, | |||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| struct stat old_st; | ||||||||||||||
| bool have_old_st = fstatat(olddir_ref.fd, old_tx.host_path, &old_st, | ||||||||||||||
| bool have_old_st = fstatat(old_host_dirfd, old_tx.host_path, &old_st, | ||||||||||||||
| AT_SYMLINK_NOFOLLOW) == 0; | ||||||||||||||
| struct stat overwritten_st; | ||||||||||||||
| bool clear_overwritten_overlay = | ||||||||||||||
| fstatat(newdir_ref.fd, new_tx.host_path, &overwritten_st, | ||||||||||||||
| fstatat(new_host_dirfd, new_tx.host_path, &overwritten_st, | ||||||||||||||
| AT_SYMLINK_NOFOLLOW) == 0 && | ||||||||||||||
| stat_identity_will_disappear(&overwritten_st) && | ||||||||||||||
| (!have_old_st || !same_stat_identity(&old_st, &overwritten_st)); | ||||||||||||||
|
|
@@ -2136,7 +2128,7 @@ int64_t sys_renameat2(guest_t *g, | |||||||||||||
| return close_dir_refs_result(&olddir_ref, &newdir_ref, 0); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (renameat(olddir_ref.fd, old_tx.host_path, newdir_ref.fd, | ||||||||||||||
| if (renameat(old_host_dirfd, old_tx.host_path, new_host_dirfd, | ||||||||||||||
| new_tx.host_path) < 0) { | ||||||||||||||
| return close_dir_refs_result(&olddir_ref, &newdir_ref, linux_errno()); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -2166,7 +2158,8 @@ int64_t sys_mknodat(guest_t *g, int dirfd, uint64_t path_gva, int mode, int dev) | |||||||||||||
| * nodes need root | ||||||||||||||
| */ | ||||||||||||||
| if (S_ISFIFO(mode)) { | ||||||||||||||
| if (mkfifoat(dir_ref.fd, tx.host_path, mode & 0777) < 0) { | ||||||||||||||
| if (mkfifoat(path_tx_dirfd(&tx, &dir_ref), tx.host_path, mode & 0777) < | ||||||||||||||
| 0) { | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -2176,8 +2169,8 @@ int64_t sys_mknodat(guest_t *g, int dirfd, uint64_t path_gva, int mode, int dev) | |||||||||||||
|
|
||||||||||||||
| /* Regular files: create an empty file */ | ||||||||||||||
| if (S_ISREG(mode) || (mode & S_IFMT) == 0) { | ||||||||||||||
| int fd = openat(dir_ref.fd, tx.host_path, O_CREAT | O_WRONLY | O_EXCL, | ||||||||||||||
| mode & 0777); | ||||||||||||||
| int fd = openat(path_tx_dirfd(&tx, &dir_ref), tx.host_path, | ||||||||||||||
| O_CREAT | O_WRONLY | O_EXCL, mode & 0777); | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| if (fd < 0) | ||||||||||||||
| return linux_errno(); | ||||||||||||||
|
|
@@ -2212,7 +2205,7 @@ int64_t sys_symlinkat(guest_t *g, | |||||||||||||
| return -LINUX_EBADF; | ||||||||||||||
|
|
||||||||||||||
| /* Resolve linkpath (the new symlink location) through sysroot */ | ||||||||||||||
| if (symlinkat(target, dir_ref.fd, tx.host_path) < 0) { | ||||||||||||||
| if (symlinkat(target, path_tx_dirfd(&tx, &dir_ref), tx.host_path) < 0) { | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -2258,11 +2251,18 @@ int64_t sys_linkat(guest_t *g, | |||||||||||||
| host_fd_ref_close(&olddir_ref); | ||||||||||||||
| return -LINUX_EBADF; | ||||||||||||||
| } | ||||||||||||||
| host_fd_t old_host_dirfd = path_tx_dirfd(&old_tx, &olddir_ref); | ||||||||||||||
| host_fd_t new_host_dirfd = path_tx_dirfd(&new_tx, &newdir_ref); | ||||||||||||||
|
|
||||||||||||||
| /* Resolve both paths through sysroot */ | ||||||||||||||
| int mac_flags = translate_at_flags(flags); | ||||||||||||||
| if (linkat(olddir_ref.fd, old_tx.host_path, newdir_ref.fd, new_tx.host_path, | ||||||||||||||
| mac_flags) < 0) { | ||||||||||||||
| /* Clear AT_SYMLINK_FOLLOW so a shm symlink is hard-linked as the leaf | ||||||||||||||
| * itself, never dereferenced to its host target (see dev_shm_resolve_path). | ||||||||||||||
| */ | ||||||||||||||
| if (old_tx.shm_redirect) | ||||||||||||||
| mac_flags &= ~AT_SYMLINK_FOLLOW; | ||||||||||||||
| if (linkat(old_host_dirfd, old_tx.host_path, new_host_dirfd, | ||||||||||||||
| new_tx.host_path, mac_flags) < 0) { | ||||||||||||||
| /* Darwin's linkat(2) man page: without AT_SYMLINK_FOLLOW, hard-linking | ||||||||||||||
| * a symlink itself (rather than its target) "may result in some file | ||||||||||||||
| * systems returning an error" -- reproduced here as ENOTSUP on | ||||||||||||||
|
|
@@ -2283,18 +2283,18 @@ int64_t sys_linkat(guest_t *g, | |||||||||||||
| struct stat old_st; | ||||||||||||||
| char target[LINUX_PATH_MAX]; | ||||||||||||||
| ssize_t target_len; | ||||||||||||||
| if (fstatat(olddir_ref.fd, old_tx.host_path, &old_st, | ||||||||||||||
| if (fstatat(old_host_dirfd, old_tx.host_path, &old_st, | ||||||||||||||
| AT_SYMLINK_NOFOLLOW) < 0 || | ||||||||||||||
| !S_ISLNK(old_st.st_mode) || | ||||||||||||||
| (target_len = readlinkat(olddir_ref.fd, old_tx.host_path, target, | ||||||||||||||
| (target_len = readlinkat(old_host_dirfd, old_tx.host_path, target, | ||||||||||||||
| sizeof(target) - 1)) < 0) { | ||||||||||||||
| host_fd_ref_close(&olddir_ref); | ||||||||||||||
| host_fd_ref_close(&newdir_ref); | ||||||||||||||
| return -LINUX_EPERM; | ||||||||||||||
| } | ||||||||||||||
| target[target_len] = '\0'; | ||||||||||||||
|
|
||||||||||||||
| if (symlinkat(target, newdir_ref.fd, new_tx.host_path) < 0) { | ||||||||||||||
| if (symlinkat(target, new_host_dirfd, new_tx.host_path) < 0) { | ||||||||||||||
| host_fd_ref_close(&olddir_ref); | ||||||||||||||
| host_fd_ref_close(&newdir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
|
|
@@ -2374,7 +2374,8 @@ int64_t sys_faccessat(guest_t *g, | |||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| int mac_flags = translate_faccessat_flags(flags); | ||||||||||||||
| if (faccessat(dir_ref.fd, tx.host_path, mode, mac_flags) < 0) { | ||||||||||||||
| if (faccessat(path_tx_dirfd(&tx, &dir_ref), tx.host_path, mode, mac_flags) < | ||||||||||||||
|
Contributor
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.
|
||||||||||||||
| 0) { | ||||||||||||||
|
Comment on lines
+2377
to
+2378
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. P1: Prompt for AI agents
Suggested change
|
||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -2437,6 +2438,21 @@ int64_t sys_truncate(guest_t *g, uint64_t path_gva, int64_t length) | |||||||||||||
| if (rc != INT64_MIN) | ||||||||||||||
| return rc; | ||||||||||||||
|
|
||||||||||||||
| /* truncate(2) has no nofollow variant, so reach a shm leaf via open | ||||||||||||||
| * O_NOFOLLOW + ftruncate (see dev_shm_resolve_path). | ||||||||||||||
| */ | ||||||||||||||
| if (tx.shm_redirect) { | ||||||||||||||
| int fd = open(tx.host_path, O_WRONLY | O_NOFOLLOW); | ||||||||||||||
|
Contributor
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.
|
||||||||||||||
| if (fd < 0) | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| if (ftruncate(fd, length) < 0) { | ||||||||||||||
| close_keep_errno(fd); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
| close(fd); | ||||||||||||||
| return 0; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (truncate(tx.host_path, length) < 0) | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| return 0; | ||||||||||||||
|
|
@@ -2529,7 +2545,10 @@ int64_t sys_fchmodat(guest_t *g, | |||||||||||||
| return -LINUX_EBADF; | ||||||||||||||
|
|
||||||||||||||
| int mac_flags = translate_at_flags(flags); | ||||||||||||||
| if (fchmodat(dir_ref.fd, tx.host_path, mode, mac_flags) < 0) { | ||||||||||||||
| if (tx.shm_redirect) | ||||||||||||||
| mac_flags |= AT_SYMLINK_NOFOLLOW; | ||||||||||||||
| if (fchmodat(path_tx_dirfd(&tx, &dir_ref), tx.host_path, mode, mac_flags) < | ||||||||||||||
| 0) { | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
|
|
@@ -2679,11 +2698,14 @@ int64_t sys_fchownat(guest_t *g, | |||||||||||||
| return -LINUX_EBADF; | ||||||||||||||
|
|
||||||||||||||
| int mac_flags = translate_at_flags(flags); | ||||||||||||||
| if (tx.shm_redirect) | ||||||||||||||
| mac_flags |= AT_SYMLINK_NOFOLLOW; | ||||||||||||||
| host_fd_t host_dirfd = path_tx_dirfd(&tx, &dir_ref); | ||||||||||||||
| struct stat before_st; | ||||||||||||||
| bool before_ok = | ||||||||||||||
| fstatat(dir_ref.fd, tx.host_path, &before_st, mac_flags) == 0; | ||||||||||||||
| fstatat(host_dirfd, tx.host_path, &before_st, mac_flags) == 0; | ||||||||||||||
|
|
||||||||||||||
| int host_rc = fchownat(dir_ref.fd, tx.host_path, owner, group, mac_flags); | ||||||||||||||
| int host_rc = fchownat(host_dirfd, tx.host_path, owner, group, mac_flags); | ||||||||||||||
| int saved_errno = errno; | ||||||||||||||
|
|
||||||||||||||
| struct stat after_st; | ||||||||||||||
|
|
@@ -2818,8 +2840,10 @@ int64_t sys_utimensat(guest_t *g, | |||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
| } else { | ||||||||||||||
| if (utimensat(dir_ref.fd, path_arg, times_gva ? ts : NULL, mac_flags) < | ||||||||||||||
| 0) { | ||||||||||||||
| if (tx.shm_redirect) | ||||||||||||||
| mac_flags |= AT_SYMLINK_NOFOLLOW; | ||||||||||||||
| if (utimensat(path_tx_dirfd(&tx, &dir_ref), path_arg, | ||||||||||||||
| times_gva ? ts : NULL, mac_flags) < 0) { | ||||||||||||||
| host_fd_ref_close(&dir_ref); | ||||||||||||||
| return linux_errno(); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
|
|
||
| #include "utils.h" | ||
|
|
||
| #include "runtime/procemu.h" | ||
| #include "syscall/abi.h" | ||
| #include "syscall/fuse.h" | ||
| #include "syscall/path.h" | ||
|
|
@@ -177,6 +178,7 @@ int path_translate_at(guest_fd_t dirfd, | |
| tx->host_path = path; | ||
| tx->proc_resolved = 0; | ||
| tx->fuse_path = false; | ||
| tx->shm_redirect = false; | ||
|
|
||
| if (!path) | ||
| return 0; | ||
|
|
@@ -200,6 +202,26 @@ int path_translate_at(guest_fd_t dirfd, | |
| } | ||
| } | ||
|
|
||
| /* /dev/shm/<leaf> maps into the per-UID host backing dir, through the | ||
| * same validated resolver as the open and stat intercepts. Only a | ||
| * non-empty flat leaf is redirected; bare "/dev/shm" and "/dev/shm/" | ||
| * stay on the sysroot path so the synthetic-directory intercepts keep | ||
| * answering for them. The resolver rejects "..", embedded '/', and | ||
| * empty names with EACCES. The early return skips sysroot resolution, | ||
| * the relative-containment recheck, and the sidecar lookup: the backing | ||
| * path is absolute, self-contained, and must never be sidecar-mapped. | ||
| * shm_redirect signals the redirect to callers, which must force nofollow | ||
| * on the host call; see dev_shm_resolve_path() for that invariant. | ||
| */ | ||
| if (!strncmp(tx->guest_path, "/dev/shm/", 9) && tx->guest_path[9] != '\0') { | ||
| if (proc_dev_shm_resolve(tx->guest_path + 9, tx->host_buf, | ||
| sizeof(tx->host_buf)) < 0) | ||
| return -1; | ||
| tx->host_path = tx->host_buf; | ||
| tx->shm_redirect = true; | ||
|
Contributor
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. Redirecting here also reaches follow-capable callers outside the nofollow-forced metadata set: |
||
| return 0; | ||
| } | ||
|
|
||
| errno = 0; | ||
| if ((flags & PATH_TR_CREATE) && sidecar_active() && | ||
| sidecar_path_targets_reserved_name(tx->guest_path)) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
..gate below usesstrstr(guest_suffix, "..")(line 710), which rejects any name containing.., so valid flat names likea..bnow fail withEACCESacross every redirected syscall. If the goal is only traversal prevention, compare the whole component:strcmp(guest_suffix, "..") == 0.