// Copyright 2017 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package openbsd import ( "fmt" "math" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/sys/targets" ) func InitTarget(target *prog.Target) { arch := &arch{ unix: targets.MakeUnixNeutralizer(target), CLOCK_REALTIME: target.GetConst("CLOCK_REALTIME"), CTL_KERN: target.GetConst("CTL_KERN"), DIOCCLRSTATES: target.GetConst("DIOCCLRSTATES"), DIOCKILLSTATES: target.GetConst("DIOCKILLSTATES"), KERN_MAXCLUSTERS: target.GetConst("KERN_MAXCLUSTERS"), KERN_MAXPROC: target.GetConst("KERN_MAXPROC"), KERN_MAXFILES: target.GetConst("KERN_MAXFILES"), KERN_MAXTHREAD: target.GetConst("KERN_MAXTHREAD"), KERN_WITNESS: target.GetConst("KERN_WITNESS"), S_IFCHR: target.GetConst("S_IFCHR"), S_IFMT: target.GetConst("S_IFMT"), MCL_FUTURE: target.GetConst("MCL_FUTURE"), RLIMIT_DATA: target.GetConst("RLIMIT_DATA"), RLIMIT_STACK: target.GetConst("RLIMIT_STACK"), } target.MakeDataMmap = targets.MakePosixMmap(target, false, false) target.Neutralize = arch.neutralize target.AnnotateCall = arch.annotateCall } type arch struct { unix *targets.UnixNeutralizer CLOCK_REALTIME uint64 CTL_KERN uint64 DIOCCLRSTATES uint64 DIOCKILLSTATES uint64 KERN_MAXCLUSTERS uint64 KERN_MAXPROC uint64 KERN_MAXFILES uint64 KERN_MAXTHREAD uint64 KERN_WITNESS uint64 S_IFCHR uint64 S_IFMT uint64 MCL_FUTURE uint64 RLIMIT_DATA uint64 RLIMIT_STACK uint64 } const ( mknodMode = 0 mknodDev = 1 // openbsd:src/etc/etc.amd64/MAKEDEV devFdMajor = 22 devNullDevT = 0x0202 // Mask covering all valid rlimit resources. rlimitMask = 0xf ) // openbsd:src/sys/sys/types.h func devmajor(dev uint64) uint64 { return (dev >> 8) & 0xff } // openbsd:src/sys/sys/types.h func devminor(dev uint64) uint64 { return (dev & 0xff) | ((dev & 0xffff0000) >> 8) } func isExecutorFd(dev uint64) bool { major := devmajor(dev) minor := devminor(dev) return major == devFdMajor && minor >= 200 } func (arch *arch) neutralize(c *prog.Call, fixStructure bool) error { argStart := 1 switch c.Meta.CallName { case "chflagsat": argStart = 2 fallthrough case "chflags", "fchflags": // Prevent changing mutability flags on files. This is // especially problematic for file descriptors referring to // tty/pty devices since it can cause the SSH connection to the // VM to die. flags := c.Args[argStart].(*prog.ConstArg) badflags := [...]uint64{ 0x00000002, // UF_IMMUTABLE 0x00000004, // UF_APPEND 0x00020000, // SF_IMMUTABLE 0x00040000, // SF_APPEND } for _, f := range badflags { flags.Val &= ^f } case "clock_settime": arch.neutralizeClockSettime(c) case "ioctl": // Performing the following ioctl commands on a /dev/pf file // descriptor causes the ssh VM connection to die. For now, just // rewire them to an invalid command. request := c.Args[1].(*prog.ConstArg) if request.Val == arch.DIOCCLRSTATES || request.Val == arch.DIOCKILLSTATES { request.Val = 0 } case "mknodat": argStart = 2 fallthrough case "mknod": // Prevent vnodes of type VBAD from being created. Such vnodes will // likely trigger assertion errors by the kernel. mode := c.Args[argStart+mknodMode].(*prog.ConstArg) if mode.Val&arch.S_IFMT == arch.S_IFMT { mode.Val &^= arch.S_IFMT mode.Val |= arch.S_IFCHR } // Prevent certain /dev/fd/X devices from getting created since // they belong to the executor. It's especially dangerous to let // the executor interact with kcov file descriptors since it can // interfere with the coverage collection and cause corpus // explosion. // https://groups.google.com/d/msg/syzkaller/_IRWeAjVoy4/Akl2XMZTDAAJ dev := c.Args[argStart+mknodDev].(*prog.ConstArg) if isExecutorFd(dev.Val) { dev.Val = devNullDevT } // Prevent /dev/sd0b (swap partition) and /dev/sd0c (raw disk) // nodes from being created. Writing to such devices can corrupt // the file system. if devmajor(dev.Val) == 4 && (devminor(dev.Val) == 1 || devminor(dev.Val) == 2) { dev.Val = devNullDevT } case "mlockall": flags := c.Args[0].(*prog.ConstArg) flags.Val &= ^arch.MCL_FUTURE case "setrlimit": arch.neutralizeRlimit(c) case "sysctl": arch.neutralizeSysctl(c) default: return arch.unix.Neutralize(c, fixStructure) } return nil } func (arch *arch) neutralizeClockSettime(c *prog.Call) { switch v := c.Args[0].(type) { case *prog.ConstArg: // Do not fiddle with the wall clock, one of the causes of "no // output from test machine" reports. if v.Val == arch.CLOCK_REALTIME { v.Val = ^uint64(0) } } } func (arch *arch) neutralizeRlimit(c *prog.Call) { rlimitMin := uint64(0) rlimitMax := uint64(math.MaxUint64) resource := c.Args[0].(*prog.ConstArg).Val & rlimitMask switch resource { case arch.RLIMIT_DATA: // OpenBSD performs a strict validation of the RLIMIT_DATA soft // limit during memory allocation. Lowering the same limit could // cause syz-executor to run out of memory quickly. Therefore // make sure to not go lower than the default soft limit for the // staff group. rlimitMin = 1536 * 1024 * 1024 case arch.RLIMIT_STACK: // Do not allow the stack to grow beyond the initial soft limit // chosen by syz-executor. Otherwise, syz-executor will most // likely not be able to perform any more heap allocations since // they majority of memory is reserved for the stack. rlimitMax = 1 * 1024 * 1024 default: return } ptr := c.Args[1].(*prog.PointerArg) if ptr.Res == nil { return } args := ptr.Res.(*prog.GroupArg).Inner for _, arg := range args { switch v := arg.(type) { case *prog.ConstArg: v.Val = max(v.Val, rlimitMin) v.Val = min(v.Val, rlimitMax) } } } func (arch *arch) neutralizeSysctl(c *prog.Call) { ptr := c.Args[0].(*prog.PointerArg) if ptr.Res == nil { return } var mib []*prog.ConstArg for _, arg := range ptr.Res.(*prog.GroupArg).Inner { switch v := arg.(type) { case *prog.ConstArg: mib = append(mib, v) } } if !arch.neutralizeSysctlKern(mib) { return } for _, m := range mib { m.Val = 0 } // Reflect changes in the namelen argument. if len(c.Args) >= 1 { switch v := c.Args[1].(type) { case *prog.ConstArg: v.Val = 0 } } } func (arch *arch) neutralizeSysctlKern(mib []*prog.ConstArg) bool { // Do not fiddle with root only knob kern.maxclusters, one of the causes // of "no output from test machine" reports. if len(mib) >= 2 && mib[0].Val == arch.CTL_KERN && mib[1].Val == arch.KERN_MAXCLUSTERS { return true } // Do not fiddle with root only knob kern.maxproc, can cause the // syz-execprog to run out of resources. if len(mib) >= 2 && mib[0].Val == arch.CTL_KERN && mib[1].Val == arch.KERN_MAXPROC { return true } // Do not fiddle with root only knob kern.maxfiles, can cause the // syz-execprog to run out of resources. if len(mib) >= 2 && mib[0].Val == arch.CTL_KERN && mib[1].Val == arch.KERN_MAXFILES { return true } // Do not fiddle with root only knob kern.maxthread, can cause the // syz-execprog process to panic. if len(mib) >= 2 && mib[0].Val == arch.CTL_KERN && mib[1].Val == arch.KERN_MAXTHREAD { return true } if len(mib) >= 2 && mib[0].Val == arch.CTL_KERN && mib[1].Val == arch.KERN_WITNESS { return true } return false } func (arch *arch) annotateCall(c prog.ExecCall) string { devArg := 2 switch c.Meta.Name { case "mknodat": devArg = 3 fallthrough case "mknod": dev := c.Args[devArg].(prog.ExecArgConst).Value return fmt.Sprintf("major = %v, minor = %v", devmajor(dev), devminor(dev)) } return "" }