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
17 changes: 12 additions & 5 deletions native/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ static int fs_walk_fast(int base,const char *cur_start,const char *rest,int foll
#define FS_DIRECT 0
#define FS_NOFOLLOW_AT AT_SYMLINK_NOFOLLOW
#define FS_NOFOLLOW_OPEN O_NOFOLLOW
#define O_SYMLINK 010000000 /* O_PATH (needs _GNU_SOURCE); with O_NOFOLLOW: the link itself */
#define FS_ANY_AT 0
#define fs_renameat renameat
#endif
Expand Down Expand Up @@ -1414,11 +1415,13 @@ static uint64_t fs_open(void) {
const uint64_t allowed=3|64|128|256|512|1024|2048|4096|16384|32768|65536|131072|262144|524288|1052672;
/* Like Linux, ignore bits open(2) does not define (MySQL passes a mode there).
O_PATH (a descriptor for *at calls, fstat and close; MariaDB walks directories
with it) opens read-only. ponytail: Linux needs no read permission for O_PATH
and can open a symlink itself (with O_NOFOLLOW); here those fail as plain opens.
O_TMPFILE is unsupported (callers fall back to a named file). */
with it) opens read-only; with O_NOFOLLOW on a symlink it opens the link itself
(glibc's fchmodat and lchmod: tar chmods each link it makes and takes fstat's
S_IFLNK as EOPNOTSUPP). ponytail: Linux needs no read permission for O_PATH; here
that fails as a plain open. O_TMPFILE is unsupported (callers fall back to a named file). */
if((flags&UINT64_C(0x400000)) || (flags&3)==3) return linux_error(95);
if(flags&UINT64_C(0x200000))flags&=UINT64_C(16384|32768|524288); /* O_DIRECTORY, O_NOFOLLOW, O_CLOEXEC */
int opath=(flags&UINT64_C(0x200000))!=0;
if(opath)flags&=UINT64_C(16384|32768|524288); /* O_DIRECTORY, O_NOFOLLOW, O_CLOEXEC */
flags&=allowed;
if(!fs_detached.on) { /* detached: checked under the lock, installed there */
unsigned slot=0;while(slot<FS_SLOTS && FS(slot).fd) slot++;
Expand Down Expand Up @@ -1540,11 +1543,15 @@ static uint64_t fs_open(void) {
int64_t r=upm_create(layers.canon,flags,cpu->gpr[3]);
if(r!=UPM_DISK) { close(parent);if(r>=0)fs_count_created();return r<0?linux_error((unsigned)-r):(uint64_t)r; }
}
/* O_PATH|O_NOFOLLOW on a symlink: the link itself (O_SYMLINK; FS_NOFOLLOW_OPEN still
refuses one before the leaf). */
int link=opath && exists && S_ISLNK(before.st_mode);
if(link)options=O_RDONLY|O_SYMLINK|FS_NOFOLLOW_OPEN|O_CLOEXEC|O_NONBLOCK;
int fd=openat(parent,leaf,options|(fresh?O_EXCL:0),mode);
if(fd<0 && fresh && errno==EEXIST && !(flags&128)) { fresh=0;fd=openat(parent,leaf,options,mode); }
if(fd<0) { uint64_t e=fs_error();close(parent);return e; }
struct stat info;
if(!fresh && (fstat(fd,&info)<0 || (!S_ISREG(info.st_mode) && !S_ISDIR(info.st_mode)) || (uint64_t)info.st_size>fs_file_limit)) {
if(!fresh && (fstat(fd,&info)<0 || (!S_ISREG(info.st_mode) && !S_ISDIR(info.st_mode) && !(link && S_ISLNK(info.st_mode))) || (uint64_t)info.st_size>fs_file_limit)) {
close(fd);close(parent);return linux_error(13);
}
GuestDirectory *directory=NULL;
Expand Down
3 changes: 2 additions & 1 deletion scripts/verify_fork_sockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def main():
setuid, setuid_leaf, ping, many_procs, inet6, bigfile = (build(n, folder, linker) for n in ('setuid_guest', 'setuid_leaf', 'ping_guest', 'many_procs_guest', 'inet6_guest', 'bigfile_guest'))
lazy_charge, lazy_leaf, oom_kill = (build(n, folder, linker) for n in ('lazy_charge_guest', 'lazy_charge_leaf', 'oom_kill_guest'))
release_charge = build('release_charge_guest', folder, linker) # memory given back (munmap, MAP_FIXED, DONTNEED, brk) is uncharged; swap beyond -m
opath_link = build('opath_link_guest', folder, linker) # open(O_PATH|O_NOFOLLOW) of a symlink: the link (glibc's lchmod, tar -p)
fs_stress = build('fs_stress_guest', folder, linker) # file calls racing close, dup2, munmap, fork, exec
segment_spare = build('segment_spare_guest', folder, linker) # read-only shared file maps leave the last segments for other memory
vfork_threads = build('vfork_threads_guest', folder, linker) # vfork from a threaded process shares its memory
Expand Down Expand Up @@ -101,7 +102,7 @@ def main():
for elf, args, expected in ((server, [], b'fork-server PASS\n'), (guest, ['--root', str(root)], b'exec-socket PASS\n'), (epoll, [], b'epoll-sched PASS\n'), (edge, [], b'epoll-et PASS\n'), (forks, ['--root', str(shared), '--writable'], b'fork PASS\n'), (identity, ['--root', str(shared), '--writable'], b'identity PASS\n'), (threads, [], b'threads PASS\n'),
(fork_exec, ['--root', str(exec_root), '--writable'], b''), (fork_threads, [], b''),
(thread_group, [], b''), (futex, [], struct.pack('<I', 11)), (thread_limit, [], b''),
(fp_preserve, [], b'fp-preserve PASS\n'), (sleep, [], b'sleep PASS\n'), (lazy, ['--mmap-limit-mib', '2048'], b'lazy-memory PASS\n'), (writev, [], b'x'*64+b'\n'), (many, [], b'many-sockets PASS\n'), (vdso, [], b'vdso PASS\n'), (udp, [], b'udp loopback PASS\n'), (udp, ['--outbound'], b'udp outbound PASS\n'), (unix, ['--root', str(shared), '--writable'], b'unix PASS\n'), (session, [], b'session PASS\n'), (fdtable, ['--root', str(shared), '--writable'], b'fd-table PASS\n'), (sigmask, ['--signals'], b'sigmask PASS\n'), (setuid, ['--root', str(setuid_root), '--uid', '1000', '--gid', '1000'], b'setuid PASS\n'), (ping, ['--process-tree'], b'ping PASS\n'), (many_procs, ['--memory-limit-mib', '512'], b'many-procs PASS\n'), (inet6, [], b'inet6 PASS\n'), (lazy_charge, ['--root', str(lazy_root), '--memory-limit-mib', '256', '--mmap-limit-mib', '12288'], b'lazy-charge PASS\n'), (oom_kill, ['--memory-limit-mib', '256', '--mmap-limit-mib', '2048'], b'oom-kill PASS\n'), (release_charge, ['--memory-limit-mib', '64', '--memory-swap-mib', '64', '--mmap-limit-mib', '64', '--dynamic'], b'release-charge PASS\n'), (bigfile, ['--root', str(shared), '--writable', '--file-limit-mib', '1048576', '--storage-limit-mib', '0'], b'bigfile PASS\n'),
(fp_preserve, [], b'fp-preserve PASS\n'), (sleep, [], b'sleep PASS\n'), (lazy, ['--mmap-limit-mib', '2048'], b'lazy-memory PASS\n'), (writev, [], b'x'*64+b'\n'), (many, [], b'many-sockets PASS\n'), (vdso, [], b'vdso PASS\n'), (udp, [], b'udp loopback PASS\n'), (udp, ['--outbound'], b'udp outbound PASS\n'), (unix, ['--root', str(shared), '--writable'], b'unix PASS\n'), (session, [], b'session PASS\n'), (fdtable, ['--root', str(shared), '--writable'], b'fd-table PASS\n'), (sigmask, ['--signals'], b'sigmask PASS\n'), (setuid, ['--root', str(setuid_root), '--uid', '1000', '--gid', '1000'], b'setuid PASS\n'), (ping, ['--process-tree'], b'ping PASS\n'), (many_procs, ['--memory-limit-mib', '512'], b'many-procs PASS\n'), (inet6, [], b'inet6 PASS\n'), (lazy_charge, ['--root', str(lazy_root), '--memory-limit-mib', '256', '--mmap-limit-mib', '12288'], b'lazy-charge PASS\n'), (oom_kill, ['--memory-limit-mib', '256', '--mmap-limit-mib', '2048'], b'oom-kill PASS\n'), (release_charge, ['--memory-limit-mib', '64', '--memory-swap-mib', '64', '--mmap-limit-mib', '64', '--dynamic'], b'release-charge PASS\n'), (opath_link, ['--root', str(shared), '--writable'], b'opath-link PASS\n'), (bigfile, ['--root', str(shared), '--writable', '--file-limit-mib', '1048576', '--storage-limit-mib', '0'], b'bigfile PASS\n'),
(cow_stress, [], b'cow-stress PASS\n'), (smp_atomic, [], b'smp-atomic PASS\n'), (fork_churn, [], b'fork-churn PASS\n'), (thread_smp, [], b'thread-smp PASS\n'), (out_edge, [], b'epoll-out-edge PASS\n'),
(around, ['--mmap-limit-mib', '256', '--signals'], b'fault-around PASS\n'), (fastcall, [], b'fastcall PASS\n'),
(fs_stress, ['--root', str(shared), '--writable', '--dynamic', '--storage-limit-mib', '0'], b'fs-stress PASS\n'),
Expand Down
36 changes: 36 additions & 0 deletions tests/native/opath_link_guest.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* open(O_PATH|O_NOFOLLOW) of a symlink opens the link itself, as Linux: glibc's
fchmodat(AT_SYMLINK_NOFOLLOW) (lchmod) opens so, fstats the descriptor and answers
EOPNOTSUPP for a link, which tar -p takes as done for each link it makes. Before, the
open failed with ELOOP and tar exited 2 ("Cannot change mode ... Too many levels of
symbolic links"). Run with --root <dir> --writable. */
typedef unsigned long U;
static long call(U n,U a,U b,U c,U d,U e,U f) {
register U x8 __asm__("x8")=n,x0 __asm__("x0")=a,x1 __asm__("x1")=b;
register U x2 __asm__("x2")=c,x3 __asm__("x3")=d,x4 __asm__("x4")=e,x5 __asm__("x5")=f;
__asm__ volatile("svc #0":"+r"(x0):"r"(x8),"r"(x1),"r"(x2),"r"(x3),"r"(x4),"r"(x5):"memory","cc");
return (long)x0;
}
static unsigned checks;static char where[]="opath-link FAIL\n";
static void check(int ok) { checks++;if(!ok) { call(64,1,(U)where,sizeof(where)-1,0,0,0);call(94,(U)checks,0,0,0,0,0);for(;;){} } }
#define AT (U)-100
static unsigned char st[128];
static U mode(void) { return (U)(st[16]|st[17]<<8|st[18]<<16|st[19]<<24)&0170000; }
void _start(void) {
long r=call(34,AT,(U)"/opath-link",0755,0,0,0);check(r==0 || r==-17);
call(35,AT,(U)"/opath-link/dangling",0,0,0,0);call(35,AT,(U)"/opath-link/file",0,0,0,0);call(35,AT,(U)"/opath-link/to-file",0,0,0,0);
long f=call(56,AT,(U)"/opath-link/file",0101|01000000,0755,0,0);check(f>=0);call(57,(U)f,0,0,0,0,0);
check(call(36,(U)"../missing/x",AT,(U)"/opath-link/dangling",0,0,0)==0); /* a placeholder tar repoints */
check(call(36,(U)"file",AT,(U)"/opath-link/to-file",0,0,0)==0);
const char *links[]={"/opath-link/dangling","/opath-link/to-file"};
for(int i=0;i<2;i++) {
long fd=call(56,AT,(U)links[i],0x200000|0100000|02000000,0,0,0);check(fd>=0); /* O_PATH|O_NOFOLLOW|O_CLOEXEC */
check(call(79,(U)fd,(U)"",(U)st,0x1000,0,0)==0 && mode()==0120000); /* fstatat(fd, "", AT_EMPTY_PATH): the link */
check(call(80,(U)fd,(U)st,0,0,0,0)==0 && mode()==0120000);
check(call(57,(U)fd,0,0,0,0,0)==0);
check(call(56,AT,(U)links[i],0100000,0,0,0)==-40); /* without O_PATH: ELOOP still */
check(call(452,AT,(U)links[i],0777,0x100,0,0)==-95); /* fchmodat2 NOFOLLOW: EOPNOTSUPP */
}
long fd=call(56,AT,(U)"/opath-link/to-file",0x200000,0,0,0);check(fd>=0); /* O_PATH, followed: the file */
check(call(80,(U)fd,(U)st,0,0,0,0)==0 && mode()==0100000);call(57,(U)fd,0,0,0,0,0);
call(64,1,(U)"opath-link PASS\n",16,0,0,0);call(94,0,0,0,0,0,0);for(;;){}
}
Loading