aboutsummaryrefslogtreecommitdiffhomepage
path: root/test/sharefs/raceattr.go
blob: 412cb2b33c9e117b59bf157d7492e20eb453243d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//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())
	}
}