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