// 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
	if resource == 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
	} else if resource == 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
	} else {
		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:
			if v.Val < rlimitMin {
				v.Val = rlimitMin
			}
			if v.Val > rlimitMax {
				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 ""
}
