// Copyright 2018 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 targets

import (
	"github.com/google/syzkaller/prog"
)

// MakePosixMmap creates a "normal" posix mmap call that maps [addr, addr+size) range.
func MakePosixMmap(target *prog.Target) func(addr, size uint64) *prog.Call {
	meta := target.SyscallMap["mmap"]
	prot := target.ConstMap["PROT_READ"] | target.ConstMap["PROT_WRITE"]
	flags := target.ConstMap["MAP_ANONYMOUS"] | target.ConstMap["MAP_PRIVATE"] | target.ConstMap["MAP_FIXED"]
	const invalidFD = ^uint64(0)
	return func(addr, size uint64) *prog.Call {
		return &prog.Call{
			Meta: meta,
			Args: []prog.Arg{
				prog.MakeVmaPointerArg(meta.Args[0], addr, size),
				prog.MakeConstArg(meta.Args[1], size),
				prog.MakeConstArg(meta.Args[2], prot),
				prog.MakeConstArg(meta.Args[3], flags),
				prog.MakeResultArg(meta.Args[4], nil, invalidFD),
				prog.MakeConstArg(meta.Args[5], 0),
			},
			Ret: prog.MakeReturnArg(meta.Ret),
		}
	}
}

func MakeSyzMmap(target *prog.Target) func(addr, size uint64) *prog.Call {
	meta := target.SyscallMap["syz_mmap"]
	return func(addr, size uint64) *prog.Call {
		return &prog.Call{
			Meta: meta,
			Args: []prog.Arg{
				prog.MakeVmaPointerArg(meta.Args[0], addr, size),
				prog.MakeConstArg(meta.Args[1], size),
			},
			Ret: prog.MakeReturnArg(meta.Ret),
		}
	}
}

type UnixSanitizer struct {
	MAP_FIXED      uint64
	MREMAP_MAYMOVE uint64
	MREMAP_FIXED   uint64
	S_IFREG        uint64
	S_IFCHR        uint64
	S_IFBLK        uint64
	S_IFIFO        uint64
	S_IFSOCK       uint64
}

func MakeUnixSanitizer(target *prog.Target) *UnixSanitizer {
	return &UnixSanitizer{
		MAP_FIXED:      target.ConstMap["MAP_FIXED"],
		MREMAP_MAYMOVE: target.ConstMap["MREMAP_MAYMOVE"],
		MREMAP_FIXED:   target.ConstMap["MREMAP_FIXED"],
		S_IFREG:        target.ConstMap["S_IFREG"],
		S_IFCHR:        target.ConstMap["S_IFCHR"],
		S_IFBLK:        target.ConstMap["S_IFBLK"],
		S_IFIFO:        target.ConstMap["S_IFIFO"],
		S_IFSOCK:       target.ConstMap["S_IFSOCK"],
	}
}

func (arch *UnixSanitizer) SanitizeCall(c *prog.Call) {
	switch c.Meta.CallName {
	case "mmap":
		// Add MAP_FIXED flag, otherwise it produces non-deterministic results.
		c.Args[3].(*prog.ConstArg).Val |= arch.MAP_FIXED
	case "mremap":
		// Add MREMAP_FIXED flag, otherwise it produces non-deterministic results.
		flags := c.Args[3].(*prog.ConstArg)
		if flags.Val&arch.MREMAP_MAYMOVE != 0 {
			flags.Val |= arch.MREMAP_FIXED
		}
	case "mknod", "mknodat":
		pos := 1
		if c.Meta.CallName == "mknodat" {
			pos = 2
		}
		if _, ok := c.Args[pos+1].Type().(*prog.ProcType); ok {
			return
		}
		mode := c.Args[pos].(*prog.ConstArg)
		dev := c.Args[pos+1].(*prog.ConstArg)
		dev.Val = uint64(uint32(dev.Val))
		// Char and block devices read/write io ports, kernel memory and do other nasty things.
		// TODO: not required if executor drops privileges.
		mask := arch.S_IFREG | arch.S_IFCHR | arch.S_IFBLK | arch.S_IFIFO | arch.S_IFSOCK
		switch mode.Val & mask {
		case arch.S_IFREG, arch.S_IFIFO, arch.S_IFSOCK:
		case arch.S_IFBLK:
			if dev.Val>>8 == 7 {
				break // loop
			}
			mode.Val &^= arch.S_IFBLK
			mode.Val |= arch.S_IFREG
		case arch.S_IFCHR:
			mode.Val &^= arch.S_IFCHR
			mode.Val |= arch.S_IFREG
		}
	case "exit", "exit_group":
		code := c.Args[0].(*prog.ConstArg)
		// These codes are reserved by executor.
		if code.Val%128 == 67 || code.Val%128 == 68 {
			code.Val = 1
		}
	}
}