From 6ca567477c3273dede17314469e86ceee902a28f Mon Sep 17 00:00:00 2001
From: Ali Polatel <alip@hexsys.org>
Date: Sun, 15 Feb 2026 21:29:47 +0100
Subject: [PATCH] *: support new sandbox mode syd for external sandboxing

Signed-off-by: Ali Polatel <alip@hexsys.org>
---
 executor/common.h              |  7 ++++
 executor/common_linux.h        | 13 +++++++
 executor/executor.cc           |  8 +++--
 pkg/csource/common.go          |  2 ++
 pkg/csource/options.go         |  5 +--
 pkg/flatrpc/flatrpc.fbs        |  2 ++
 pkg/flatrpc/flatrpc.go         | 54 ++++++++++++++++-------------
 pkg/flatrpc/flatrpc.h          | 62 +++++++++++++++++++---------------
 pkg/flatrpc/helpers.go         |  4 ++-
 pkg/fuzzer/queue/queue.go      |  3 +-
 pkg/ifaceprobe/ifaceprobe.go   |  9 +++--
 pkg/instance/instance_test.go  |  2 +-
 pkg/mgrconfig/load.go          |  4 +--
 pkg/vminfo/features.go         | 39 ++++++++++++++++++++-
 pkg/vminfo/syscalls.go         |  3 ++
 tools/syz-execprog/execprog.go |  2 +-
 16 files changed, 154 insertions(+), 65 deletions(-)

