aboutsummaryrefslogtreecommitdiffstats
path: root/executor
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2026-01-27 12:31:49 +0000
committerAleksandr Nogikh <nogikh@google.com>2026-02-17 14:55:28 +0000
commit72e0f1b67bdd3f89cf51e89a3c17dd4a7cb575f1 (patch)
treec7e29b0a56ad5f81ed82f2bf47531c0d0b52812d /executor
parent21b4b9b6789f7b255eb115d3757e82652bb33eaa (diff)
all: add a DumpMemory feature
On Linux, verify that makedumpfile and the second kernel are present, then set up a kernel to be used on panic.
Diffstat (limited to 'executor')
-rw-r--r--executor/executor.cc1
-rw-r--r--executor/executor_common.h29
-rw-r--r--executor/executor_linux.h45
-rw-r--r--executor/test.h46
4 files changed, 121 insertions, 0 deletions
diff --git a/executor/executor.cc b/executor/executor.cc
index d6bba7aa7..3ba21e846 100644
--- a/executor/executor.cc
+++ b/executor/executor.cc
@@ -298,6 +298,7 @@ static bool in_execute_one = false;
#define SYZ_EXECUTOR 1
#include "common.h"
+#include "executor_common.h"
const size_t kMaxInput = 4 << 20; // keep in sync with prog.ExecBufferSize
const size_t kMaxCommands = 1000; // prog package knows about this constant (prog.execMaxCommands)
diff --git a/executor/executor_common.h b/executor/executor_common.h
new file mode 100644
index 000000000..90f731dbf
--- /dev/null
+++ b/executor/executor_common.h
@@ -0,0 +1,29 @@
+// Copyright 2025 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.
+
+#ifndef EXECUTOR_COMMON_H
+#define EXECUTOR_COMMON_H
+
+#include <stdio.h>
+#include <string.h>
+
+static void get_last_opt(const char* cmdline, const char* key, char* out, size_t out_len)
+{
+ char key_eq[128];
+ snprintf(key_eq, sizeof(key_eq), "%s=", key);
+ const char* val = NULL;
+ for (const char* p = cmdline; (p = strstr(p, key_eq)); p += strlen(key_eq)) {
+ if (p == cmdline || p[-1] == ' ' || p[-1] == '\t' || p[-1] == '\n')
+ val = p + strlen(key_eq);
+ }
+
+ if (val) {
+ size_t len = strcspn(val, " \t\n");
+ if (len >= out_len)
+ len = out_len - 1;
+ memcpy(out, val, len);
+ out[len] = 0;
+ }
+}
+
+#endif // EXECUTOR_COMMON_H
diff --git a/executor/executor_linux.h b/executor/executor_linux.h
index bbc5dfff0..9a5d22423 100644
--- a/executor/executor_linux.h
+++ b/executor/executor_linux.h
@@ -366,6 +366,50 @@ static const char* setup_kcov_reset_ioctl()
return error;
}
+static const char* setup_kdump()
+{
+ if (access("/boot/bzImageKexec", F_OK) != 0)
+ return "/boot/bzImageKexec is missing";
+ if (access("/usr/sbin/makedumpfile", F_OK) != 0)
+ return "/usr/sbin/makedumpfile is missing";
+ char cmdline[4096];
+ int fd = open("/proc/cmdline", O_RDONLY);
+ if (fd < 0)
+ return "failed to open /proc/cmdline";
+ ssize_t n = read(fd, cmdline, sizeof(cmdline) - 1);
+ close(fd);
+ if (n <= 0)
+ return "failed to read /proc/cmdline";
+ cmdline[n] = 0;
+ if (strstr(cmdline, "crashkernel=") == NULL)
+ return "crashkernel= is not present in /proc/cmdline";
+
+ // Current default values
+ char root[128] = "/dev/sda1";
+ char console[128] = "ttyS0";
+ get_last_opt(cmdline, "root", root, sizeof(root));
+ get_last_opt(cmdline, "console", console, sizeof(console));
+
+ char cmd[1024];
+ snprintf(cmd, sizeof(cmd),
+ "kexec -p /boot/bzImageKexec --append=\"earlyprintk=serial net.ifnames=0 ima_policy=tcb no_hash_pointers root=%s console=%s vsyscall=native watchdog_thresh=55 irqpoll nr_cpus=1 reset_devices\"",
+ root, console);
+
+ if (system(cmd) != 0)
+ return "kexec failed";
+ int s_fd = open("/sys/kernel/kexec_crash_loaded", O_RDONLY);
+ if (s_fd >= 0) {
+ char loaded_status[1];
+ ssize_t sn = read(s_fd, loaded_status, sizeof(loaded_status));
+ close(s_fd);
+ if (sn != 1)
+ return "failed to read /sys/kernel/kexec_crash_loaded";
+ if (loaded_status[0] != '1')
+ return "/sys/kernel/kexec_crash_loaded is not 1";
+ }
+ return NULL;
+}
+
#define SYZ_HAVE_FEATURES 1
static feature_t features[] = {
{rpc::Feature::DelayKcovMmap, setup_delay_kcov},
@@ -379,4 +423,5 @@ static feature_t features[] = {
{rpc::Feature::Swap, setup_swap},
{rpc::Feature::NicVF, setup_nicvf},
{rpc::Feature::DevlinkPCI, setup_devlink_pci},
+ {rpc::Feature::MemoryDump, setup_kdump},
};
diff --git a/executor/test.h b/executor/test.h
index 8997ef52f..eb97aa5b9 100644
--- a/executor/test.h
+++ b/executor/test.h
@@ -10,6 +10,8 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "executor_common.h"
+
static int test_copyin()
{
static uint16 buf[3];
@@ -365,6 +367,49 @@ static int test_glob()
return 0;
}
+static int test_get_last_opt()
+{
+ struct {
+ const char* cmdline;
+ const char* key;
+ const char* want;
+ } tests[] = {
+ {"key=val", "key", "val"},
+ {"key=val ", "key", "val"},
+ {" key=val", "key", "val"},
+ {" key=val ", "key", "val"},
+ {"key=val1 key=val2", "key", "val2"},
+ {"key=val1 key=val2 ", "key", "val2"},
+ {"key=val1 key=val2 key=val3", "key", "val3"},
+ {"other=val key=val", "key", "val"},
+ {"key=val other=val", "key", "val"},
+ {"foo=bar", "key", ""},
+ {"nokey=val", "key", ""},
+ {"key", "key", ""},
+ {"full match", "full", ""}, // "full" != "full="
+ };
+
+ char buf[128];
+ for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) {
+ memset(buf, 0, sizeof(buf));
+ get_last_opt(tests[i].cmdline, tests[i].key, buf, sizeof(buf));
+ if (strcmp(buf, tests[i].want) != 0) {
+ printf("test_get_last_opt failed: cmdline='%s' key='%s' want='%s' got='%s'\n",
+ tests[i].cmdline, tests[i].key, tests[i].want, buf);
+ return 1;
+ }
+ }
+
+ char small_buf[4];
+ get_last_opt("key=value", "key", small_buf, sizeof(small_buf));
+ if (strcmp(small_buf, "val") != 0) {
+ printf("test_get_last_opt truncation failed: want='val' got='%s'\n", small_buf);
+ return 1;
+ }
+
+ return 0;
+}
+
static struct {
const char* name;
int (*f)();
@@ -380,6 +425,7 @@ static struct {
#endif
{"test_cover_filter", test_cover_filter},
{"test_glob", test_glob},
+ {"test_get_last_opt", test_get_last_opt},
};
static int run_tests(const char* test)