github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/vminfo/syscalls.go (about) 1 // Copyright 2024 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package vminfo 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 "syscall" 11 12 "github.com/google/syzkaller/pkg/flatrpc" 13 "github.com/google/syzkaller/pkg/fuzzer/queue" 14 "github.com/google/syzkaller/pkg/ipc" 15 "github.com/google/syzkaller/pkg/mgrconfig" 16 "github.com/google/syzkaller/prog" 17 "github.com/google/syzkaller/sys/targets" 18 ) 19 20 // checkContext arranges checking of presence/support of all target syscalls. 21 // The actual checking is done by OS-specific impl.syscallCheck, 22 // while checkContext invokes that function for each syscall in a special manner 23 // and provides primitives for reading target VM files, checking if a file can be opened, 24 // executing test programs on the target VM, etc. 25 // 26 // The external interface of this type contains only 2 methods: 27 // startCheck - starts impl.syscallCheck goroutines and collects all test programs in progs, 28 // finishCheck - accepts results of program execution, unblocks impl.syscallCheck goroutines, 29 // 30 // waits and returns results of checking. 31 type checkContext struct { 32 ctx context.Context 33 impl checker 34 cfg *mgrconfig.Config 35 target *prog.Target 36 sandbox flatrpc.ExecEnv 37 executor queue.Executor 38 fs filesystem 39 // Once checking of a syscall is finished, the result is sent to syscalls. 40 // The main goroutine will wait for exactly pendingSyscalls messages. 41 syscalls chan syscallResult 42 pendingSyscalls int 43 features chan featureResult 44 } 45 46 type syscallResult struct { 47 call *prog.Syscall 48 reason string 49 } 50 51 func newCheckContext(ctx context.Context, cfg *mgrconfig.Config, impl checker, 52 executor queue.Executor) *checkContext { 53 sandbox, err := ipc.SandboxToFlags(cfg.Sandbox) 54 if err != nil { 55 panic(fmt.Sprintf("failed to parse sandbox: %v", err)) 56 } 57 return &checkContext{ 58 ctx: ctx, 59 impl: impl, 60 cfg: cfg, 61 target: cfg.Target, 62 sandbox: sandbox, 63 executor: executor, 64 syscalls: make(chan syscallResult), 65 features: make(chan featureResult, 100), 66 } 67 } 68 69 func (ctx *checkContext) start(fileInfos []*flatrpc.FileInfo) { 70 ctx.fs = createVirtualFilesystem(fileInfos) 71 for _, id := range ctx.cfg.Syscalls { 72 call := ctx.target.Syscalls[id] 73 if call.Attrs.Disabled { 74 continue 75 } 76 ctx.pendingSyscalls++ 77 syscallCheck := ctx.impl.syscallCheck 78 if strings.HasPrefix(call.CallName, "syz_ext_") { 79 // Non-mainline pseudo-syscalls in executor/common_ext.h can't have 80 // the checking function and are assumed to be unconditionally supported. 81 syscallCheck = alwaysSupported 82 } 83 // HostFuzzer targets can't run Go binaries on the targets, 84 // so we actually run on the host on another OS. The same for targets.TestOS OS. 85 if ctx.cfg.SysTarget.HostFuzzer || ctx.target.OS == targets.TestOS { 86 syscallCheck = alwaysSupported 87 } 88 go func() { 89 var reason string 90 deps := ctx.cfg.SysTarget.PseudoSyscallDeps[call.CallName] 91 if len(deps) != 0 { 92 reason = ctx.supportedSyscalls(deps) 93 } 94 // Only check the call if all its dependencies are satisfied. 95 if reason == "" { 96 reason = syscallCheck(ctx, call) 97 } 98 ctx.syscalls <- syscallResult{call, reason} 99 }() 100 } 101 ctx.startFeaturesCheck() 102 } 103 104 func (ctx *checkContext) wait(featureInfos []*flatrpc.FeatureInfo) ( 105 map[*prog.Syscall]bool, map[*prog.Syscall]string, Features, error) { 106 enabled := make(map[*prog.Syscall]bool) 107 disabled := make(map[*prog.Syscall]string) 108 for i := 0; i < ctx.pendingSyscalls; i++ { 109 res := <-ctx.syscalls 110 if res.reason == "" { 111 enabled[res.call] = true 112 } else { 113 disabled[res.call] = res.reason 114 } 115 } 116 features, err := ctx.finishFeatures(featureInfos) 117 return enabled, disabled, features, err 118 } 119 120 func (ctx *checkContext) rootCanOpen(file string) string { 121 return ctx.canOpenImpl(file, nil, true) 122 } 123 124 func (ctx *checkContext) canOpen(file string) string { 125 return ctx.canOpenImpl(file, nil, false) 126 } 127 128 func (ctx *checkContext) canWrite(file string) string { 129 return ctx.canOpenImpl(file, []uint64{ctx.val("O_WRONLY")}, false) 130 } 131 132 func (ctx *checkContext) canOpenImpl(file string, modes []uint64, root bool) string { 133 if len(modes) == 0 { 134 modes = ctx.allOpenModes() 135 } 136 var calls []string 137 for _, mode := range modes { 138 call := fmt.Sprintf("openat(0x%x, &AUTO='%s', 0x%x, 0x0)", ctx.val("AT_FDCWD"), file, mode) 139 calls = append(calls, call) 140 } 141 info := ctx.execRaw(calls, prog.StrictUnsafe, root) 142 for _, call := range info.Calls { 143 if call.Errno == 0 { 144 return "" 145 } 146 } 147 who := "" 148 if root { 149 who = "root " 150 } 151 return fmt.Sprintf("%vfailed to open %s: %v", who, file, syscall.Errno(info.Calls[0].Errno)) 152 } 153 154 func (ctx *checkContext) supportedSyscalls(names []string) string { 155 var calls []string 156 for _, name := range names { 157 if strings.HasPrefix(name, "syz_") { 158 panic("generic syscall check used for pseudo-syscall: " + name) 159 } 160 calls = append(calls, name+"()") 161 } 162 info := ctx.execRaw(calls, prog.NonStrictUnsafe, false) 163 for i, res := range info.Calls { 164 if res.Errno == int(syscall.ENOSYS) { 165 return fmt.Sprintf("syscall %v is not present", names[i]) 166 } 167 } 168 return "" 169 } 170 171 func supportedOpenat(ctx *checkContext, call *prog.Syscall) string { 172 fname, ok := extractStringConst(call.Args[1].Type) 173 if !ok || fname[0] != '/' { 174 return "" 175 } 176 modes := ctx.allOpenModes() 177 // Attempt to extract flags from the syscall description. 178 if mode, ok := call.Args[2].Type.(*prog.ConstType); ok { 179 modes = []uint64{mode.Val} 180 } 181 var calls []string 182 for _, mode := range modes { 183 call := fmt.Sprintf("openat(0x%0x, &AUTO='%v', 0x%x, 0x0)", ctx.val("AT_FDCWD"), fname, mode) 184 calls = append(calls, call) 185 } 186 return ctx.anyCallSucceeds(calls, fmt.Sprintf("failed to open %v", fname)) 187 } 188 189 func (ctx *checkContext) allOpenModes() []uint64 { 190 // Various open modes we need to try if we don't have a concrete mode. 191 // Some files can be opened only for reading, some only for writing, 192 // and some only in non-blocking mode. 193 // Note: some of these consts are different for different arches. 194 return []uint64{ctx.val("O_RDONLY"), ctx.val("O_WRONLY"), ctx.val("O_RDWR"), 195 ctx.val("O_RDONLY") | ctx.val("O_NONBLOCK")} 196 } 197 198 func (ctx *checkContext) callSucceeds(call string) string { 199 return ctx.anyCallSucceeds([]string{call}, call+" failed") 200 } 201 202 func (ctx *checkContext) execCall(call string) syscall.Errno { 203 info := ctx.execRaw([]string{call}, prog.StrictUnsafe, false) 204 return syscall.Errno(info.Calls[0].Errno) 205 } 206 207 func (ctx *checkContext) anyCallSucceeds(calls []string, msg string) string { 208 info := ctx.execRaw(calls, prog.StrictUnsafe, false) 209 for _, call := range info.Calls { 210 if call.Errno == 0 { 211 return "" 212 } 213 } 214 return fmt.Sprintf("%s: %v", msg, syscall.Errno(info.Calls[0].Errno)) 215 } 216 217 func (ctx *checkContext) onlySandboxNone() string { 218 if ctx.sandbox != 0 { 219 return "only supported under root with sandbox=none" 220 } 221 return "" 222 } 223 224 func (ctx *checkContext) onlySandboxNoneOrNamespace() string { 225 if ctx.sandbox != 0 && ctx.sandbox != flatrpc.ExecEnvSandboxNamespace { 226 return "only supported under root with sandbox=none/namespace" 227 } 228 return "" 229 } 230 231 func (ctx *checkContext) val(name string) uint64 { 232 val, ok := ctx.target.ConstMap[name] 233 if !ok { 234 panic(fmt.Sprintf("const %v is not present", name)) 235 } 236 return val 237 } 238 239 func (ctx *checkContext) execRaw(calls []string, mode prog.DeserializeMode, root bool) *ipc.ProgInfo { 240 sandbox := ctx.sandbox 241 if root { 242 sandbox = 0 243 } 244 info := &ipc.ProgInfo{} 245 for remain := calls; len(remain) != 0; { 246 // Don't put too many syscalls into a single program, 247 // it will have higher chances to time out. 248 ncalls := min(len(remain), prog.MaxCalls/2) 249 progStr := strings.Join(remain[:ncalls], "\n") 250 remain = remain[ncalls:] 251 p, err := ctx.target.Deserialize([]byte(progStr), mode) 252 if err != nil { 253 panic(fmt.Sprintf("failed to deserialize: %v\n%v", err, progStr)) 254 } 255 req := &queue.Request{ 256 Prog: p, 257 ExecOpts: ipc.ExecOpts{ 258 EnvFlags: sandbox, 259 ExecFlags: 0, 260 SandboxArg: ctx.cfg.SandboxArg, 261 }, 262 Important: true, 263 } 264 ctx.executor.Submit(req) 265 res := req.Wait(ctx.ctx) 266 if res.Status == queue.Success { 267 info.Calls = append(info.Calls, res.Info.Calls...) 268 } else if res.Status == queue.Crashed { 269 // Pretend these calls were not executed. 270 info.Calls = append(info.Calls, ipc.EmptyProgInfo(ncalls).Calls...) 271 } else { 272 // The program must have been either executed or not due to a crash. 273 panic(fmt.Sprintf("got unexpected execution status (%d) for the prog %s", 274 res.Status, progStr)) 275 } 276 } 277 if len(info.Calls) != len(calls) { 278 panic(fmt.Sprintf("got %v != %v results for program:\n%s", 279 len(info.Calls), len(calls), strings.Join(calls, "\n"))) 280 } 281 return info 282 } 283 284 func (ctx *checkContext) readFile(name string) ([]byte, error) { 285 return ctx.fs.ReadFile(name) 286 } 287 288 func alwaysSupported(ctx *checkContext, call *prog.Syscall) string { 289 return "" 290 } 291 292 func extractStringConst(typ prog.Type) (string, bool) { 293 ptr, ok := typ.(*prog.PtrType) 294 if !ok { 295 panic("first open arg is not a pointer to string const") 296 } 297 str, ok := ptr.Elem.(*prog.BufferType) 298 if !ok || str.Kind != prog.BufferString || len(str.Values) == 0 { 299 return "", false 300 } 301 v := str.Values[0] 302 for v != "" && v[len(v)-1] == 0 { 303 v = v[:len(v)-1] // string terminating \x00 304 } 305 return v, true 306 }