diff --git a/executor/common.h b/executor/common.h
index e7e02ffb2..2c1e61604 100644
--- a/executor/common.h
+++ b/executor/common.h
@@ -258,6 +258,13 @@ static void __attribute__((noinline)) remove_dir(const char* dir)
 {
 	DIR* dp = opendir(dir);
 	if (dp == NULL) {
+		if (errno == ENOENT) {
+			// The directory was already removed by a fuzzing program
+			// (e.g. rmdir, rename, or umount of the work directory).
+			// This is expected under sandbox=syd where programs can
+			// manipulate the filesystem freely.
+			return;
+		}
 		if (errno == EACCES) {
 			// We could end up here in a recursive call to remove_dir() below.
 			// One of executed syscall could end up creating a directory rooted
diff --git a/executor/common_linux.h b/executor/common_linux.h
index 7c4fd9b68..d9606bf5a 100644
--- a/executor/common_linux.h
+++ b/executor/common_linux.h
@@ -4237,6 +4237,19 @@ static int do_sandbox_none(void)
 }
 #endif
 
+// Syd provides external sandboxing, so the executor skips sandboxing.
+static int do_sandbox_syd(void)
+{
+	pid_t pid = fork();
+	if (pid != 0)
+		return wait_for_loop(pid);
+	prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
+
+	use_temporary_dir();
+	loop();
+	doexit(1);
+}
+
 #if SYZ_EXECUTOR || SYZ_SANDBOX_SETUID
 #include <grp.h>
 #include <sched.h>
diff --git a/executor/executor.cc b/executor/executor.cc
index d6bba7aa7..2277f130f 100644
--- a/executor/executor.cc
+++ b/executor/executor.cc
@@ -262,6 +262,7 @@ static bool flag_sandbox_none;
 static bool flag_sandbox_setuid;
 static bool flag_sandbox_namespace;
 static bool flag_sandbox_android;
+static bool flag_sandbox_syd;
 static bool flag_extra_coverage;
 static bool flag_net_injection;
 static bool flag_net_devices;
@@ -689,6 +690,8 @@ int main(int argc, char** argv)
 	else if (flag_sandbox_android)
 		status = do_sandbox_android(sandbox_arg);
 #endif
+	else if (flag_sandbox_syd)
+		status = do_sandbox_syd();
 	else
 		fail("unknown sandbox type");
 
@@ -836,6 +839,7 @@ void parse_handshake(const handshake_req& req)
 	flag_sandbox_setuid = (bool)(req.flags & rpc::ExecEnv::SandboxSetuid);
 	flag_sandbox_namespace = (bool)(req.flags & rpc::ExecEnv::SandboxNamespace);
 	flag_sandbox_android = (bool)(req.flags & rpc::ExecEnv::SandboxAndroid);
+	flag_sandbox_syd = (bool)(req.flags & rpc::ExecEnv::SandboxSyd);
 	flag_extra_coverage = (bool)(req.flags & rpc::ExecEnv::ExtraCover);
 	flag_net_injection = (bool)(req.flags & rpc::ExecEnv::EnableTun);
 	flag_net_devices = (bool)(req.flags & rpc::ExecEnv::EnableNetDev);
@@ -873,10 +877,10 @@ void parse_execute(const execute_req& req)
 	all_extra_signal = req.all_extra_signal;
 
 	debug("[%llums] exec opts: reqid=%llu type=%llu procid=%llu threaded=%d cover=%d comps=%d dedup=%d signal=%d "
-	      " sandbox=%d/%d/%d/%d timeouts=%llu/%llu/%llu kernel_64_bit=%d\n",
+	      " sandbox=%d/%d/%d/%d/%d timeouts=%llu/%llu/%llu kernel_64_bit=%d\n",
 	      current_time_ms() - start_time_ms, request_id, (uint64)request_type, procid, flag_threaded, flag_collect_cover,
 	      flag_comparisons, flag_dedup_cover, flag_collect_signal, flag_sandbox_none, flag_sandbox_setuid,
-	      flag_sandbox_namespace, flag_sandbox_android, syscall_timeout_ms, program_timeout_ms, slowdown_scale,
+	      flag_sandbox_namespace, flag_sandbox_android, flag_sandbox_syd, syscall_timeout_ms, program_timeout_ms, slowdown_scale,
 	      is_kernel_64_bit);
 	if (syscall_timeout_ms == 0 || program_timeout_ms <= syscall_timeout_ms || slowdown_scale == 0)
 		failmsg("bad timeouts", "syscall=%llu, program=%llu, scale=%llu",
diff --git a/pkg/csource/common.go b/pkg/csource/common.go
index f71926659..0be03fcec 100644
--- a/pkg/csource/common.go
+++ b/pkg/csource/common.go
@@ -22,6 +22,7 @@ const (
 	sandboxSetuid    = "setuid"
 	sandboxNamespace = "namespace"
 	sandboxAndroid   = "android"
+	sandboxSyd       = "syd"
 )
 
 func createCommonHeader(p, mmapProg *prog.Prog, replacements map[string]string, opts Options) ([]byte, error) {
@@ -101,6 +102,7 @@ func commonDefines(p *prog.Prog, opts Options) map[string]bool {
 		"SYZ_SANDBOX_SETUID":            opts.Sandbox == sandboxSetuid,
 		"SYZ_SANDBOX_NAMESPACE":         opts.Sandbox == sandboxNamespace,
 		"SYZ_SANDBOX_ANDROID":           opts.Sandbox == sandboxAndroid,
+		"SYZ_SANDBOX_SYD":               opts.Sandbox == sandboxSyd,
 		"SYZ_THREADED":                  opts.Threaded,
 		"SYZ_ASYNC":                     features.Async,
 		"SYZ_REPEAT":                    opts.Repeat,
diff --git a/pkg/csource/options.go b/pkg/csource/options.go
index 4438e5df2..95497ce4c 100644
--- a/pkg/csource/options.go
+++ b/pkg/csource/options.go
@@ -69,7 +69,7 @@ type LegacyOptions struct {
 // Invalid combinations must not be passed to Write.
 func (opts Options) Check(OS string) error {
 	switch opts.Sandbox {
-	case "", sandboxNone, sandboxNamespace, sandboxSetuid, sandboxAndroid:
+	case "", sandboxNone, sandboxNamespace, sandboxSetuid, sandboxAndroid, sandboxSyd:
 	default:
 		return fmt.Errorf("unknown sandbox %v", opts.Sandbox)
 	}
@@ -133,7 +133,8 @@ func (opts Options) checkLinuxOnly(OS string) error {
 	}
 	if opts.Sandbox == sandboxNamespace ||
 		(opts.Sandbox == sandboxSetuid && OS != targets.OpenBSD && OS != targets.FreeBSD && OS != targets.NetBSD) ||
-		opts.Sandbox == sandboxAndroid {
+		opts.Sandbox == sandboxAndroid ||
+		opts.Sandbox == sandboxSyd {
 		return fmt.Errorf("option Sandbox=%v is not supported on %v", opts.Sandbox, OS)
 	}
 	for name, opt := range map[string]*bool{
diff --git a/pkg/flatrpc/flatrpc.fbs b/pkg/flatrpc/flatrpc.fbs
index 3876af965..884a8360e 100644
--- a/pkg/flatrpc/flatrpc.fbs
+++ b/pkg/flatrpc/flatrpc.fbs
@@ -21,6 +21,7 @@ enum Feature : uint64 (bit_flags) {
 	SandboxSetuid,
 	SandboxNamespace,
 	SandboxAndroid,
+	SandboxSyd,
 	Fault,
 	Leak,
 	NetInjection,
@@ -142,6 +143,7 @@ enum ExecEnv : uint64 (bit_flags) {
 	SandboxSetuid,		// impersonate nobody user
 	SandboxNamespace,	// use namespaces for sandboxing
 	SandboxAndroid,		// use Android sandboxing for the untrusted_app domain
+	SandboxSyd,		// external Syd sandbox, no namespace/net/tmpfs setup
 	ExtraCover,		// collect extra coverage
 	EnableTun,		// setup and use /dev/tun for packet injection
 	EnableNetDev,		// setup more network devices for testing
diff --git a/pkg/flatrpc/flatrpc.go b/pkg/flatrpc/flatrpc.go
index 439d06b67..72ef96dc9 100644
--- a/pkg/flatrpc/flatrpc.go
+++ b/pkg/flatrpc/flatrpc.go
@@ -49,19 +49,20 @@ const (
 	FeatureSandboxSetuid    Feature = 64
 	FeatureSandboxNamespace Feature = 128
 	FeatureSandboxAndroid   Feature = 256
-	FeatureFault            Feature = 512
-	FeatureLeak             Feature = 1024
-	FeatureNetInjection     Feature = 2048
-	FeatureNetDevices       Feature = 4096
-	FeatureKCSAN            Feature = 8192
-	FeatureDevlinkPCI       Feature = 16384
-	FeatureNicVF            Feature = 32768
-	FeatureUSBEmulation     Feature = 65536
-	FeatureVhciInjection    Feature = 131072
-	FeatureWifiEmulation    Feature = 262144
-	FeatureLRWPANEmulation  Feature = 524288
-	FeatureBinFmtMisc       Feature = 1048576
-	FeatureSwap             Feature = 2097152
+	FeatureSandboxSyd       Feature = 512
+	FeatureFault            Feature = 1024
+	FeatureLeak             Feature = 2048
+	FeatureNetInjection     Feature = 4096
+	FeatureNetDevices       Feature = 8192
+	FeatureKCSAN            Feature = 16384
+	FeatureDevlinkPCI       Feature = 32768
+	FeatureNicVF            Feature = 65536
+	FeatureUSBEmulation     Feature = 131072
+	FeatureVhciInjection    Feature = 262144
+	FeatureWifiEmulation    Feature = 524288
+	FeatureLRWPANEmulation  Feature = 1048576
+	FeatureBinFmtMisc       Feature = 2097152
+	FeatureSwap             Feature = 4194304
 )
 
 var EnumNamesFeature = map[Feature]string{
@@ -74,6 +75,7 @@ var EnumNamesFeature = map[Feature]string{
 	FeatureSandboxSetuid:    "SandboxSetuid",
 	FeatureSandboxNamespace: "SandboxNamespace",
 	FeatureSandboxAndroid:   "SandboxAndroid",
+	FeatureSandboxSyd:       "SandboxSyd",
 	FeatureFault:            "Fault",
 	FeatureLeak:             "Leak",
 	FeatureNetInjection:     "NetInjection",
@@ -99,6 +101,7 @@ var EnumValuesFeature = map[string]Feature{
 	"SandboxSetuid":    FeatureSandboxSetuid,
 	"SandboxNamespace": FeatureSandboxNamespace,
 	"SandboxAndroid":   FeatureSandboxAndroid,
+	"SandboxSyd":       FeatureSandboxSyd,
 	"Fault":            FeatureFault,
 	"Leak":             FeatureLeak,
 	"NetInjection":     FeatureNetInjection,
@@ -328,17 +331,18 @@ const (
 	ExecEnvSandboxSetuid       ExecEnv = 32
 	ExecEnvSandboxNamespace    ExecEnv = 64
 	ExecEnvSandboxAndroid      ExecEnv = 128
-	ExecEnvExtraCover          ExecEnv = 256
-	ExecEnvEnableTun           ExecEnv = 512
-	ExecEnvEnableNetDev        ExecEnv = 1024
-	ExecEnvEnableNetReset      ExecEnv = 2048
-	ExecEnvEnableCgroups       ExecEnv = 4096
-	ExecEnvEnableCloseFds      ExecEnv = 8192
-	ExecEnvEnableDevlinkPCI    ExecEnv = 16384
-	ExecEnvEnableVhciInjection ExecEnv = 32768
-	ExecEnvEnableWifi          ExecEnv = 65536
-	ExecEnvDelayKcovMmap       ExecEnv = 131072
-	ExecEnvEnableNicVF         ExecEnv = 262144
+	ExecEnvSandboxSyd          ExecEnv = 256
+	ExecEnvExtraCover          ExecEnv = 512
+	ExecEnvEnableTun           ExecEnv = 1024
+	ExecEnvEnableNetDev        ExecEnv = 2048
+	ExecEnvEnableNetReset      ExecEnv = 4096
+	ExecEnvEnableCgroups       ExecEnv = 8192
+	ExecEnvEnableCloseFds      ExecEnv = 16384
+	ExecEnvEnableDevlinkPCI    ExecEnv = 32768
+	ExecEnvEnableVhciInjection ExecEnv = 65536
+	ExecEnvEnableWifi          ExecEnv = 131072
+	ExecEnvDelayKcovMmap       ExecEnv = 262144
+	ExecEnvEnableNicVF         ExecEnv = 524288
 )
 
 var EnumNamesExecEnv = map[ExecEnv]string{
@@ -350,6 +354,7 @@ var EnumNamesExecEnv = map[ExecEnv]string{
 	ExecEnvSandboxSetuid:       "SandboxSetuid",
 	ExecEnvSandboxNamespace:    "SandboxNamespace",
 	ExecEnvSandboxAndroid:      "SandboxAndroid",
+	ExecEnvSandboxSyd:          "SandboxSyd",
 	ExecEnvExtraCover:          "ExtraCover",
 	ExecEnvEnableTun:           "EnableTun",
 	ExecEnvEnableNetDev:        "EnableNetDev",
@@ -372,6 +377,7 @@ var EnumValuesExecEnv = map[string]ExecEnv{
 	"SandboxSetuid":       ExecEnvSandboxSetuid,
 	"SandboxNamespace":    ExecEnvSandboxNamespace,
 	"SandboxAndroid":      ExecEnvSandboxAndroid,
+	"SandboxSyd":          ExecEnvSandboxSyd,
 	"ExtraCover":          ExecEnvExtraCover,
 	"EnableTun":           ExecEnvEnableTun,
 	"EnableNetDev":        ExecEnvEnableNetDev,
diff --git a/pkg/flatrpc/flatrpc.h b/pkg/flatrpc/flatrpc.h
index aa386f7dc..914fe086d 100644
--- a/pkg/flatrpc/flatrpc.h
+++ b/pkg/flatrpc/flatrpc.h
@@ -146,25 +146,26 @@ enum class Feature : uint64_t {
   SandboxSetuid = 64ULL,
   SandboxNamespace = 128ULL,
   SandboxAndroid = 256ULL,
-  Fault = 512ULL,
-  Leak = 1024ULL,
-  NetInjection = 2048ULL,
-  NetDevices = 4096ULL,
-  KCSAN = 8192ULL,
-  DevlinkPCI = 16384ULL,
-  NicVF = 32768ULL,
-  USBEmulation = 65536ULL,
-  VhciInjection = 131072ULL,
-  WifiEmulation = 262144ULL,
-  LRWPANEmulation = 524288ULL,
-  BinFmtMisc = 1048576ULL,
-  Swap = 2097152ULL,
+  SandboxSyd = 512ULL,
+  Fault = 1024ULL,
+  Leak = 2048ULL,
+  NetInjection = 4096ULL,
+  NetDevices = 8192ULL,
+  KCSAN = 16384ULL,
+  DevlinkPCI = 32768ULL,
+  NicVF = 65536ULL,
+  USBEmulation = 131072ULL,
+  VhciInjection = 262144ULL,
+  WifiEmulation = 524288ULL,
+  LRWPANEmulation = 1048576ULL,
+  BinFmtMisc = 2097152ULL,
+  Swap = 4194304ULL,
   NONE = 0,
-  ANY = 4194303ULL
+  ANY = 8388607ULL
 };
 FLATBUFFERS_DEFINE_BITMASK_OPERATORS(Feature, uint64_t)
 
-inline const Feature (&EnumValuesFeature())[22] {
+inline const Feature (&EnumValuesFeature())[23] {
   static const Feature values[] = {
     Feature::Coverage,
     Feature::Comparisons,
@@ -175,6 +176,7 @@ inline const Feature (&EnumValuesFeature())[22] {
     Feature::SandboxSetuid,
     Feature::SandboxNamespace,
     Feature::SandboxAndroid,
+    Feature::SandboxSyd,
     Feature::Fault,
     Feature::Leak,
     Feature::NetInjection,
@@ -203,6 +205,7 @@ inline const char *EnumNameFeature(Feature e) {
     case Feature::SandboxSetuid: return "SandboxSetuid";
     case Feature::SandboxNamespace: return "SandboxNamespace";
     case Feature::SandboxAndroid: return "SandboxAndroid";
+    case Feature::SandboxSyd: return "SandboxSyd";
     case Feature::Fault: return "Fault";
     case Feature::Leak: return "Leak";
     case Feature::NetInjection: return "NetInjection";
@@ -566,23 +569,24 @@ enum class ExecEnv : uint64_t {
   SandboxSetuid = 32ULL,
   SandboxNamespace = 64ULL,
   SandboxAndroid = 128ULL,
-  ExtraCover = 256ULL,
-  EnableTun = 512ULL,
-  EnableNetDev = 1024ULL,
-  EnableNetReset = 2048ULL,
-  EnableCgroups = 4096ULL,
-  EnableCloseFds = 8192ULL,
-  EnableDevlinkPCI = 16384ULL,
-  EnableVhciInjection = 32768ULL,
-  EnableWifi = 65536ULL,
-  DelayKcovMmap = 131072ULL,
-  EnableNicVF = 262144ULL,
+  SandboxSyd = 256ULL,
+  ExtraCover = 512ULL,
+  EnableTun = 1024ULL,
+  EnableNetDev = 2048ULL,
+  EnableNetReset = 4096ULL,
+  EnableCgroups = 8192ULL,
+  EnableCloseFds = 16384ULL,
+  EnableDevlinkPCI = 32768ULL,
+  EnableVhciInjection = 65536ULL,
+  EnableWifi = 131072ULL,
+  DelayKcovMmap = 262144ULL,
+  EnableNicVF = 524288ULL,
   NONE = 0,
-  ANY = 524287ULL
+  ANY = 1048575ULL
 };
 FLATBUFFERS_DEFINE_BITMASK_OPERATORS(ExecEnv, uint64_t)
 
-inline const ExecEnv (&EnumValuesExecEnv())[19] {
+inline const ExecEnv (&EnumValuesExecEnv())[20] {
   static const ExecEnv values[] = {
     ExecEnv::Debug,
     ExecEnv::Signal,
@@ -592,6 +596,7 @@ inline const ExecEnv (&EnumValuesExecEnv())[19] {
     ExecEnv::SandboxSetuid,
     ExecEnv::SandboxNamespace,
     ExecEnv::SandboxAndroid,
+    ExecEnv::SandboxSyd,
     ExecEnv::ExtraCover,
     ExecEnv::EnableTun,
     ExecEnv::EnableNetDev,
@@ -617,6 +622,7 @@ inline const char *EnumNameExecEnv(ExecEnv e) {
     case ExecEnv::SandboxSetuid: return "SandboxSetuid";
     case ExecEnv::SandboxNamespace: return "SandboxNamespace";
     case ExecEnv::SandboxAndroid: return "SandboxAndroid";
+    case ExecEnv::SandboxSyd: return "SandboxSyd";
     case ExecEnv::ExtraCover: return "ExtraCover";
     case ExecEnv::EnableTun: return "EnableTun";
     case ExecEnv::EnableNetDev: return "EnableNetDev";
diff --git a/pkg/flatrpc/helpers.go b/pkg/flatrpc/helpers.go
index 5aa5cfe74..1a9ee7a0f 100644
--- a/pkg/flatrpc/helpers.go
+++ b/pkg/flatrpc/helpers.go
@@ -96,8 +96,10 @@ func SandboxToFlags(sandbox string) (ExecEnv, error) {
 		return ExecEnvSandboxNamespace, nil
 	case "android":
 		return ExecEnvSandboxAndroid, nil
+	case "syd":
+		return ExecEnvSandboxSyd, nil
 	default:
-		return 0, fmt.Errorf("sandbox must contain one of none/setuid/namespace/android")
+		return 0, fmt.Errorf("sandbox must contain one of none/setuid/namespace/android/syd")
 	}
 }
 
diff --git a/pkg/fuzzer/queue/queue.go b/pkg/fuzzer/queue/queue.go
index 20509dcb7..5ed11c155 100644
--- a/pkg/fuzzer/queue/queue.go
+++ b/pkg/fuzzer/queue/queue.go
@@ -128,7 +128,8 @@ func (r *Request) Validate() error {
 			return fmt.Errorf("program is not set")
 		}
 		sandboxes := flatrpc.ExecEnvSandboxNone | flatrpc.ExecEnvSandboxSetuid |
-			flatrpc.ExecEnvSandboxNamespace | flatrpc.ExecEnvSandboxAndroid
+			flatrpc.ExecEnvSandboxNamespace | flatrpc.ExecEnvSandboxAndroid |
+			flatrpc.ExecEnvSandboxSyd
 		if r.ExecOpts.EnvFlags&sandboxes == 0 {
 			return fmt.Errorf("no sandboxes set")
 		}
diff --git a/pkg/ifaceprobe/ifaceprobe.go b/pkg/ifaceprobe/ifaceprobe.go
index c5bb63639..1a45369f5 100644
--- a/pkg/ifaceprobe/ifaceprobe.go
+++ b/pkg/ifaceprobe/ifaceprobe.go
@@ -163,8 +163,13 @@ func (pr *prober) submitGlob(glob string) {
 func (pr *prober) onGlobDone(req *queue.Request, res *queue.Result) bool {
 	defer pr.wg.Done()
 	if res.Status != queue.Success {
-		pr.noteError(fmt.Errorf("failed to execute glob: %w (%v)\n%s\n%s",
-			res.Err, res.Status, req.GlobPattern, res.Output))
+		if res.Err == nil {
+			pr.noteError(fmt.Errorf("glob did not succeed (status=%v)\n%s\n%s",
+				res.Status, req.GlobPattern, res.Output))
+		} else {
+			pr.noteError(fmt.Errorf("failed to execute glob: %w (%v)\n%s\n%s",
+				res.Err, res.Status, req.GlobPattern, res.Output))
+		}
 	}
 	files := res.GlobFiles()
 	log.Logf(0, "glob %v expanded to %v files", req.GlobPattern, len(files))
diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go
index cd291233c..9f57b5625 100644
--- a/pkg/instance/instance_test.go
+++ b/pkg/instance/instance_test.go
@@ -34,7 +34,7 @@ func TestExecprogCmd(t *testing.T) {
 	// sets this flag and never relies on the default value.
 	flagCollide := flags.Bool("collide", false, "collide syscalls to provoke data races")
 	flagSignal := flags.Bool("cover", false, "collect feedback signals (coverage)")
-	flagSandbox := flags.String("sandbox", "none", "sandbox for fuzzing (none/setuid/namespace/android)")
+	flagSandbox := flags.String("sandbox", "none", "sandbox for fuzzing (none/setuid/namespace/android/syd)")
 	flagSlowdown := flags.Int("slowdown", 1, "")
 	flagSandboxArg := flags.Int("sandbox_arg", 0, "argument for sandbox runner to adjust it via config")
 	cmdLine := ExecprogCmd(os.Args[0], "/myexecutor", targets.FreeBSD, targets.I386, "vmtype",
diff --git a/pkg/mgrconfig/load.go b/pkg/mgrconfig/load.go
index 38f0d0062..690cacba1 100644
--- a/pkg/mgrconfig/load.go
+++ b/pkg/mgrconfig/load.go
@@ -166,9 +166,9 @@ func Complete(cfg *Config) error {
 		return fmt.Errorf("bad config param procs: '%v', want [1, %v]", cfg.Procs, prog.MaxPids)
 	}
 	switch cfg.Sandbox {
-	case "none", "setuid", "namespace", "android":
+	case "none", "setuid", "namespace", "android", "syd":
 	default:
-		return fmt.Errorf("config param sandbox must contain one of none/setuid/namespace/android")
+		return fmt.Errorf("config param sandbox must contain one of none/setuid/namespace/android/syd")
 	}
 	if err := cfg.checkSSHParams(); err != nil {
 		return err
diff --git a/pkg/vminfo/features.go b/pkg/vminfo/features.go
index 6c4d6ae6d..14e5986f5 100644
--- a/pkg/vminfo/features.go
+++ b/pkg/vminfo/features.go
@@ -52,6 +52,12 @@ func (ctx *checkContext) startFeaturesCheck() {
 			ctx.features <- featureResult{feat, "disabled by user"}
 			continue
 		}
+		// Don't run execution tests for non-configured sandbox features.
+		// This is needed when running under an external sandbox (e.g. Syd).
+		if ctx.isNonConfiguredSandboxFeature(feat) {
+			ctx.features <- featureResult{feat, "not the configured sandbox"}
+			continue
+		}
 		go func() {
 			envFlags, execFlags := ctx.featureToFlags(feat)
 			req := &queue.Request{
@@ -109,7 +115,18 @@ func (ctx *checkContext) finishFeatures(featureInfos []*flatrpc.FeatureInfo) (Fe
 		feat.Reason = strings.TrimSpace(outputReplacer.Replace(feat.Reason))
 		features[res.id] = feat
 	}
-	if feat := features[flatrpc.FeatureSandboxNone]; !feat.Enabled {
+	sandboxFeature := flatrpc.FeatureSandboxNone
+	switch ctx.cfg.Sandbox {
+	case flatrpc.ExecEnvSandboxSetuid:
+		sandboxFeature = flatrpc.FeatureSandboxSetuid
+	case flatrpc.ExecEnvSandboxNamespace:
+		sandboxFeature = flatrpc.FeatureSandboxNamespace
+	case flatrpc.ExecEnvSandboxAndroid:
+		sandboxFeature = flatrpc.FeatureSandboxAndroid
+	case flatrpc.ExecEnvSandboxSyd:
+		sandboxFeature = flatrpc.FeatureSandboxSyd
+	}
+	if feat := features[sandboxFeature]; !feat.Enabled {
 		return features, fmt.Errorf("execution of simple program fails: %v", feat.Reason)
 	}
 	if feat := features[flatrpc.FeatureCoverage]; ctx.cfg.Cover && !feat.Enabled {
@@ -152,6 +169,9 @@ func (ctx *checkContext) featureToFlags(feat flatrpc.Feature) (flatrpc.ExecEnv,
 	case flatrpc.FeatureSandboxAndroid:
 		envFlags &= ^ctx.cfg.Sandbox
 		envFlags |= flatrpc.ExecEnvSandboxAndroid
+	case flatrpc.FeatureSandboxSyd:
+		envFlags &= ^ctx.cfg.Sandbox
+		envFlags |= flatrpc.ExecEnvSandboxSyd
 	case flatrpc.FeatureFault:
 	case flatrpc.FeatureLeak:
 	case flatrpc.FeatureNetInjection:
@@ -177,6 +197,23 @@ func (ctx *checkContext) featureToFlags(feat flatrpc.Feature) (flatrpc.ExecEnv,
 	return envFlags, execFlags
 }
 
+// isNonConfiguredSandboxFeature returns true,
+// if feat is a sandbox feature that doesn't match the configured sandbox.
+func (ctx *checkContext) isNonConfiguredSandboxFeature(feat flatrpc.Feature) bool {
+	sandboxFeatures := map[flatrpc.Feature]flatrpc.ExecEnv{
+		flatrpc.FeatureSandboxNone:      flatrpc.ExecEnvSandboxNone,
+		flatrpc.FeatureSandboxSetuid:    flatrpc.ExecEnvSandboxSetuid,
+		flatrpc.FeatureSandboxNamespace: flatrpc.ExecEnvSandboxNamespace,
+		flatrpc.FeatureSandboxAndroid:   flatrpc.ExecEnvSandboxAndroid,
+		flatrpc.FeatureSandboxSyd:       flatrpc.ExecEnvSandboxSyd,
+	}
+	env, isSandbox := sandboxFeatures[feat]
+	if !isSandbox {
+		return false
+	}
+	return env != ctx.cfg.Sandbox
+}
+
 // featureSucceeded checks if execution of a simple program with feature-specific flags succeed.
 // This generally checks that just all syscalls were executed and succeed,
 // for coverage features we also check that we got actual coverage.
diff --git a/pkg/vminfo/syscalls.go b/pkg/vminfo/syscalls.go
index f4a6919a5..6466b0f02 100644
--- a/pkg/vminfo/syscalls.go
+++ b/pkg/vminfo/syscalls.go
@@ -119,6 +119,9 @@ func (ctx *checkContext) do(fileInfos []*flatrpc.FileInfo, featureInfos []*flatr
 			// Don't return an error on context cancellation.
 			return nil, nil, nil, nil
 		} else if res.Status != queue.Success {
+			if res.Err == nil {
+				return nil, nil, nil, nil
+			}
 			return nil, nil, nil, fmt.Errorf("failed to execute glob: %w (%v)\n%s\n%s",
 				res.Err, res.Status, req.GlobPattern, res.Output)
 		}
diff --git a/tools/syz-execprog/execprog.go b/tools/syz-execprog/execprog.go
index 5b3244c28..420e28ad3 100644
--- a/tools/syz-execprog/execprog.go
+++ b/tools/syz-execprog/execprog.go
@@ -48,7 +48,7 @@ var (
 	flagExecutor   = flag.String("executor", "./syz-executor", "path to executor binary")
 	flagThreaded   = flag.Bool("threaded", true, "use threaded mode in executor")
 	flagSignal     = flag.Bool("cover", false, "collect feedback signals (coverage)")
-	flagSandbox    = flag.String("sandbox", "none", "sandbox for fuzzing (none/setuid/namespace/android)")
+	flagSandbox    = flag.String("sandbox", "none", "sandbox for fuzzing (none/setuid/namespace/android/syd)")
 	flagSandboxArg = flag.Int("sandbox_arg", 0, "argument for sandbox runner to adjust it via config")
 	flagDebug      = flag.Bool("debug", false, "debug output from executor")
 	flagSlowdown   = flag.Int("slowdown", 1, "execution slowdown caused by emulation/instrumentation")
-- 
2.53.0

