aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--config/config.go20
-rw-r--r--executor/executor.cc91
-rw-r--r--ipc/ipc.go31
-rw-r--r--syz-fuzzer/fuzzer.go5
-rw-r--r--syz-manager/manager.go4
-rw-r--r--tools/syz-execprog/execprog.go5
-rw-r--r--tools/syz-stress/stress.go5
8 files changed, 130 insertions, 40 deletions
diff --git a/README.md b/README.md
index 7f6dd03d9..976345adb 100644
--- a/README.md
+++ b/README.md
@@ -97,9 +97,12 @@ following keys in its top-level object:
the virtual machine.
- `cpu`: Number of CPUs to simulate in the VM (*not currently used*).
- `mem`: Amount of memory (in MiB) for the VM; this is passed as the `-m` option to `qemu-system-x86_64`.
- - `dropprivs` : Whether the executor program should try to use namespaces to drop privileges
- before executing (requires a kernel built with `CONFIG_NAMESPACES`, `CONFIG_UTS_NS`,
- `CONFIG_USER_NS`, `CONFIG_PID_NS` and `CONFIG_NET_NS`).
+ - `sandbox` : Sandboxing mode, one of "none", "setuid", "namespace".
+ "none": don't do anything special (has false positives, e.g. due to killing init)
+ "setuid": impersonate into user nobody (65534), default
+ "namespace": use namespaces to drop privileges,
+ (requires a kernel built with `CONFIG_NAMESPACES`, `CONFIG_UTS_NS`,
+ `CONFIG_USER_NS`, `CONFIG_PID_NS` and `CONFIG_NET_NS`).
- `enable_syscalls`: List of syscalls to test (optional).
- `disable_syscalls`: List of system calls that should be treated as disabled (optional).
- `suppressions`: List of regexps for known bugs.
diff --git a/config/config.go b/config/config.go
index 7142139d2..ce5b8553b 100644
--- a/config/config.go
+++ b/config/config.go
@@ -37,9 +37,14 @@ type Config struct {
Count int // number of VMs
Procs int // number of parallel processes inside of every VM
- Cover bool // use kcov coverage (default: true)
- DropPrivs bool // drop privileges during fuzzing (default: true)
- Leak bool // do memory leak checking
+ Sandbox string // type of sandbox to use during fuzzing:
+ // "none": don't do anything special (has false positives, e.g. due to killing init)
+ // "setuid": impersonate into user nobody (65534), default
+ // "namespace": create a new namespace for fuzzer using CLONE_NEWNS/CLONE_NEWNET/CLONE_NEWPID/etc,
+ // requires building kernel with CONFIG_NAMESPACES, CONFIG_UTS_NS, CONFIG_USER_NS, CONFIG_PID_NS and CONFIG_NET_NS.
+
+ Cover bool // use kcov coverage (default: true)
+ Leak bool // do memory leak checking
ConsoleDev string // console device for adb vm
@@ -69,7 +74,7 @@ func parse(data []byte) (*Config, map[int]bool, []*regexp.Regexp, error) {
}
cfg := new(Config)
cfg.Cover = true
- cfg.DropPrivs = true
+ cfg.Sandbox = "setuid"
if err := json.Unmarshal(data, cfg); err != nil {
return nil, nil, nil, fmt.Errorf("failed to parse config file: %v", err)
}
@@ -109,6 +114,11 @@ func parse(data []byte) (*Config, map[int]bool, []*regexp.Regexp, error) {
default:
return nil, nil, nil, fmt.Errorf("config param output must contain one of none/stdout/dmesg/file")
}
+ switch cfg.Sandbox {
+ case "none", "setuid", "namespace":
+ default:
+ return nil, nil, nil, fmt.Errorf("config param sandbox must contain one of none/setuid/namespace")
+ }
syscalls, err := parseSyscalls(cfg)
if err != nil {
@@ -240,7 +250,7 @@ func checkUnknownFields(data []byte) (string, error) {
"Count",
"Procs",
"Cover",
- "DropPrivs",
+ "Sandbox",
"Leak",
"ConsoleDev",
"Enable_Syscalls",
diff --git a/executor/executor.cc b/executor/executor.cc
index e25309257..52275b185 100644
--- a/executor/executor.cc
+++ b/executor/executor.cc
@@ -67,12 +67,19 @@ const int kRetryStatus = 69;
// so good enough for our purposes.
const uint64_t default_value = -1;
+enum sandbox_type {
+ sandbox_none,
+ sandbox_setuid,
+ sandbox_namespace,
+};
+
bool flag_debug;
bool flag_cover;
bool flag_threaded;
bool flag_collide;
bool flag_deduplicate;
-bool flag_drop_privs;
+bool flag_sandbox_privs;
+sandbox_type flag_sandbox;
__attribute__((aligned(64 << 10))) char input_data[kMaxInput];
__attribute__((aligned(64 << 10))) char output_data[kMaxOutput];
@@ -117,7 +124,11 @@ __attribute__((noreturn)) void fail(const char* msg, ...);
__attribute__((noreturn)) void error(const char* msg, ...);
__attribute__((noreturn)) void exitf(const char* msg, ...);
void debug(const char* msg, ...);
-int sandbox(void* arg);
+int sandbox_proc(void* arg);
+int do_sandbox_none();
+int do_sandbox_setuid();
+int do_sandbox_namespace();
+void sandbox_common();
void loop();
void execute_one();
uint64_t read_input(uint64_t** input_posp, bool peek = false);
@@ -165,7 +176,11 @@ int main(int argc, char** argv)
flag_threaded = flags & (1 << 2);
flag_collide = flags & (1 << 3);
flag_deduplicate = flags & (1 << 4);
- flag_drop_privs = flags & (1 << 5);
+ flag_sandbox = sandbox_none;
+ if (flags & (1 << 5))
+ flag_sandbox = sandbox_setuid;
+ else if (flags & (1 << 6))
+ flag_sandbox = sandbox_namespace;
if (!flag_threaded)
flag_collide = false;
@@ -181,17 +196,18 @@ int main(int argc, char** argv)
syscall(SYS_rt_sigaction, 0x21, &sa, NULL, 8);
int pid = -1;
- if (flag_drop_privs) {
- real_uid = getuid();
- real_gid = getgid();
- pid = clone(sandbox, &sandbox_stack[sizeof(sandbox_stack) - 8],
- CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL);
- } else {
- pid = fork();
- if (pid == 0) {
- loop();
- exit(1);
- }
+ switch (flag_sandbox) {
+ case sandbox_none:
+ pid = do_sandbox_none();
+ break;
+ case sandbox_setuid:
+ pid = do_sandbox_setuid();
+ break;
+ case sandbox_namespace:
+ pid = do_sandbox_namespace();
+ break;
+ default:
+ fail("unknown sandbox type");
}
if (pid < 0)
fail("clone failed");
@@ -282,7 +298,46 @@ void loop()
}
}
-int sandbox(void* arg)
+int do_sandbox_none()
+{
+ int pid = fork();
+ if (pid)
+ return pid;
+ loop();
+ exit(1);
+}
+
+int do_sandbox_setuid()
+{
+ int pid = fork();
+ if (pid)
+ return pid;
+
+ sandbox_common();
+
+ const int nobody = 65534;
+ if (setgroups(0, NULL))
+ fail("failed to setgroups");
+ // glibc versions do not we want -- they force all threads to setuid.
+ // We want to preserve the thread above as root.
+ if (syscall(SYS_setresgid, nobody, nobody, nobody))
+ fail("failed to setresgid");
+ if (syscall(SYS_setresuid, nobody, nobody, nobody))
+ fail("failed to setresuid");
+
+ loop();
+ exit(1);
+}
+
+int do_sandbox_namespace()
+{
+ real_uid = getuid();
+ real_gid = getgid();
+ return clone(sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 8],
+ CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL);
+}
+
+void sandbox_common()
{
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setpgrp();
@@ -299,8 +354,14 @@ int sandbox(void* arg)
setrlimit(RLIMIT_CORE, &rlim);
// CLONE_NEWIPC/CLONE_IO cause EINVAL on some systems, so we do them separately of clone.
+ unshare(CLONE_NEWNS);
unshare(CLONE_NEWIPC);
unshare(CLONE_IO);
+}
+
+int sandbox_proc(void* arg)
+{
+ sandbox_common();
// /proc/self/setgroups is not present on some systems, ignore error.
write_file("/proc/self/setgroups", "deny");
diff --git a/ipc/ipc.go b/ipc/ipc.go
index 8301d0f78..2d4686bfc 100644
--- a/ipc/ipc.go
+++ b/ipc/ipc.go
@@ -37,19 +37,20 @@ type Env struct {
}
const (
- FlagDebug = uint64(1) << iota // debug output from executor
- FlagCover // collect coverage
- FlagThreaded // use multiple threads to mitigate blocked syscalls
- FlagCollide // collide syscalls to provoke data races
- FlagDedupCover // deduplicate coverage in executor
- FlagDropPrivs // impersonate nobody user
+ FlagDebug = uint64(1) << iota // debug output from executor
+ FlagCover // collect coverage
+ FlagThreaded // use multiple threads to mitigate blocked syscalls
+ FlagCollide // collide syscalls to provoke data races
+ FlagDedupCover // deduplicate coverage in executor
+ FlagSandboxSetuid // impersonate nobody user
+ FlagSandboxNamespace // use namespaces for sandboxing
)
var (
flagThreaded = flag.Bool("threaded", true, "use threaded mode in executor")
flagCollide = flag.Bool("collide", true, "collide syscalls to provoke data races")
flagCover = flag.Bool("cover", true, "collect coverage")
- flagNobody = flag.Bool("nobody", true, "impersonate into nobody")
+ flagSandbox = flag.String("sandbox", "setuid", "sandbox for fuzzing (none/setuid/namespace)")
flagDebug = flag.Bool("debug", false, "debug output from executor")
// Executor protects against most hangs, so we use quite large timeout here.
// Executor can be slow due to global locks in namespaces and other things,
@@ -57,7 +58,7 @@ var (
flagTimeout = flag.Duration("timeout", 1*time.Minute, "execution timeout")
)
-func DefaultFlags() (uint64, time.Duration) {
+func DefaultFlags() (uint64, time.Duration, error) {
var flags uint64
if *flagThreaded {
flags |= FlagThreaded
@@ -69,13 +70,19 @@ func DefaultFlags() (uint64, time.Duration) {
flags |= FlagCover
flags |= FlagDedupCover
}
- if *flagNobody {
- flags |= FlagDropPrivs
+ switch *flagSandbox {
+ case "none":
+ case "setuid":
+ flags |= FlagSandboxSetuid
+ case "namespace":
+ flags |= FlagSandboxNamespace
+ default:
+ return 0, 0, fmt.Errorf("flag sandbox must contain one of none/setuid/namespace")
}
if *flagDebug {
flags |= FlagDebug
}
- return flags, *flagTimeout
+ return flags, *flagTimeout, nil
}
func MakeEnv(bin string, timeout time.Duration, flags uint64) (*Env, error) {
@@ -311,7 +318,7 @@ func makeCommand(bin []string, timeout time.Duration, flags uint64, inFile *os.F
}
}()
- if flags&FlagDropPrivs != 0 {
+ if flags&(FlagSandboxSetuid|FlagSandboxNamespace) != 0 {
if err := os.Chmod(dir, 0777); err != nil {
return nil, fmt.Errorf("failed to chmod temp dir: %v", err)
}
diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go
index f1a459b45..cc7182af7 100644
--- a/syz-fuzzer/fuzzer.go
+++ b/syz-fuzzer/fuzzer.go
@@ -119,7 +119,10 @@ func main() {
kmemleakInit()
- flags, timeout := ipc.DefaultFlags()
+ flags, timeout, err := ipc.DefaultFlags()
+ if err != nil {
+ panic(err)
+ }
noCover = flags&ipc.FlagCover == 0
if !noCover {
fd, err := syscall.Open("/sys/kernel/debug/kcov", syscall.O_RDWR, 0)
diff --git a/syz-manager/manager.go b/syz-manager/manager.go
index 5afbe7972..35e9fd8f6 100644
--- a/syz-manager/manager.go
+++ b/syz-manager/manager.go
@@ -237,8 +237,8 @@ func (mgr *Manager) runInstance(vmCfg *vm.Config, first bool) bool {
leak := first && mgr.cfg.Leak
// Run the fuzzer binary.
- outputC, errorC, err := inst.Run(time.Hour, fmt.Sprintf("%v -executor %v -name %v -manager %v -output=%v -procs %v -leak=%v -cover=%v -nobody=%v -v %d",
- fuzzerBin, executorBin, vmCfg.Name, fwdAddr, mgr.cfg.Output, mgr.cfg.Procs, leak, mgr.cfg.Cover, mgr.cfg.DropPrivs, *flagV))
+ outputC, errorC, err := inst.Run(time.Hour, fmt.Sprintf("%v -executor %v -name %v -manager %v -output=%v -procs %v -leak=%v -cover=%v -sandbox=%v -v %d",
+ fuzzerBin, executorBin, vmCfg.Name, fwdAddr, mgr.cfg.Output, mgr.cfg.Procs, leak, mgr.cfg.Cover, mgr.cfg.Sandbox, *flagV))
if err != nil {
logf(0, "failed to run fuzzer: %v", err)
return false
diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go
index fdba0f258..5b7610754 100644
--- a/tools/syz-execprog/execprog.go
+++ b/tools/syz-execprog/execprog.go
@@ -55,7 +55,10 @@ func main() {
return
}
- flags, timeout := ipc.DefaultFlags()
+ flags, timeout, err := ipc.DefaultFlags()
+ if err != nil {
+ log.Fatalf("%v", err)
+ }
if *flagCoverFile != "" {
flags |= ipc.FlagCover
flags &= ^ipc.FlagDedupCover
diff --git a/tools/syz-stress/stress.go b/tools/syz-stress/stress.go
index de5f8ddf8..bb2352f76 100644
--- a/tools/syz-stress/stress.go
+++ b/tools/syz-stress/stress.go
@@ -47,7 +47,10 @@ func main() {
prios := prog.CalculatePriorities(corpus)
ct := prog.BuildChoiceTable(prios, calls)
- flags, timeout := ipc.DefaultFlags()
+ flags, timeout, err := ipc.DefaultFlags()
+ if err != nil {
+ failf("%v", err)
+ }
gate = ipc.NewGate(2**flagProcs, nil)
for pid := 0; pid < *flagProcs; pid++ {
pid := pid