//go:build raceattr // The raceattr program reproduces vfs inode file attribute race. // // Even though libfuse high-level API presents the address of a struct stat // alongside struct fuse_context, file attributes are actually inherent to the // inode, instead of the specific call from userspace. The kernel implementation // in fs/fuse/xattr.c appears to make stale data in the inode (set by a previous // call) impossible or very unlikely to reach userspace via the stat family of // syscalls. However, when using default_permissions to have the VFS check // permissions, this race still happens, despite the resulting struct stat being // correct when overriding the check via capabilities otherwise. // // This program reproduces the failure, but because of its continuous nature, it // is provided independent of the vm integration test suite. package main import ( "context" "flag" "log" "os" "os/signal" "runtime" "sync" "sync/atomic" "syscall" ) func newStatAs( ctx context.Context, cancel context.CancelFunc, n *atomic.Uint64, ok *atomic.Bool, uid uint32, pathname string, continuous bool, ) func() { return func() { runtime.LockOSThread() defer cancel() if _, _, errno := syscall.Syscall( syscall.SYS_SETUID, uintptr(uid), 0, 0, ); errno != 0 { cancel() log.Printf("cannot set uid to %d: %s", uid, errno) } var stat syscall.Stat_t for { if ctx.Err() != nil { return } if err := syscall.Lstat(pathname, &stat); err != nil { // SHAREFS_PERM_DIR not world executable, or // SHAREFS_PERM_REG not world readable if !continuous { cancel() } ok.Store(true) log.Printf("uid %d: %v", uid, err) } else if stat.Uid != uid { // appears to be unreachable if !continuous { cancel() } ok.Store(true) log.Printf("got uid %d instead of %d", stat.Uid, uid) } n.Add(1) } } } func main() { log.SetFlags(0) log.SetPrefix("raceattr: ") p := flag.String("target", "/sdcard/raceattr", "pathname of test file") u0 := flag.Int("uid0", 1<<10-1, "first uid") u1 := flag.Int("uid1", 1<<10-2, "second uid") count := flag.Int("count", 1, "threads per uid") continuous := flag.Bool("continuous", false, "keep running even after reproduce") flag.Parse() if os.Geteuid() != 0 { log.Fatal("this program must run as root") } ctx, cancel := signal.NotifyContext( context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, ) if err := os.WriteFile(*p, nil, 0); err != nil { log.Fatal(err) } var ( wg sync.WaitGroup n atomic.Uint64 ok atomic.Bool ) if *count < 1 { *count = 1 } for range *count { wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u0), *p, *continuous)) if *u1 >= 0 { wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u1), *p, *continuous)) } } wg.Wait() if !*continuous && ok.Load() { log.Printf("reproduced after %d calls", n.Load()) } }