diff --git a/native/fs.h b/native/fs.h index f783cb5..bbc3cd7 100644 --- a/native/fs.h +++ b/native/fs.h @@ -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 @@ -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(slotgpr[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; diff --git a/scripts/verify_fork_sockets.py b/scripts/verify_fork_sockets.py index 6194cba..e5f58fb 100644 --- a/scripts/verify_fork_sockets.py +++ b/scripts/verify_fork_sockets.py @@ -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 @@ -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(' --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(;;){} +}