mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-30 23:00:29 +00:00
5662 lines
153 KiB
Go
5662 lines
153 KiB
Go
// AUTOGENERATED FROM executor/common.h
|
|
|
|
package csource
|
|
|
|
var commonHeader = `
|
|
|
|
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#if GOOS_freebsd || GOOS_test && HOSTGOOS_freebsd
|
|
#include <sys/endian.h>
|
|
#else
|
|
#include <endian.h>
|
|
#endif
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if SYZ_TRACE
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR && !GOOS_linux
|
|
#include <unistd.h>
|
|
NORETURN void doexit(int status)
|
|
{
|
|
_exit(status);
|
|
for (;;) {
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_PROCS || SYZ_REPEAT && SYZ_ENABLE_CGROUPS || \
|
|
SYZ_ENABLE_NETDEV || __NR_syz_mount_image || __NR_syz_read_part_table || \
|
|
__NR_syz_usb_connect || (GOOS_freebsd || GOOS_openbsd || GOOS_netbsd) && SYZ_TUN_ENABLE
|
|
unsigned long long procid;
|
|
#endif
|
|
|
|
#if !GOOS_fuchsia && !GOOS_windows
|
|
#if SYZ_EXECUTOR || SYZ_HANDLE_SEGV
|
|
#include <setjmp.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
|
|
#if GOOS_linux
|
|
#include <sys/syscall.h>
|
|
#endif
|
|
|
|
static __thread int skip_segv;
|
|
static __thread jmp_buf segv_env;
|
|
|
|
#if GOOS_akaros
|
|
#include <parlib/parlib.h>
|
|
static void recover(void)
|
|
{
|
|
_longjmp(segv_env, 1);
|
|
}
|
|
#endif
|
|
|
|
static void segv_handler(int sig, siginfo_t* info, void* ctx)
|
|
{
|
|
uintptr_t addr = (uintptr_t)info->si_addr;
|
|
const uintptr_t prog_start = 1 << 20;
|
|
const uintptr_t prog_end = 100 << 20;
|
|
if (__atomic_load_n(&skip_segv, __ATOMIC_RELAXED) && (addr < prog_start || addr > prog_end)) {
|
|
debug("SIGSEGV on %p, skipping\n", (void*)addr);
|
|
#if GOOS_akaros
|
|
struct user_context* uctx = (struct user_context*)ctx;
|
|
uctx->tf.hw_tf.tf_rip = (long)(void*)recover;
|
|
return;
|
|
#else
|
|
_longjmp(segv_env, 1);
|
|
#endif
|
|
}
|
|
debug("SIGSEGV on %p, exiting\n", (void*)addr);
|
|
doexit(sig);
|
|
}
|
|
|
|
static void install_segv_handler(void)
|
|
{
|
|
struct sigaction sa;
|
|
#if GOOS_linux
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_handler = SIG_IGN;
|
|
syscall(SYS_rt_sigaction, 0x20, &sa, NULL, 8);
|
|
syscall(SYS_rt_sigaction, 0x21, &sa, NULL, 8);
|
|
#endif
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sa_sigaction = segv_handler;
|
|
sa.sa_flags = SA_NODEFER | SA_SIGINFO;
|
|
sigaction(SIGSEGV, &sa, NULL);
|
|
sigaction(SIGBUS, &sa, NULL);
|
|
}
|
|
|
|
#define NONFAILING(...) \
|
|
{ \
|
|
__atomic_fetch_add(&skip_segv, 1, __ATOMIC_SEQ_CST); \
|
|
if (_setjmp(segv_env) == 0) { \
|
|
__VA_ARGS__; \
|
|
} \
|
|
__atomic_fetch_sub(&skip_segv, 1, __ATOMIC_SEQ_CST); \
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !GOOS_linux
|
|
#if (SYZ_EXECUTOR || SYZ_REPEAT) && SYZ_EXECUTOR_USES_FORK_SERVER
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
static void kill_and_wait(int pid, int* status)
|
|
{
|
|
kill(pid, SIGKILL);
|
|
while (waitpid(-1, status, 0) != pid) {
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !GOOS_windows
|
|
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER || \
|
|
__NR_syz_usb_connect
|
|
static void sleep_ms(uint64 ms)
|
|
{
|
|
usleep(ms * 1000);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER || \
|
|
SYZ_ENABLE_LEAK
|
|
#include <time.h>
|
|
|
|
static uint64 current_time_ms(void)
|
|
{
|
|
struct timespec ts;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts))
|
|
fail("clock_gettime failed");
|
|
return (uint64)ts.tv_sec * 1000 + (uint64)ts.tv_nsec / 1000000;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP || SYZ_USE_TMP_DIR
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
static void use_temporary_dir(void)
|
|
{
|
|
#if SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
|
|
char tmpdir_template[] = "/data/data/syzkaller/syzkaller.XXXXXX";
|
|
#elif GOOS_fuchsia
|
|
char tmpdir_template[] = "/tmp/syzkaller.XXXXXX";
|
|
#else
|
|
char tmpdir_template[] = "./syzkaller.XXXXXX";
|
|
#endif
|
|
char* tmpdir = mkdtemp(tmpdir_template);
|
|
if (!tmpdir)
|
|
fail("failed to mkdtemp");
|
|
if (chmod(tmpdir, 0777))
|
|
fail("failed to chmod");
|
|
if (chdir(tmpdir))
|
|
fail("failed to chdir");
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if GOOS_akaros || GOOS_netbsd || GOOS_freebsd || GOOS_openbsd || GOOS_test
|
|
#if SYZ_EXECUTOR || SYZ_EXECUTOR_USES_FORK_SERVER && SYZ_REPEAT && SYZ_USE_TMP_DIR
|
|
#include <dirent.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static void remove_dir(const char* dir)
|
|
{
|
|
DIR* dp;
|
|
struct dirent* ep;
|
|
dp = opendir(dir);
|
|
if (dp == NULL)
|
|
exitf("opendir(%s) failed", dir);
|
|
while ((ep = readdir(dp))) {
|
|
if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
|
|
continue;
|
|
char filename[FILENAME_MAX];
|
|
snprintf(filename, sizeof(filename), "%s/%s", dir, ep->d_name);
|
|
struct stat st;
|
|
if (lstat(filename, &st))
|
|
exitf("lstat(%s) failed", filename);
|
|
if (S_ISDIR(st.st_mode)) {
|
|
remove_dir(filename);
|
|
continue;
|
|
}
|
|
if (unlink(filename))
|
|
exitf("unlink(%s) failed", filename);
|
|
}
|
|
closedir(dp);
|
|
if (rmdir(dir))
|
|
exitf("rmdir(%s) failed", dir);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !GOOS_linux
|
|
#if SYZ_EXECUTOR
|
|
static int inject_fault(int nth)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR
|
|
static int fault_injected(int fail_fd)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !GOOS_windows
|
|
#if SYZ_EXECUTOR || SYZ_THREADED
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
|
|
static void thread_start(void* (*fn)(void*), void* arg)
|
|
{
|
|
pthread_t th;
|
|
pthread_attr_t attr;
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setstacksize(&attr, 128 << 10);
|
|
int i;
|
|
for (i = 0; i < 100; i++) {
|
|
if (pthread_create(&th, &attr, fn, arg) == 0) {
|
|
pthread_attr_destroy(&attr);
|
|
return;
|
|
}
|
|
if (errno == EAGAIN) {
|
|
usleep(50);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
exitf("pthread_create failed");
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
|
|
#if GOOS_freebsd || GOOS_netbsd || GOOS_openbsd || GOOS_akaros || GOOS_test
|
|
#if SYZ_EXECUTOR || SYZ_THREADED
|
|
|
|
#include <pthread.h>
|
|
#include <time.h>
|
|
|
|
typedef struct {
|
|
pthread_mutex_t mu;
|
|
pthread_cond_t cv;
|
|
int state;
|
|
} event_t;
|
|
|
|
static void event_init(event_t* ev)
|
|
{
|
|
if (pthread_mutex_init(&ev->mu, 0))
|
|
exitf("pthread_mutex_init failed");
|
|
if (pthread_cond_init(&ev->cv, 0))
|
|
exitf("pthread_cond_init failed");
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_reset(event_t* ev)
|
|
{
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_set(event_t* ev)
|
|
{
|
|
pthread_mutex_lock(&ev->mu);
|
|
if (ev->state)
|
|
fail("event already set");
|
|
ev->state = 1;
|
|
pthread_mutex_unlock(&ev->mu);
|
|
pthread_cond_broadcast(&ev->cv);
|
|
}
|
|
|
|
static void event_wait(event_t* ev)
|
|
{
|
|
pthread_mutex_lock(&ev->mu);
|
|
while (!ev->state)
|
|
pthread_cond_wait(&ev->cv, &ev->mu);
|
|
pthread_mutex_unlock(&ev->mu);
|
|
}
|
|
|
|
static int event_isset(event_t* ev)
|
|
{
|
|
pthread_mutex_lock(&ev->mu);
|
|
int res = ev->state;
|
|
pthread_mutex_unlock(&ev->mu);
|
|
return res;
|
|
}
|
|
|
|
static int event_timedwait(event_t* ev, uint64 timeout)
|
|
{
|
|
uint64 start = current_time_ms();
|
|
uint64 now = start;
|
|
pthread_mutex_lock(&ev->mu);
|
|
for (;;) {
|
|
if (ev->state)
|
|
break;
|
|
uint64 remain = timeout - (now - start);
|
|
struct timespec ts;
|
|
ts.tv_sec = remain / 1000;
|
|
ts.tv_nsec = (remain % 1000) * 1000 * 1000;
|
|
pthread_cond_timedwait(&ev->cv, &ev->mu, &ts);
|
|
now = current_time_ms();
|
|
if (now - start > timeout)
|
|
break;
|
|
}
|
|
int res = ev->state;
|
|
pthread_mutex_unlock(&ev->mu);
|
|
return res;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_USE_BITMASKS
|
|
#define BITMASK(bf_off, bf_len) (((1ull << (bf_len)) - 1) << (bf_off))
|
|
#define STORE_BY_BITMASK(type, htobe, addr, val, bf_off, bf_len) \
|
|
*(type*)(addr) = htobe((htobe(*(type*)(addr)) & ~BITMASK((bf_off), (bf_len))) | \
|
|
(((type)(val) << (bf_off)) & BITMASK((bf_off), (bf_len))))
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_USE_CHECKSUMS
|
|
struct csum_inet {
|
|
uint32 acc;
|
|
};
|
|
|
|
static void csum_inet_init(struct csum_inet* csum)
|
|
{
|
|
csum->acc = 0;
|
|
}
|
|
|
|
static void csum_inet_update(struct csum_inet* csum, const uint8* data, size_t length)
|
|
{
|
|
if (length == 0)
|
|
return;
|
|
|
|
size_t i;
|
|
for (i = 0; i < length - 1; i += 2)
|
|
csum->acc += *(uint16*)&data[i];
|
|
|
|
if (length & 1)
|
|
csum->acc += (uint16)data[length - 1];
|
|
|
|
while (csum->acc > 0xffff)
|
|
csum->acc = (csum->acc & 0xffff) + (csum->acc >> 16);
|
|
}
|
|
|
|
static uint16 csum_inet_digest(struct csum_inet* csum)
|
|
{
|
|
return ~csum->acc;
|
|
}
|
|
#endif
|
|
|
|
#if GOOS_akaros
|
|
|
|
#include <ros/syscall.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE
|
|
static void loop();
|
|
static int do_sandbox_none(void)
|
|
{
|
|
loop();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT
|
|
static void execute_one();
|
|
const char* program_name;
|
|
|
|
void child()
|
|
{
|
|
#if SYZ_EXECUTOR || SYZ_HANDLE_SEGV
|
|
install_segv_handler();
|
|
#endif
|
|
#if SYZ_EXECUTOR
|
|
receive_execute();
|
|
close(kInPipeFd);
|
|
#endif
|
|
execute_one();
|
|
doexit(0);
|
|
}
|
|
#endif
|
|
|
|
#elif GOOS_freebsd || GOOS_netbsd || GOOS_openbsd
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <pwd.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <sys/syscall.h>
|
|
|
|
#if GOOS_openbsd
|
|
|
|
#define __syscall syscall
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_open_pts
|
|
|
|
#include <termios.h>
|
|
#include <util.h>
|
|
|
|
static uintptr_t syz_open_pts(void)
|
|
{
|
|
int master, slave;
|
|
|
|
if (openpty(&master, &slave, NULL, NULL, NULL) == -1)
|
|
return -1;
|
|
if (dup2(master, master + 100) != -1)
|
|
close(master);
|
|
return slave;
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#if GOOS_freebsd || GOOS_openbsd || GOOS_netbsd
|
|
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE
|
|
|
|
#include <fcntl.h>
|
|
#include <net/if_tun.h>
|
|
#include <sys/types.h>
|
|
|
|
static int tunfd = -1;
|
|
#define SYZ_TUN_MAX_PACKET_SIZE 1000
|
|
|
|
#if GOOS_netbsd
|
|
#define MAX_TUN 64
|
|
|
|
#else
|
|
#define MAX_TUN 4
|
|
#endif
|
|
#define TUN_IFACE "tap%d"
|
|
#define TUN_DEVICE "/dev/tap%d"
|
|
|
|
#define LOCAL_MAC "aa:aa:aa:aa:aa:aa"
|
|
#define REMOTE_MAC "aa:aa:aa:aa:aa:bb"
|
|
#define LOCAL_IPV4 "172.20.%d.170"
|
|
#define REMOTE_IPV4 "172.20.%d.187"
|
|
#define LOCAL_IPV6 "fe80::%02hxaa"
|
|
#define REMOTE_IPV6 "fe80::%02hxbb"
|
|
|
|
static void vsnprintf_check(char* str, size_t size, const char* format, va_list args)
|
|
{
|
|
int rv;
|
|
|
|
rv = vsnprintf(str, size, format, args);
|
|
if (rv < 0)
|
|
fail("vsnprintf failed");
|
|
if ((size_t)rv >= size)
|
|
fail("vsnprintf: string '%s...' doesn't fit into buffer", str);
|
|
}
|
|
|
|
static void snprintf_check(char* str, size_t size, const char* format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
vsnprintf_check(str, size, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
#define COMMAND_MAX_LEN 128
|
|
#define PATH_PREFIX "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin "
|
|
#define PATH_PREFIX_LEN (sizeof(PATH_PREFIX) - 1)
|
|
|
|
static void execute_command(bool panic, const char* format, ...)
|
|
{
|
|
va_list args;
|
|
char command[PATH_PREFIX_LEN + COMMAND_MAX_LEN];
|
|
int rv;
|
|
|
|
va_start(args, format);
|
|
memcpy(command, PATH_PREFIX, PATH_PREFIX_LEN);
|
|
vsnprintf_check(command + PATH_PREFIX_LEN, COMMAND_MAX_LEN, format, args);
|
|
va_end(args);
|
|
rv = system(command);
|
|
if (rv) {
|
|
if (panic)
|
|
fail("command '%s' failed: %d", &command[0], rv);
|
|
debug("command '%s': %d\n", &command[0], rv);
|
|
}
|
|
}
|
|
|
|
static void initialize_tun(int tun_id)
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_tun)
|
|
return;
|
|
#endif
|
|
|
|
if (tun_id < 0 || tun_id >= MAX_TUN) {
|
|
fail("tun_id out of range %d\n", tun_id);
|
|
}
|
|
|
|
char tun_device[sizeof(TUN_DEVICE)];
|
|
snprintf_check(tun_device, sizeof(tun_device), TUN_DEVICE, tun_id);
|
|
|
|
char tun_iface[sizeof(TUN_IFACE)];
|
|
snprintf_check(tun_iface, sizeof(tun_iface), TUN_IFACE, tun_id);
|
|
|
|
#if GOOS_netbsd
|
|
execute_command(0, "ifconfig %s destroy", tun_iface);
|
|
execute_command(0, "ifconfig %s create", tun_iface);
|
|
#else
|
|
execute_command(0, "ifconfig %s destroy", tun_device);
|
|
#endif
|
|
|
|
tunfd = open(tun_device, O_RDWR | O_NONBLOCK);
|
|
#if GOOS_freebsd
|
|
if ((tunfd < 0) && (errno == ENOENT)) {
|
|
execute_command(0, "kldload -q if_tap");
|
|
tunfd = open(tun_device, O_RDWR | O_NONBLOCK);
|
|
}
|
|
#endif
|
|
if (tunfd == -1) {
|
|
#if SYZ_EXECUTOR
|
|
fail("tun: can't open %s\n", tun_device);
|
|
#else
|
|
printf("tun: can't open %s: errno=%d\n", tun_device, errno);
|
|
return;
|
|
#endif
|
|
}
|
|
const int kTunFd = 240;
|
|
if (dup2(tunfd, kTunFd) < 0)
|
|
fail("dup2(tunfd, kTunFd) failed");
|
|
close(tunfd);
|
|
tunfd = kTunFd;
|
|
|
|
char local_mac[sizeof(LOCAL_MAC)];
|
|
snprintf_check(local_mac, sizeof(local_mac), LOCAL_MAC);
|
|
#if GOOS_openbsd
|
|
execute_command(1, "ifconfig %s lladdr %s", tun_iface, local_mac);
|
|
#elif GOOS_netbsd
|
|
execute_command(1, "ifconfig %s link %s", tun_iface, local_mac);
|
|
#else
|
|
execute_command(1, "ifconfig %s ether %s", tun_iface, local_mac);
|
|
#endif
|
|
char local_ipv4[sizeof(LOCAL_IPV4)];
|
|
snprintf_check(local_ipv4, sizeof(local_ipv4), LOCAL_IPV4, tun_id);
|
|
execute_command(1, "ifconfig %s inet %s netmask 255.255.255.0", tun_iface, local_ipv4);
|
|
char remote_mac[sizeof(REMOTE_MAC)];
|
|
char remote_ipv4[sizeof(REMOTE_IPV4)];
|
|
snprintf_check(remote_mac, sizeof(remote_mac), REMOTE_MAC);
|
|
snprintf_check(remote_ipv4, sizeof(remote_ipv4), REMOTE_IPV4, tun_id);
|
|
execute_command(0, "arp -s %s %s", remote_ipv4, remote_mac);
|
|
char local_ipv6[sizeof(LOCAL_IPV6)];
|
|
snprintf_check(local_ipv6, sizeof(local_ipv6), LOCAL_IPV6, tun_id);
|
|
execute_command(1, "ifconfig %s inet6 %s", tun_iface, local_ipv6);
|
|
char remote_ipv6[sizeof(REMOTE_IPV6)];
|
|
snprintf_check(remote_ipv6, sizeof(remote_ipv6), REMOTE_IPV6, tun_id);
|
|
execute_command(0, "ndp -s %s%%%s %s", remote_ipv6, tun_iface, remote_mac);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_emit_ethernet && SYZ_TUN_ENABLE
|
|
#include <stdbool.h>
|
|
#include <sys/uio.h>
|
|
|
|
static long syz_emit_ethernet(volatile long a0, volatile long a1)
|
|
{
|
|
if (tunfd < 0)
|
|
return (uintptr_t)-1;
|
|
|
|
size_t length = a0;
|
|
const char* data = (char*)a1;
|
|
debug_dump_data(data, length);
|
|
|
|
return write(tunfd, data, length);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE && (__NR_syz_extract_tcp_res || SYZ_REPEAT)
|
|
#include <errno.h>
|
|
|
|
static int read_tun(char* data, int size)
|
|
{
|
|
if (tunfd < 0)
|
|
return -1;
|
|
|
|
int rv = read(tunfd, data, size);
|
|
if (rv < 0) {
|
|
if (errno == EAGAIN)
|
|
return -1;
|
|
fail("tun: read failed with %d", rv);
|
|
}
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_extract_tcp_res && SYZ_TUN_ENABLE
|
|
|
|
struct tcp_resources {
|
|
uint32 seq;
|
|
uint32 ack;
|
|
};
|
|
|
|
#if GOOS_freebsd
|
|
#include <net/ethernet.h>
|
|
#else
|
|
#include <net/ethertypes.h>
|
|
#endif
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip6.h>
|
|
#include <netinet/tcp.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
static long syz_extract_tcp_res(volatile long a0, volatile long a1, volatile long a2)
|
|
{
|
|
|
|
if (tunfd < 0)
|
|
return (uintptr_t)-1;
|
|
|
|
char data[SYZ_TUN_MAX_PACKET_SIZE];
|
|
int rv = read_tun(&data[0], sizeof(data));
|
|
if (rv == -1)
|
|
return (uintptr_t)-1;
|
|
size_t length = rv;
|
|
debug_dump_data(data, length);
|
|
|
|
struct tcphdr* tcphdr;
|
|
|
|
if (length < sizeof(struct ether_header))
|
|
return (uintptr_t)-1;
|
|
struct ether_header* ethhdr = (struct ether_header*)&data[0];
|
|
|
|
if (ethhdr->ether_type == htons(ETHERTYPE_IP)) {
|
|
if (length < sizeof(struct ether_header) + sizeof(struct ip))
|
|
return (uintptr_t)-1;
|
|
struct ip* iphdr = (struct ip*)&data[sizeof(struct ether_header)];
|
|
if (iphdr->ip_p != IPPROTO_TCP)
|
|
return (uintptr_t)-1;
|
|
if (length < sizeof(struct ether_header) + iphdr->ip_hl * 4 + sizeof(struct tcphdr))
|
|
return (uintptr_t)-1;
|
|
tcphdr = (struct tcphdr*)&data[sizeof(struct ether_header) + iphdr->ip_hl * 4];
|
|
} else {
|
|
if (length < sizeof(struct ether_header) + sizeof(struct ip6_hdr))
|
|
return (uintptr_t)-1;
|
|
struct ip6_hdr* ipv6hdr = (struct ip6_hdr*)&data[sizeof(struct ether_header)];
|
|
if (ipv6hdr->ip6_nxt != IPPROTO_TCP)
|
|
return (uintptr_t)-1;
|
|
if (length < sizeof(struct ether_header) + sizeof(struct ip6_hdr) + sizeof(struct tcphdr))
|
|
return (uintptr_t)-1;
|
|
tcphdr = (struct tcphdr*)&data[sizeof(struct ether_header) + sizeof(struct ip6_hdr)];
|
|
}
|
|
|
|
struct tcp_resources* res = (struct tcp_resources*)a0;
|
|
NONFAILING(res->seq = htonl((ntohl(tcphdr->th_seq) + (uint32)a1)));
|
|
NONFAILING(res->ack = htonl((ntohl(tcphdr->th_ack) + (uint32)a2)));
|
|
|
|
debug("extracted seq: %08x\n", res->seq);
|
|
debug("extracted ack: %08x\n", res->ack);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NONE
|
|
|
|
#include <sys/resource.h>
|
|
#include <unistd.h>
|
|
|
|
static void sandbox_common()
|
|
{
|
|
if (setsid() == -1)
|
|
fail("setsid failed");
|
|
struct rlimit rlim;
|
|
#ifdef GOOS_freebsd
|
|
rlim.rlim_cur = rlim.rlim_max = 128 << 20;
|
|
setrlimit(RLIMIT_AS, &rlim);
|
|
#endif
|
|
rlim.rlim_cur = rlim.rlim_max = 8 << 20;
|
|
setrlimit(RLIMIT_MEMLOCK, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 1 << 20;
|
|
setrlimit(RLIMIT_FSIZE, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 1 << 20;
|
|
setrlimit(RLIMIT_STACK, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 0;
|
|
setrlimit(RLIMIT_CORE, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 256;
|
|
setrlimit(RLIMIT_NOFILE, &rlim);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE
|
|
|
|
static void loop();
|
|
|
|
static int do_sandbox_none(void)
|
|
{
|
|
sandbox_common();
|
|
#if (GOOS_freebsd || GOOS_openbsd || GOOS_netbsd) && (SYZ_EXECUTOR || SYZ_TUN_ENABLE)
|
|
initialize_tun(procid);
|
|
#endif
|
|
loop();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_SETUID
|
|
|
|
#include <sys/resource.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
static void loop();
|
|
|
|
static int wait_for_loop(int pid)
|
|
{
|
|
if (pid < 0)
|
|
fail("sandbox fork failed");
|
|
debug("spawned loop pid %d\n", pid);
|
|
int status = 0;
|
|
while (waitpid(-1, &status, WUNTRACED) != pid) {
|
|
}
|
|
return WEXITSTATUS(status);
|
|
}
|
|
|
|
#define SYZ_HAVE_SANDBOX_SETUID 1
|
|
static int do_sandbox_setuid(void)
|
|
{
|
|
int pid = fork();
|
|
if (pid != 0)
|
|
return wait_for_loop(pid);
|
|
|
|
sandbox_common();
|
|
#if (GOOS_freebsd || GOOS_openbsd || GOOS_netbsd) && (SYZ_EXECUTOR || SYZ_TUN_ENABLE)
|
|
initialize_tun(procid);
|
|
#endif
|
|
|
|
char pwbuf[1024];
|
|
struct passwd *pw, pwres;
|
|
if (getpwnam_r("nobody", &pwres, pwbuf, sizeof(pwbuf), &pw) != 0 || !pw)
|
|
fail("getpwnam_r(\"nobody\") failed");
|
|
|
|
if (setgroups(0, NULL))
|
|
fail("failed to setgroups");
|
|
if (setgid(pw->pw_gid))
|
|
fail("failed to setgid");
|
|
if (setuid(pw->pw_uid))
|
|
fail("failed to setuid");
|
|
|
|
loop();
|
|
doexit(1);
|
|
}
|
|
#endif
|
|
|
|
#elif GOOS_fuchsia
|
|
|
|
#include <fcntl.h>
|
|
#include <lib/fdio/directory.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <sys/file.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <utime.h>
|
|
#include <zircon/process.h>
|
|
#include <zircon/syscalls.h>
|
|
|
|
#if SYZ_EXECUTOR || __NR_get_root_resource
|
|
#include <ddk/driver.h>
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_HANDLE_SEGV
|
|
#include <pthread.h>
|
|
#include <setjmp.h>
|
|
#include <zircon/syscalls/debug.h>
|
|
#include <zircon/syscalls/exception.h>
|
|
#include <zircon/syscalls/object.h>
|
|
#include <zircon/syscalls/port.h>
|
|
|
|
static __thread int skip_segv;
|
|
static __thread jmp_buf segv_env;
|
|
|
|
static void segv_handler(void)
|
|
{
|
|
if (__atomic_load_n(&skip_segv, __ATOMIC_RELAXED)) {
|
|
debug("recover: skipping\n");
|
|
longjmp(segv_env, 1);
|
|
}
|
|
debug("recover: exiting\n");
|
|
doexit(SIGSEGV);
|
|
}
|
|
|
|
static void* ex_handler(void* arg)
|
|
{
|
|
zx_handle_t port = (zx_handle_t)(long)arg;
|
|
for (int i = 0; i < 10000; i++) {
|
|
zx_port_packet_t packet = {};
|
|
zx_status_t status = zx_port_wait(port, ZX_TIME_INFINITE, &packet);
|
|
if (status != ZX_OK) {
|
|
debug("zx_port_wait failed: %d\n", status);
|
|
continue;
|
|
}
|
|
debug("got exception packet: type=%d status=%d tid=%llu\n",
|
|
packet.type, packet.status, (unsigned long long)(packet.exception.tid));
|
|
zx_handle_t thread;
|
|
status = zx_object_get_child(zx_process_self(), packet.exception.tid,
|
|
ZX_RIGHT_SAME_RIGHTS, &thread);
|
|
if (status != ZX_OK) {
|
|
debug("zx_object_get_child failed: %d\n", status);
|
|
continue;
|
|
}
|
|
zx_thread_state_general_regs_t regs;
|
|
status = zx_thread_read_state(thread, ZX_THREAD_STATE_GENERAL_REGS,
|
|
®s, sizeof(regs));
|
|
if (status != ZX_OK) {
|
|
debug("zx_thread_read_state failed: %d (%d)\n",
|
|
(int)sizeof(regs), status);
|
|
} else {
|
|
#if GOARCH_amd64
|
|
regs.rip = (uint64)(void*)&segv_handler;
|
|
#elif GOARCH_arm64
|
|
regs.pc = (uint64)(void*)&segv_handler;
|
|
#else
|
|
#error "unsupported arch"
|
|
#endif
|
|
status = zx_thread_write_state(thread, ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs));
|
|
if (status != ZX_OK) {
|
|
debug("zx_thread_write_state failed: %d\n", status);
|
|
}
|
|
}
|
|
status = zx_task_resume_from_exception(thread, port, 0);
|
|
if (status != ZX_OK) {
|
|
debug("zx_task_resume_from_exception failed: %d\n", status);
|
|
}
|
|
zx_handle_close(thread);
|
|
}
|
|
doexit(1);
|
|
return 0;
|
|
}
|
|
|
|
static void install_segv_handler(void)
|
|
{
|
|
zx_status_t status;
|
|
zx_handle_t port;
|
|
if ((status = zx_port_create(0, &port)) != ZX_OK)
|
|
fail("zx_port_create failed: %d", status);
|
|
if ((status = zx_task_bind_exception_port(zx_process_self(), port, 0, 0)) != ZX_OK)
|
|
fail("zx_task_bind_exception_port failed: %d", status);
|
|
pthread_t th;
|
|
if (pthread_create(&th, 0, ex_handler, (void*)(long)port))
|
|
fail("pthread_create failed");
|
|
}
|
|
|
|
#define NONFAILING(...) \
|
|
{ \
|
|
__atomic_fetch_add(&skip_segv, 1, __ATOMIC_SEQ_CST); \
|
|
if (sigsetjmp(segv_env, 0) == 0) { \
|
|
__VA_ARGS__; \
|
|
} \
|
|
__atomic_fetch_sub(&skip_segv, 1, __ATOMIC_SEQ_CST); \
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_THREADED
|
|
#include <unistd.h>
|
|
typedef struct {
|
|
int state;
|
|
} event_t;
|
|
|
|
static void event_init(event_t* ev)
|
|
{
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_reset(event_t* ev)
|
|
{
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_set(event_t* ev)
|
|
{
|
|
if (ev->state)
|
|
fail("event already set");
|
|
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
|
|
}
|
|
|
|
static void event_wait(event_t* ev)
|
|
{
|
|
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
|
|
usleep(200);
|
|
}
|
|
|
|
static int event_isset(event_t* ev)
|
|
{
|
|
return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE);
|
|
}
|
|
|
|
static int event_timedwait(event_t* ev, uint64 timeout_ms)
|
|
{
|
|
uint64 start = current_time_ms();
|
|
for (;;) {
|
|
if (__atomic_load_n(&ev->state, __ATOMIC_RELAXED))
|
|
return 1;
|
|
if (current_time_ms() - start > timeout_ms)
|
|
return 0;
|
|
usleep(200);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_mmap
|
|
long syz_mmap(size_t addr, size_t size)
|
|
{
|
|
zx_handle_t root = zx_vmar_root_self();
|
|
zx_info_vmar_t info;
|
|
zx_status_t status = zx_object_get_info(root, ZX_INFO_VMAR, &info, sizeof(info), 0, 0);
|
|
if (status != ZX_OK)
|
|
fail("zx_object_get_info(ZX_INFO_VMAR) failed: %d", status);
|
|
zx_handle_t vmo;
|
|
status = zx_vmo_create(size, 0, &vmo);
|
|
if (status != ZX_OK) {
|
|
debug("zx_vmo_create failed with: %d\n", status);
|
|
return status;
|
|
}
|
|
status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo);
|
|
if (status != ZX_OK)
|
|
return status;
|
|
uintptr_t mapped_addr;
|
|
status = zx_vmar_map(root, ZX_VM_FLAG_SPECIFIC_OVERWRITE | ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE | ZX_VM_FLAG_PERM_EXECUTE,
|
|
addr - info.base, vmo, 0, size,
|
|
&mapped_addr);
|
|
return status;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_process_self
|
|
static long syz_process_self(void)
|
|
{
|
|
return zx_process_self();
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_thread_self
|
|
static long syz_thread_self(void)
|
|
{
|
|
return zx_thread_self();
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_vmar_root_self
|
|
static long syz_vmar_root_self(void)
|
|
{
|
|
return zx_vmar_root_self();
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_job_default
|
|
static long syz_job_default(void)
|
|
{
|
|
return zx_job_default();
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_future_time
|
|
static long syz_future_time(volatile long when)
|
|
{
|
|
zx_time_t delta_ms;
|
|
zx_time_t now;
|
|
switch (when) {
|
|
case 0:
|
|
delta_ms = 5;
|
|
break;
|
|
case 1:
|
|
delta_ms = 30;
|
|
break;
|
|
default:
|
|
delta_ms = 10000;
|
|
break;
|
|
}
|
|
zx_clock_get(ZX_CLOCK_MONOTONIC, &now);
|
|
return now + delta_ms * 1000 * 1000;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE
|
|
static void loop();
|
|
static int do_sandbox_none(void)
|
|
{
|
|
loop();
|
|
return 0;
|
|
}
|
|
#endif
|
|
#define CAST(f) ({void* p = (void*)f; p; })
|
|
|
|
#elif GOOS_linux
|
|
|
|
#include <stdlib.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#if SYZ_EXECUTOR
|
|
const int kExtraCoverSize = 256 << 10;
|
|
struct cover_t;
|
|
static void cover_reset(cover_t* cov);
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_THREADED
|
|
#include <linux/futex.h>
|
|
#include <pthread.h>
|
|
|
|
typedef struct {
|
|
int state;
|
|
} event_t;
|
|
|
|
static void event_init(event_t* ev)
|
|
{
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_reset(event_t* ev)
|
|
{
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_set(event_t* ev)
|
|
{
|
|
if (ev->state)
|
|
fail("event already set");
|
|
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
|
|
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG);
|
|
}
|
|
|
|
static void event_wait(event_t* ev)
|
|
{
|
|
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
|
|
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
|
|
}
|
|
|
|
static int event_isset(event_t* ev)
|
|
{
|
|
return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE);
|
|
}
|
|
|
|
static int event_timedwait(event_t* ev, uint64 timeout)
|
|
{
|
|
uint64 start = current_time_ms();
|
|
uint64 now = start;
|
|
for (;;) {
|
|
uint64 remain = timeout - (now - start);
|
|
struct timespec ts;
|
|
ts.tv_sec = remain / 1000;
|
|
ts.tv_nsec = (remain % 1000) * 1000 * 1000;
|
|
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, &ts);
|
|
if (__atomic_load_n(&ev->state, __ATOMIC_RELAXED))
|
|
return 1;
|
|
now = current_time_ms();
|
|
if (now - start > timeout)
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT || SYZ_TUN_ENABLE || SYZ_FAULT_INJECTION || SYZ_SANDBOX_NONE || \
|
|
SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP || \
|
|
SYZ_FAULT_INJECTION || SYZ_ENABLE_LEAK || SYZ_ENABLE_BINFMT_MISC
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static bool write_file(const char* file, const char* what, ...)
|
|
{
|
|
char buf[1024];
|
|
va_list args;
|
|
va_start(args, what);
|
|
vsnprintf(buf, sizeof(buf), what, args);
|
|
va_end(args);
|
|
buf[sizeof(buf) - 1] = 0;
|
|
int len = strlen(buf);
|
|
|
|
int fd = open(file, O_WRONLY | O_CLOEXEC);
|
|
if (fd == -1)
|
|
return false;
|
|
if (write(fd, buf, len) != len) {
|
|
int err = errno;
|
|
close(fd);
|
|
debug("write(%s) failed: %d\n", file, err);
|
|
errno = err;
|
|
return false;
|
|
}
|
|
close(fd);
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV || SYZ_TUN_ENABLE
|
|
#include <arpa/inet.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <linux/if_addr.h>
|
|
#include <linux/if_link.h>
|
|
#include <linux/in6.h>
|
|
#include <linux/neighbour.h>
|
|
#include <linux/net.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/veth.h>
|
|
|
|
static struct {
|
|
char* pos;
|
|
int nesting;
|
|
struct nlattr* nested[8];
|
|
char buf[1024];
|
|
} nlmsg;
|
|
|
|
static void netlink_init(int typ, int flags, const void* data, int size)
|
|
{
|
|
memset(&nlmsg, 0, sizeof(nlmsg));
|
|
struct nlmsghdr* hdr = (struct nlmsghdr*)nlmsg.buf;
|
|
hdr->nlmsg_type = typ;
|
|
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
|
|
memcpy(hdr + 1, data, size);
|
|
nlmsg.pos = (char*)(hdr + 1) + NLMSG_ALIGN(size);
|
|
}
|
|
|
|
static void netlink_attr(int typ, const void* data, int size)
|
|
{
|
|
struct nlattr* attr = (struct nlattr*)nlmsg.pos;
|
|
attr->nla_len = sizeof(*attr) + size;
|
|
attr->nla_type = typ;
|
|
memcpy(attr + 1, data, size);
|
|
nlmsg.pos += NLMSG_ALIGN(attr->nla_len);
|
|
}
|
|
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
static void netlink_nest(int typ)
|
|
{
|
|
struct nlattr* attr = (struct nlattr*)nlmsg.pos;
|
|
attr->nla_type = typ;
|
|
nlmsg.pos += sizeof(*attr);
|
|
nlmsg.nested[nlmsg.nesting++] = attr;
|
|
}
|
|
|
|
static void netlink_done(void)
|
|
{
|
|
struct nlattr* attr = nlmsg.nested[--nlmsg.nesting];
|
|
attr->nla_len = nlmsg.pos - (char*)attr;
|
|
}
|
|
#endif
|
|
|
|
static int netlink_send(int sock)
|
|
{
|
|
if (nlmsg.pos > nlmsg.buf + sizeof(nlmsg.buf) || nlmsg.nesting)
|
|
fail("nlmsg overflow/bad nesting");
|
|
struct nlmsghdr* hdr = (struct nlmsghdr*)nlmsg.buf;
|
|
hdr->nlmsg_len = nlmsg.pos - nlmsg.buf;
|
|
struct sockaddr_nl addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.nl_family = AF_NETLINK;
|
|
unsigned n = sendto(sock, nlmsg.buf, hdr->nlmsg_len, 0, (struct sockaddr*)&addr, sizeof(addr));
|
|
if (n != hdr->nlmsg_len)
|
|
fail("short netlink write: %d/%d", n, hdr->nlmsg_len);
|
|
n = recv(sock, nlmsg.buf, sizeof(nlmsg.buf), 0);
|
|
if (n < sizeof(struct nlmsghdr) + sizeof(struct nlmsgerr))
|
|
fail("short netlink read: %d", n);
|
|
if (hdr->nlmsg_type != NLMSG_ERROR)
|
|
fail("short netlink ack: %d", hdr->nlmsg_type);
|
|
return -((struct nlmsgerr*)(hdr + 1))->error;
|
|
}
|
|
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
static void netlink_add_device_impl(const char* type, const char* name)
|
|
{
|
|
struct ifinfomsg hdr;
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
netlink_init(RTM_NEWLINK, NLM_F_EXCL | NLM_F_CREATE, &hdr, sizeof(hdr));
|
|
if (name)
|
|
netlink_attr(IFLA_IFNAME, name, strlen(name));
|
|
netlink_nest(IFLA_LINKINFO);
|
|
netlink_attr(IFLA_INFO_KIND, type, strlen(type));
|
|
}
|
|
|
|
static void netlink_add_device(int sock, const char* type, const char* name)
|
|
{
|
|
netlink_add_device_impl(type, name);
|
|
netlink_done();
|
|
int err = netlink_send(sock);
|
|
debug("netlink: adding device %s type %s: %s\n", name, type, strerror(err));
|
|
(void)err;
|
|
}
|
|
|
|
static void netlink_add_veth(int sock, const char* name, const char* peer)
|
|
{
|
|
netlink_add_device_impl("veth", name);
|
|
netlink_nest(IFLA_INFO_DATA);
|
|
netlink_nest(VETH_INFO_PEER);
|
|
nlmsg.pos += sizeof(struct ifinfomsg);
|
|
netlink_attr(IFLA_IFNAME, peer, strlen(peer));
|
|
netlink_done();
|
|
netlink_done();
|
|
netlink_done();
|
|
int err = netlink_send(sock);
|
|
debug("netlink: adding device %s type veth peer %s: %s\n", name, peer, strerror(err));
|
|
(void)err;
|
|
}
|
|
|
|
static void netlink_add_hsr(int sock, const char* name, const char* slave1, const char* slave2)
|
|
{
|
|
netlink_add_device_impl("hsr", name);
|
|
netlink_nest(IFLA_INFO_DATA);
|
|
int ifindex1 = if_nametoindex(slave1);
|
|
netlink_attr(IFLA_HSR_SLAVE1, &ifindex1, sizeof(ifindex1));
|
|
int ifindex2 = if_nametoindex(slave2);
|
|
netlink_attr(IFLA_HSR_SLAVE2, &ifindex2, sizeof(ifindex2));
|
|
netlink_done();
|
|
netlink_done();
|
|
int err = netlink_send(sock);
|
|
debug("netlink: adding device %s type hsr slave1 %s slave2 %s: %s\n",
|
|
name, slave1, slave2, strerror(err));
|
|
(void)err;
|
|
}
|
|
#endif
|
|
|
|
static void netlink_device_change(int sock, const char* name, bool up,
|
|
const char* master, const void* mac, int macsize)
|
|
{
|
|
struct ifinfomsg hdr;
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
if (up)
|
|
hdr.ifi_flags = hdr.ifi_change = IFF_UP;
|
|
netlink_init(RTM_NEWLINK, 0, &hdr, sizeof(hdr));
|
|
netlink_attr(IFLA_IFNAME, name, strlen(name));
|
|
if (master) {
|
|
int ifindex = if_nametoindex(master);
|
|
netlink_attr(IFLA_MASTER, &ifindex, sizeof(ifindex));
|
|
}
|
|
if (macsize)
|
|
netlink_attr(IFLA_ADDRESS, mac, macsize);
|
|
int err = netlink_send(sock);
|
|
debug("netlink: device %s up master %s: %s\n", name, master, strerror(err));
|
|
(void)err;
|
|
}
|
|
|
|
static int netlink_add_addr(int sock, const char* dev, const void* addr, int addrsize)
|
|
{
|
|
struct ifaddrmsg hdr;
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
hdr.ifa_family = addrsize == 4 ? AF_INET : AF_INET6;
|
|
hdr.ifa_prefixlen = addrsize == 4 ? 24 : 120;
|
|
hdr.ifa_scope = RT_SCOPE_UNIVERSE;
|
|
hdr.ifa_index = if_nametoindex(dev);
|
|
netlink_init(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, &hdr, sizeof(hdr));
|
|
netlink_attr(IFA_LOCAL, addr, addrsize);
|
|
netlink_attr(IFA_ADDRESS, addr, addrsize);
|
|
return netlink_send(sock);
|
|
}
|
|
|
|
static void netlink_add_addr4(int sock, const char* dev, const char* addr)
|
|
{
|
|
struct in_addr in_addr;
|
|
inet_pton(AF_INET, addr, &in_addr);
|
|
int err = netlink_add_addr(sock, dev, &in_addr, sizeof(in_addr));
|
|
debug("netlink: add addr %s dev %s: %s\n", addr, dev, strerror(err));
|
|
(void)err;
|
|
}
|
|
|
|
static void netlink_add_addr6(int sock, const char* dev, const char* addr)
|
|
{
|
|
struct in6_addr in6_addr;
|
|
inet_pton(AF_INET6, addr, &in6_addr);
|
|
int err = netlink_add_addr(sock, dev, &in6_addr, sizeof(in6_addr));
|
|
debug("netlink: add addr %s dev %s: %s\n", addr, dev, strerror(err));
|
|
(void)err;
|
|
}
|
|
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE
|
|
static void netlink_add_neigh(int sock, const char* name,
|
|
const void* addr, int addrsize, const void* mac, int macsize)
|
|
{
|
|
struct ndmsg hdr;
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
hdr.ndm_family = addrsize == 4 ? AF_INET : AF_INET6;
|
|
hdr.ndm_ifindex = if_nametoindex(name);
|
|
hdr.ndm_state = NUD_PERMANENT;
|
|
netlink_init(RTM_NEWNEIGH, NLM_F_EXCL | NLM_F_CREATE, &hdr, sizeof(hdr));
|
|
netlink_attr(NDA_DST, addr, addrsize);
|
|
netlink_attr(NDA_LLADDR, mac, macsize);
|
|
int err = netlink_send(sock);
|
|
debug("netlink: add neigh %s addr %d lladdr %d: %s\n",
|
|
name, addrsize, macsize, strerror(err));
|
|
(void)err;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if_tun.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/tcp.h>
|
|
|
|
static int tunfd = -1;
|
|
static int tun_frags_enabled;
|
|
#define SYZ_TUN_MAX_PACKET_SIZE 1000
|
|
|
|
#define TUN_IFACE "syz_tun"
|
|
|
|
#define LOCAL_MAC 0xaaaaaaaaaaaa
|
|
#define REMOTE_MAC 0xaaaaaaaaaabb
|
|
|
|
#define LOCAL_IPV4 "172.20.20.170"
|
|
#define REMOTE_IPV4 "172.20.20.187"
|
|
|
|
#define LOCAL_IPV6 "fe80::aa"
|
|
#define REMOTE_IPV6 "fe80::bb"
|
|
|
|
#ifndef IFF_NAPI
|
|
#define IFF_NAPI 0x0010
|
|
#endif
|
|
#ifndef IFF_NAPI_FRAGS
|
|
#define IFF_NAPI_FRAGS 0x0020
|
|
#endif
|
|
|
|
static void initialize_tun(void)
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_tun)
|
|
return;
|
|
#endif
|
|
tunfd = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
|
|
if (tunfd == -1) {
|
|
#if SYZ_EXECUTOR
|
|
fail("tun: can't open /dev/net/tun");
|
|
#else
|
|
printf("tun: can't open /dev/net/tun: please enable CONFIG_TUN=y\n");
|
|
printf("otherwise fuzzing or reproducing might not work as intended\n");
|
|
return;
|
|
#endif
|
|
}
|
|
const int kTunFd = 240;
|
|
if (dup2(tunfd, kTunFd) < 0)
|
|
fail("dup2(tunfd, kTunFd) failed");
|
|
close(tunfd);
|
|
tunfd = kTunFd;
|
|
|
|
struct ifreq ifr;
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strncpy(ifr.ifr_name, TUN_IFACE, IFNAMSIZ);
|
|
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_NAPI | IFF_NAPI_FRAGS;
|
|
if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0) {
|
|
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
|
|
if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0)
|
|
fail("tun: ioctl(TUNSETIFF) failed");
|
|
}
|
|
if (ioctl(tunfd, TUNGETIFF, (void*)&ifr) < 0)
|
|
fail("tun: ioctl(TUNGETIFF) failed");
|
|
tun_frags_enabled = (ifr.ifr_flags & IFF_NAPI_FRAGS) != 0;
|
|
debug("tun_frags_enabled=%d\n", tun_frags_enabled);
|
|
char sysctl[64];
|
|
sprintf(sysctl, "/proc/sys/net/ipv6/conf/%s/accept_dad", TUN_IFACE);
|
|
write_file(sysctl, "0");
|
|
sprintf(sysctl, "/proc/sys/net/ipv6/conf/%s/router_solicitations", TUN_IFACE);
|
|
write_file(sysctl, "0");
|
|
|
|
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
|
if (sock == -1)
|
|
fail("socket(AF_NETLINK) failed");
|
|
|
|
netlink_add_addr4(sock, TUN_IFACE, LOCAL_IPV4);
|
|
netlink_add_addr6(sock, TUN_IFACE, LOCAL_IPV6);
|
|
uint64 macaddr = REMOTE_MAC;
|
|
struct in_addr in_addr;
|
|
inet_pton(AF_INET, REMOTE_IPV4, &in_addr);
|
|
netlink_add_neigh(sock, TUN_IFACE, &in_addr, sizeof(in_addr), &macaddr, ETH_ALEN);
|
|
struct in6_addr in6_addr;
|
|
inet_pton(AF_INET6, REMOTE_IPV6, &in6_addr);
|
|
netlink_add_neigh(sock, TUN_IFACE, &in6_addr, sizeof(in6_addr), &macaddr, ETH_ALEN);
|
|
macaddr = LOCAL_MAC;
|
|
netlink_device_change(sock, TUN_IFACE, true, 0, &macaddr, ETH_ALEN);
|
|
close(sock);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <linux/if_ether.h>
|
|
#include <linux/if_tun.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/tcp.h>
|
|
#define DEV_IPV4 "172.20.20.%d"
|
|
#define DEV_IPV6 "fe80::%02x"
|
|
#define DEV_MAC 0x00aaaaaaaaaa
|
|
static void initialize_netdevices(void)
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_net_dev)
|
|
return;
|
|
#endif
|
|
char netdevsim[16];
|
|
sprintf(netdevsim, "netdevsim%d", (int)procid);
|
|
struct {
|
|
const char* type;
|
|
const char* dev;
|
|
} devtypes[] = {
|
|
{"ip6gretap", "ip6gretap0"},
|
|
{"bridge", "bridge0"},
|
|
{"vcan", "vcan0"},
|
|
{"bond", "bond0"},
|
|
{"team", "team0"},
|
|
{"dummy", "dummy0"},
|
|
{"nlmon", "nlmon0"},
|
|
{"caif", "caif0"},
|
|
{"batadv", "batadv0"},
|
|
{"vxcan", "vxcan1"},
|
|
{"netdevsim", netdevsim},
|
|
{"veth", 0},
|
|
};
|
|
const char* devmasters[] = {"bridge", "bond", "team"};
|
|
struct {
|
|
const char* name;
|
|
int macsize;
|
|
bool noipv6;
|
|
} devices[] = {
|
|
{"lo", ETH_ALEN},
|
|
{"sit0", 0},
|
|
{"bridge0", ETH_ALEN},
|
|
{"vcan0", 0, true},
|
|
{"tunl0", 0},
|
|
{"gre0", 0},
|
|
{"gretap0", ETH_ALEN},
|
|
{"ip_vti0", 0},
|
|
{"ip6_vti0", 0},
|
|
{"ip6tnl0", 0},
|
|
{"ip6gre0", 0},
|
|
{"ip6gretap0", ETH_ALEN},
|
|
{"erspan0", ETH_ALEN},
|
|
{"bond0", ETH_ALEN},
|
|
{"veth0", ETH_ALEN},
|
|
{"veth1", ETH_ALEN},
|
|
{"team0", ETH_ALEN},
|
|
{"veth0_to_bridge", ETH_ALEN},
|
|
{"veth1_to_bridge", ETH_ALEN},
|
|
{"veth0_to_bond", ETH_ALEN},
|
|
{"veth1_to_bond", ETH_ALEN},
|
|
{"veth0_to_team", ETH_ALEN},
|
|
{"veth1_to_team", ETH_ALEN},
|
|
{"veth0_to_hsr", ETH_ALEN},
|
|
{"veth1_to_hsr", ETH_ALEN},
|
|
{"hsr0", 0},
|
|
{"dummy0", ETH_ALEN},
|
|
{"nlmon0", 0},
|
|
{"vxcan1", 0, true},
|
|
{"caif0", ETH_ALEN},
|
|
{"batadv0", ETH_ALEN},
|
|
{netdevsim, ETH_ALEN},
|
|
};
|
|
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
|
if (sock == -1)
|
|
fail("socket(AF_NETLINK) failed");
|
|
unsigned i;
|
|
for (i = 0; i < sizeof(devtypes) / sizeof(devtypes[0]); i++)
|
|
netlink_add_device(sock, devtypes[i].type, devtypes[i].dev);
|
|
for (i = 0; i < sizeof(devmasters) / (sizeof(devmasters[0])); i++) {
|
|
char master[32], slave0[32], veth0[32], slave1[32], veth1[32];
|
|
sprintf(slave0, "%s_slave_0", devmasters[i]);
|
|
sprintf(veth0, "veth0_to_%s", devmasters[i]);
|
|
netlink_add_veth(sock, slave0, veth0);
|
|
sprintf(slave1, "%s_slave_1", devmasters[i]);
|
|
sprintf(veth1, "veth1_to_%s", devmasters[i]);
|
|
netlink_add_veth(sock, slave1, veth1);
|
|
sprintf(master, "%s0", devmasters[i]);
|
|
netlink_device_change(sock, slave0, false, master, 0, 0);
|
|
netlink_device_change(sock, slave1, false, master, 0, 0);
|
|
}
|
|
netlink_device_change(sock, "bridge_slave_0", true, 0, 0, 0);
|
|
netlink_device_change(sock, "bridge_slave_1", true, 0, 0, 0);
|
|
netlink_add_veth(sock, "hsr_slave_0", "veth0_to_hsr");
|
|
netlink_add_veth(sock, "hsr_slave_1", "veth1_to_hsr");
|
|
netlink_add_hsr(sock, "hsr0", "hsr_slave_0", "hsr_slave_1");
|
|
netlink_device_change(sock, "hsr_slave_0", true, 0, 0, 0);
|
|
netlink_device_change(sock, "hsr_slave_1", true, 0, 0, 0);
|
|
|
|
for (i = 0; i < sizeof(devices) / (sizeof(devices[0])); i++) {
|
|
char addr[32];
|
|
sprintf(addr, DEV_IPV4, i + 10);
|
|
netlink_add_addr4(sock, devices[i].name, addr);
|
|
if (!devices[i].noipv6) {
|
|
sprintf(addr, DEV_IPV6, i + 10);
|
|
netlink_add_addr6(sock, devices[i].name, addr);
|
|
}
|
|
uint64 macaddr = DEV_MAC + ((i + 10ull) << 40);
|
|
netlink_device_change(sock, devices[i].name, true, 0, &macaddr, devices[i].macsize);
|
|
}
|
|
close(sock);
|
|
}
|
|
static void initialize_netdevices_init(void)
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_net_dev)
|
|
return;
|
|
#endif
|
|
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
|
if (sock == -1)
|
|
fail("socket(AF_NETLINK) failed");
|
|
struct {
|
|
const char* type;
|
|
int macsize;
|
|
bool noipv6;
|
|
bool noup;
|
|
} devtypes[] = {
|
|
{"nr", 7, true},
|
|
{"rose", 5, true, true},
|
|
};
|
|
unsigned i;
|
|
for (i = 0; i < sizeof(devtypes) / sizeof(devtypes[0]); i++) {
|
|
char dev[32], addr[32];
|
|
sprintf(dev, "%s%d", devtypes[i].type, (int)procid);
|
|
sprintf(addr, "172.30.%d.%d", i, (int)procid + 1);
|
|
netlink_add_addr4(sock, dev, addr);
|
|
if (!devtypes[i].noipv6) {
|
|
sprintf(addr, "fe88::%02x:%02x", i, (int)procid + 1);
|
|
netlink_add_addr6(sock, dev, addr);
|
|
}
|
|
int macsize = devtypes[i].macsize;
|
|
uint64 macaddr = 0xbbbbbb + ((unsigned long long)i << (8 * (macsize - 2))) +
|
|
(procid << (8 * (macsize - 1)));
|
|
netlink_device_change(sock, dev, !devtypes[i].noup, 0, &macaddr, macsize);
|
|
}
|
|
close(sock);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE && (__NR_syz_extract_tcp_res || SYZ_REPEAT)
|
|
#include <errno.h>
|
|
|
|
static int read_tun(char* data, int size)
|
|
{
|
|
if (tunfd < 0)
|
|
return -1;
|
|
|
|
int rv = read(tunfd, data, size);
|
|
if (rv < 0) {
|
|
if (errno == EAGAIN)
|
|
return -1;
|
|
if (errno == EBADFD)
|
|
return -1;
|
|
fail("tun: read failed with %d", rv);
|
|
}
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_emit_ethernet && SYZ_TUN_ENABLE
|
|
#include <stdbool.h>
|
|
#include <sys/uio.h>
|
|
|
|
#define MAX_FRAGS 4
|
|
struct vnet_fragmentation {
|
|
uint32 full;
|
|
uint32 count;
|
|
uint32 frags[MAX_FRAGS];
|
|
};
|
|
|
|
static long syz_emit_ethernet(volatile long a0, volatile long a1, volatile long a2)
|
|
{
|
|
if (tunfd < 0)
|
|
return (uintptr_t)-1;
|
|
|
|
uint32 length = a0;
|
|
char* data = (char*)a1;
|
|
debug_dump_data(data, length);
|
|
|
|
struct vnet_fragmentation* frags = (struct vnet_fragmentation*)a2;
|
|
struct iovec vecs[MAX_FRAGS + 1];
|
|
uint32 nfrags = 0;
|
|
if (!tun_frags_enabled || frags == NULL) {
|
|
vecs[nfrags].iov_base = data;
|
|
vecs[nfrags].iov_len = length;
|
|
nfrags++;
|
|
} else {
|
|
bool full = true;
|
|
uint32 i, count = 0;
|
|
NONFAILING(full = frags->full);
|
|
NONFAILING(count = frags->count);
|
|
if (count > MAX_FRAGS)
|
|
count = MAX_FRAGS;
|
|
for (i = 0; i < count && length != 0; i++) {
|
|
uint32 size = 0;
|
|
NONFAILING(size = frags->frags[i]);
|
|
if (size > length)
|
|
size = length;
|
|
vecs[nfrags].iov_base = data;
|
|
vecs[nfrags].iov_len = size;
|
|
nfrags++;
|
|
data += size;
|
|
length -= size;
|
|
}
|
|
if (length != 0 && (full || nfrags == 0)) {
|
|
vecs[nfrags].iov_base = data;
|
|
vecs[nfrags].iov_len = length;
|
|
nfrags++;
|
|
}
|
|
}
|
|
return writev(tunfd, vecs, nfrags);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT && SYZ_TUN_ENABLE
|
|
static void flush_tun()
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_tun)
|
|
return;
|
|
#endif
|
|
char data[SYZ_TUN_MAX_PACKET_SIZE];
|
|
while (read_tun(&data[0], sizeof(data)) != -1) {
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_extract_tcp_res && SYZ_TUN_ENABLE
|
|
#ifndef __ANDROID__
|
|
struct ipv6hdr {
|
|
__u8 priority : 4,
|
|
version : 4;
|
|
__u8 flow_lbl[3];
|
|
|
|
__be16 payload_len;
|
|
__u8 nexthdr;
|
|
__u8 hop_limit;
|
|
|
|
struct in6_addr saddr;
|
|
struct in6_addr daddr;
|
|
};
|
|
#endif
|
|
|
|
struct tcp_resources {
|
|
uint32 seq;
|
|
uint32 ack;
|
|
};
|
|
|
|
static long syz_extract_tcp_res(volatile long a0, volatile long a1, volatile long a2)
|
|
{
|
|
|
|
if (tunfd < 0)
|
|
return (uintptr_t)-1;
|
|
|
|
char data[SYZ_TUN_MAX_PACKET_SIZE];
|
|
int rv = read_tun(&data[0], sizeof(data));
|
|
if (rv == -1)
|
|
return (uintptr_t)-1;
|
|
size_t length = rv;
|
|
debug_dump_data(data, length);
|
|
|
|
struct tcphdr* tcphdr;
|
|
|
|
if (length < sizeof(struct ethhdr))
|
|
return (uintptr_t)-1;
|
|
struct ethhdr* ethhdr = (struct ethhdr*)&data[0];
|
|
|
|
if (ethhdr->h_proto == htons(ETH_P_IP)) {
|
|
if (length < sizeof(struct ethhdr) + sizeof(struct iphdr))
|
|
return (uintptr_t)-1;
|
|
struct iphdr* iphdr = (struct iphdr*)&data[sizeof(struct ethhdr)];
|
|
if (iphdr->protocol != IPPROTO_TCP)
|
|
return (uintptr_t)-1;
|
|
if (length < sizeof(struct ethhdr) + iphdr->ihl * 4 + sizeof(struct tcphdr))
|
|
return (uintptr_t)-1;
|
|
tcphdr = (struct tcphdr*)&data[sizeof(struct ethhdr) + iphdr->ihl * 4];
|
|
} else {
|
|
if (length < sizeof(struct ethhdr) + sizeof(struct ipv6hdr))
|
|
return (uintptr_t)-1;
|
|
struct ipv6hdr* ipv6hdr = (struct ipv6hdr*)&data[sizeof(struct ethhdr)];
|
|
if (ipv6hdr->nexthdr != IPPROTO_TCP)
|
|
return (uintptr_t)-1;
|
|
if (length < sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
|
|
return (uintptr_t)-1;
|
|
tcphdr = (struct tcphdr*)&data[sizeof(struct ethhdr) + sizeof(struct ipv6hdr)];
|
|
}
|
|
|
|
struct tcp_resources* res = (struct tcp_resources*)a0;
|
|
NONFAILING(res->seq = htonl((ntohl(tcphdr->seq) + (uint32)a1)));
|
|
NONFAILING(res->ack = htonl((ntohl(tcphdr->ack_seq) + (uint32)a2)));
|
|
|
|
debug("extracted seq: %08x\n", res->seq);
|
|
debug("extracted ack: %08x\n", res->ack);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_usb_connect
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/usb/ch9.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#define USB_DEBUG 0
|
|
|
|
#define USB_MAX_IFACE_NUM 4
|
|
#define USB_MAX_EP_NUM 32
|
|
|
|
struct usb_iface_index {
|
|
struct usb_interface_descriptor* iface;
|
|
struct usb_endpoint_descriptor* eps[USB_MAX_EP_NUM];
|
|
unsigned eps_num;
|
|
};
|
|
|
|
struct usb_device_index {
|
|
struct usb_device_descriptor* dev;
|
|
struct usb_config_descriptor* config;
|
|
unsigned config_length;
|
|
struct usb_iface_index ifaces[USB_MAX_IFACE_NUM];
|
|
unsigned ifaces_num;
|
|
};
|
|
|
|
static bool parse_usb_descriptor(char* buffer, size_t length, struct usb_device_index* index)
|
|
{
|
|
if (length < sizeof(*index->dev) + sizeof(*index->config))
|
|
return false;
|
|
|
|
memset(index, 0, sizeof(*index));
|
|
|
|
index->dev = (struct usb_device_descriptor*)buffer;
|
|
index->config = (struct usb_config_descriptor*)(buffer + sizeof(*index->dev));
|
|
index->config_length = length - sizeof(*index->dev);
|
|
size_t offset = 0;
|
|
|
|
while (true) {
|
|
if (offset + 1 >= length)
|
|
break;
|
|
uint8 desc_length = buffer[offset];
|
|
uint8 desc_type = buffer[offset + 1];
|
|
if (desc_length <= 2)
|
|
break;
|
|
if (offset + desc_length > length)
|
|
break;
|
|
if (desc_type == USB_DT_INTERFACE && index->ifaces_num < USB_MAX_IFACE_NUM) {
|
|
struct usb_interface_descriptor* iface = (struct usb_interface_descriptor*)(buffer + offset);
|
|
debug("parse_usb_descriptor: found interface #%u (%d, %d) at %p\n",
|
|
index->ifaces_num, iface->bInterfaceNumber, iface->bAlternateSetting, iface);
|
|
index->ifaces[index->ifaces_num++].iface = iface;
|
|
}
|
|
if (desc_type == USB_DT_ENDPOINT && index->ifaces_num > 0) {
|
|
struct usb_iface_index* iface = &index->ifaces[index->ifaces_num - 1];
|
|
debug("parse_usb_descriptor: found endpoint #%u at %p\n", iface->eps_num, buffer + offset);
|
|
if (iface->eps_num < USB_MAX_EP_NUM)
|
|
iface->eps[iface->eps_num++] = (struct usb_endpoint_descriptor*)(buffer + offset);
|
|
}
|
|
offset += desc_length;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum usb_fuzzer_event_type {
|
|
USB_FUZZER_EVENT_INVALID,
|
|
USB_FUZZER_EVENT_CONNECT,
|
|
USB_FUZZER_EVENT_DISCONNECT,
|
|
USB_FUZZER_EVENT_SUSPEND,
|
|
USB_FUZZER_EVENT_RESUME,
|
|
USB_FUZZER_EVENT_CONTROL,
|
|
};
|
|
|
|
struct usb_fuzzer_event {
|
|
uint32 type;
|
|
uint32 length;
|
|
char data[0];
|
|
};
|
|
|
|
struct usb_fuzzer_init {
|
|
uint64 speed;
|
|
const char* driver_name;
|
|
const char* device_name;
|
|
};
|
|
|
|
struct usb_fuzzer_ep_io {
|
|
uint16 ep;
|
|
uint16 flags;
|
|
uint32 length;
|
|
char data[0];
|
|
};
|
|
|
|
#define USB_FUZZER_IOCTL_INIT _IOW('U', 0, struct usb_fuzzer_init)
|
|
#define USB_FUZZER_IOCTL_RUN _IO('U', 1)
|
|
#define USB_FUZZER_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_fuzzer_event)
|
|
#define USB_FUZZER_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_fuzzer_ep_io)
|
|
#define USB_FUZZER_IOCTL_EP0_READ _IOWR('U', 4, struct usb_fuzzer_ep_io)
|
|
#define USB_FUZZER_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor)
|
|
#define USB_FUZZER_IOCTL_EP_WRITE _IOW('U', 7, struct usb_fuzzer_ep_io)
|
|
#define USB_FUZZER_IOCTL_EP_READ _IOWR('U', 8, struct usb_fuzzer_ep_io)
|
|
#define USB_FUZZER_IOCTL_CONFIGURE _IO('U', 9)
|
|
#define USB_FUZZER_IOCTL_VBUS_DRAW _IOW('U', 10, uint32)
|
|
|
|
int usb_fuzzer_open()
|
|
{
|
|
return open("/sys/kernel/debug/usb-fuzzer", O_RDWR);
|
|
}
|
|
|
|
int usb_fuzzer_init(int fd, uint32 speed, const char* driver, const char* device)
|
|
{
|
|
struct usb_fuzzer_init arg;
|
|
arg.speed = speed;
|
|
arg.driver_name = driver;
|
|
arg.device_name = device;
|
|
return ioctl(fd, USB_FUZZER_IOCTL_INIT, &arg);
|
|
}
|
|
|
|
int usb_fuzzer_run(int fd)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_RUN, 0);
|
|
}
|
|
|
|
int usb_fuzzer_event_fetch(int fd, struct usb_fuzzer_event* event)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_EVENT_FETCH, event);
|
|
}
|
|
|
|
int usb_fuzzer_ep0_write(int fd, struct usb_fuzzer_ep_io* io)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_EP0_WRITE, io);
|
|
}
|
|
|
|
int usb_fuzzer_ep0_read(int fd, struct usb_fuzzer_ep_io* io)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_EP0_READ, io);
|
|
}
|
|
|
|
int usb_fuzzer_ep_write(int fd, struct usb_fuzzer_ep_io* io)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_EP_WRITE, io);
|
|
}
|
|
|
|
int usb_fuzzer_ep_read(int fd, struct usb_fuzzer_ep_io* io)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_EP_READ, io);
|
|
}
|
|
|
|
int usb_fuzzer_ep_enable(int fd, struct usb_endpoint_descriptor* desc)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_EP_ENABLE, desc);
|
|
}
|
|
|
|
int usb_fuzzer_configure(int fd)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_CONFIGURE, 0);
|
|
}
|
|
|
|
int usb_fuzzer_vbus_draw(int fd, uint32 power)
|
|
{
|
|
return ioctl(fd, USB_FUZZER_IOCTL_VBUS_DRAW, power);
|
|
}
|
|
|
|
#define USB_MAX_PACKET_SIZE 1024
|
|
|
|
struct usb_fuzzer_control_event {
|
|
struct usb_fuzzer_event inner;
|
|
struct usb_ctrlrequest ctrl;
|
|
char data[USB_MAX_PACKET_SIZE];
|
|
};
|
|
|
|
struct usb_fuzzer_ep_io_data {
|
|
struct usb_fuzzer_ep_io inner;
|
|
char data[USB_MAX_PACKET_SIZE];
|
|
};
|
|
|
|
struct vusb_connect_string_descriptor {
|
|
uint32 len;
|
|
char* str;
|
|
} __attribute__((packed));
|
|
|
|
struct vusb_connect_descriptors {
|
|
uint32 qual_len;
|
|
char* qual;
|
|
uint32 bos_len;
|
|
char* bos;
|
|
uint32 strs_len;
|
|
struct vusb_connect_string_descriptor strs[0];
|
|
} __attribute__((packed));
|
|
|
|
static const char* default_string = "syzkaller";
|
|
|
|
static bool lookup_connect_response(struct vusb_connect_descriptors* descs, struct usb_device_index* index,
|
|
struct usb_ctrlrequest* ctrl, char** response_data, uint32* response_length)
|
|
{
|
|
uint8 str_idx;
|
|
|
|
switch (ctrl->bRequestType & USB_TYPE_MASK) {
|
|
case USB_TYPE_STANDARD:
|
|
switch (ctrl->bRequest) {
|
|
case USB_REQ_GET_DESCRIPTOR:
|
|
switch (ctrl->wValue >> 8) {
|
|
case USB_DT_DEVICE:
|
|
*response_data = (char*)index->dev;
|
|
*response_length = sizeof(*index->dev);
|
|
return true;
|
|
case USB_DT_CONFIG:
|
|
*response_data = (char*)index->config;
|
|
*response_length = index->config_length;
|
|
return true;
|
|
case USB_DT_STRING:
|
|
str_idx = (uint8)ctrl->wValue;
|
|
if (!descs || str_idx >= descs->strs_len) {
|
|
*response_data = (char*)default_string;
|
|
*response_length = strlen(default_string);
|
|
} else {
|
|
*response_data = descs->strs[str_idx].str;
|
|
*response_length = descs->strs[str_idx].len;
|
|
}
|
|
return true;
|
|
case USB_DT_BOS:
|
|
*response_data = descs->bos;
|
|
*response_length = descs->bos_len;
|
|
return true;
|
|
case USB_DT_DEVICE_QUALIFIER:
|
|
*response_data = descs->qual;
|
|
*response_length = descs->qual_len;
|
|
return true;
|
|
default:
|
|
fail("syz_usb_connect: no response");
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
fail("syz_usb_connect: no response");
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
fail("syz_usb_connect: no response");
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static volatile long syz_usb_connect(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
|
|
{
|
|
uint64 speed = a0;
|
|
uint64 dev_len = a1;
|
|
char* dev = (char*)a2;
|
|
struct vusb_connect_descriptors* descs = (struct vusb_connect_descriptors*)a3;
|
|
|
|
debug("syz_usb_connect: dev: %p\n", dev);
|
|
if (!dev) {
|
|
debug("syz_usb_connect: dev is null\n");
|
|
return -1;
|
|
}
|
|
|
|
debug("syz_usb_connect: device data:\n");
|
|
debug_dump_data(dev, dev_len);
|
|
|
|
struct usb_device_index index;
|
|
memset(&index, 0, sizeof(index));
|
|
int rv = 0;
|
|
NONFAILING(rv = parse_usb_descriptor(dev, dev_len, &index));
|
|
if (!rv) {
|
|
debug("syz_usb_connect: parse_usb_descriptor failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
debug("syz_usb_connect: parsed usb descriptor\n");
|
|
|
|
int fd = usb_fuzzer_open();
|
|
if (fd < 0) {
|
|
debug("syz_usb_connect: usb_fuzzer_open failed with %d\n", rv);
|
|
return fd;
|
|
}
|
|
debug("syz_usb_connect: usb_fuzzer_open success\n");
|
|
char device[32];
|
|
sprintf(&device[0], "dummy_udc.%llu", procid);
|
|
rv = usb_fuzzer_init(fd, speed, "dummy_udc", &device[0]);
|
|
if (rv < 0) {
|
|
debug("syz_usb_connect: usb_fuzzer_init failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
debug("syz_usb_connect: usb_fuzzer_init success\n");
|
|
|
|
rv = usb_fuzzer_run(fd);
|
|
if (rv < 0) {
|
|
debug("syz_usb_connect: usb_fuzzer_run failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
debug("syz_usb_connect: usb_fuzzer_run success\n");
|
|
|
|
bool done = false;
|
|
while (!done) {
|
|
struct usb_fuzzer_control_event event;
|
|
event.inner.type = 0;
|
|
event.inner.length = sizeof(event.ctrl);
|
|
rv = usb_fuzzer_event_fetch(fd, (struct usb_fuzzer_event*)&event);
|
|
if (rv < 0) {
|
|
debug("syz_usb_connect: usb_fuzzer_event_fetch failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
if (event.inner.type != USB_FUZZER_EVENT_CONTROL)
|
|
continue;
|
|
|
|
debug("syz_usb_connect: bRequestType: 0x%x (%s), bRequest: 0x%x, wValue: 0x%x, wIndex: 0x%x, wLength: %d\n",
|
|
event.ctrl.bRequestType, (event.ctrl.bRequestType & USB_DIR_IN) ? "IN" : "OUT",
|
|
event.ctrl.bRequest, event.ctrl.wValue, event.ctrl.wIndex, event.ctrl.wLength);
|
|
|
|
bool response_found = false;
|
|
char* response_data = NULL;
|
|
uint32 response_length = 0;
|
|
|
|
if (event.ctrl.bRequestType & USB_DIR_IN) {
|
|
NONFAILING(response_found = lookup_connect_response(descs, &index, &event.ctrl, &response_data, &response_length));
|
|
if (!response_found) {
|
|
debug("syz_usb_connect: unknown control IN request\n");
|
|
return -1;
|
|
}
|
|
} else {
|
|
if ((event.ctrl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD ||
|
|
event.ctrl.bRequest != USB_REQ_SET_CONFIGURATION) {
|
|
fail("syz_usb_connect: unknown control OUT request");
|
|
return -1;
|
|
}
|
|
done = true;
|
|
}
|
|
|
|
if (done) {
|
|
rv = usb_fuzzer_vbus_draw(fd, index.config->bMaxPower);
|
|
if (rv < 0) {
|
|
debug("syz_usb_connect: usb_fuzzer_vbus_draw failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
rv = usb_fuzzer_configure(fd);
|
|
if (rv < 0) {
|
|
debug("syz_usb_connect: usb_fuzzer_configure failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
unsigned ep;
|
|
for (ep = 0; ep < index.ifaces[0].eps_num; ep++) {
|
|
rv = usb_fuzzer_ep_enable(fd, index.ifaces[0].eps[ep]);
|
|
if (rv < 0) {
|
|
debug("syz_usb_connect: usb_fuzzer_ep_enable(%d) failed with %d\n", ep, rv);
|
|
} else {
|
|
debug("syz_usb_connect: endpoint %d enabled\n", ep);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct usb_fuzzer_ep_io_data response;
|
|
response.inner.ep = 0;
|
|
response.inner.flags = 0;
|
|
if (response_length > sizeof(response.data))
|
|
response_length = 0;
|
|
if (event.ctrl.wLength < response_length)
|
|
response_length = event.ctrl.wLength;
|
|
response.inner.length = response_length;
|
|
if (response_data)
|
|
memcpy(&response.data[0], response_data, response_length);
|
|
else
|
|
memset(&response.data[0], 0, response_length);
|
|
|
|
if (event.ctrl.bRequestType & USB_DIR_IN) {
|
|
debug("syz_usb_connect: IN, length = %d\n", response.inner.length);
|
|
rv = usb_fuzzer_ep0_write(fd, (struct usb_fuzzer_ep_io*)&response);
|
|
} else {
|
|
rv = usb_fuzzer_ep0_read(fd, (struct usb_fuzzer_ep_io*)&response);
|
|
debug("syz_usb_connect: OUT, length = %d\n", response.inner.length);
|
|
debug_dump_data(&event.data[0], response.inner.length);
|
|
}
|
|
if (rv < 0) {
|
|
debug("syz_usb_connect: usb_fuzzer_ep0_read/write failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
sleep_ms(200);
|
|
|
|
debug("syz_usb_connect: configured\n");
|
|
|
|
return fd;
|
|
}
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_usb_control_io
|
|
struct vusb_descriptor {
|
|
uint8 req_type;
|
|
uint8 desc_type;
|
|
uint32 len;
|
|
char data[0];
|
|
} __attribute__((packed));
|
|
|
|
struct vusb_descriptors {
|
|
uint32 len;
|
|
struct vusb_descriptor* generic;
|
|
struct vusb_descriptor* descs[0];
|
|
} __attribute__((packed));
|
|
|
|
struct vusb_response {
|
|
uint8 type;
|
|
uint8 req;
|
|
uint32 len;
|
|
char data[0];
|
|
} __attribute__((packed));
|
|
|
|
struct vusb_responses {
|
|
uint32 len;
|
|
struct vusb_response* generic;
|
|
struct vusb_response* resps[0];
|
|
} __attribute__((packed));
|
|
|
|
static bool lookup_control_response(struct vusb_descriptors* descs, struct vusb_responses* resps,
|
|
struct usb_ctrlrequest* ctrl, char** response_data, uint32* response_length)
|
|
{
|
|
int descs_num = 0;
|
|
int resps_num = 0;
|
|
|
|
if (descs)
|
|
descs_num = (descs->len - offsetof(struct vusb_descriptors, descs)) / sizeof(descs->descs[0]);
|
|
if (resps)
|
|
resps_num = (resps->len - offsetof(struct vusb_responses, resps)) / sizeof(resps->resps[0]);
|
|
|
|
uint8 req = ctrl->bRequest;
|
|
uint8 req_type = ctrl->bRequestType & USB_TYPE_MASK;
|
|
uint8 desc_type = ctrl->wValue >> 8;
|
|
|
|
if (req == USB_REQ_GET_DESCRIPTOR) {
|
|
int i;
|
|
|
|
for (i = 0; i < descs_num; i++) {
|
|
struct vusb_descriptor* desc = descs->descs[i];
|
|
if (!desc)
|
|
continue;
|
|
if (desc->req_type == req_type && desc->desc_type == desc_type) {
|
|
*response_length = desc->len;
|
|
if (*response_length != 0)
|
|
*response_data = &desc->data[0];
|
|
else
|
|
*response_data = NULL;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (descs->generic) {
|
|
*response_data = &descs->generic->data[0];
|
|
*response_length = descs->generic->len;
|
|
return true;
|
|
}
|
|
} else {
|
|
int i;
|
|
|
|
for (i = 0; i < resps_num; i++) {
|
|
struct vusb_response* resp = resps->resps[i];
|
|
if (!resp)
|
|
continue;
|
|
if (resp->type == req_type && resp->req == req) {
|
|
*response_length = resp->len;
|
|
if (*response_length != 0)
|
|
*response_data = &resp->data[0];
|
|
else
|
|
*response_data = NULL;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (resps->generic) {
|
|
*response_data = &resps->generic->data[0];
|
|
*response_length = resps->generic->len;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if USB_DEBUG
|
|
#include <linux/hid.h>
|
|
#include <linux/usb/cdc.h>
|
|
#include <linux/usb/ch11.h>
|
|
#include <linux/usb/ch9.h>
|
|
|
|
static void analyze_control_request(struct usb_ctrlrequest* ctrl)
|
|
{
|
|
switch (ctrl->bRequestType & USB_TYPE_MASK) {
|
|
case USB_TYPE_STANDARD:
|
|
switch (ctrl->bRequest) {
|
|
case USB_REQ_GET_DESCRIPTOR:
|
|
switch (ctrl->wValue >> 8) {
|
|
case USB_DT_DEVICE:
|
|
case USB_DT_CONFIG:
|
|
case USB_DT_STRING:
|
|
case HID_DT_REPORT:
|
|
case USB_DT_BOS:
|
|
case USB_DT_HUB:
|
|
case USB_DT_SS_HUB:
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case USB_TYPE_CLASS:
|
|
switch (ctrl->bRequest) {
|
|
case USB_REQ_GET_INTERFACE:
|
|
case USB_REQ_GET_CONFIGURATION:
|
|
case USB_REQ_GET_STATUS:
|
|
case USB_CDC_GET_NTB_PARAMETERS:
|
|
return;
|
|
}
|
|
}
|
|
fail("analyze_control_request: unknown control request (0x%x, 0x%x, 0x%x)",
|
|
ctrl->bRequestType, ctrl->bRequest, ctrl->wValue);
|
|
}
|
|
#endif
|
|
|
|
static volatile long syz_usb_control_io(volatile long a0, volatile long a1, volatile long a2)
|
|
{
|
|
int fd = a0;
|
|
struct vusb_descriptors* descs = (struct vusb_descriptors*)a1;
|
|
struct vusb_responses* resps = (struct vusb_responses*)a2;
|
|
|
|
struct usb_fuzzer_control_event event;
|
|
event.inner.type = 0;
|
|
event.inner.length = USB_MAX_PACKET_SIZE;
|
|
int rv = usb_fuzzer_event_fetch(fd, (struct usb_fuzzer_event*)&event);
|
|
if (rv < 0) {
|
|
debug("syz_usb_control_io: usb_fuzzer_ep0_read failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
if (event.inner.type != USB_FUZZER_EVENT_CONTROL) {
|
|
debug("syz_usb_control_io: wrong event type: %d\n", (int)event.inner.type);
|
|
return -1;
|
|
}
|
|
|
|
debug("syz_usb_control_io: bRequestType: 0x%x (%s), bRequest: 0x%x, wValue: 0x%x, wIndex: 0x%x, wLength: %d\n",
|
|
event.ctrl.bRequestType, (event.ctrl.bRequestType & USB_DIR_IN) ? "IN" : "OUT",
|
|
event.ctrl.bRequest, event.ctrl.wValue, event.ctrl.wIndex, event.ctrl.wLength);
|
|
|
|
bool response_found = false;
|
|
char* response_data = NULL;
|
|
uint32 response_length = 0;
|
|
|
|
if (event.ctrl.bRequestType & USB_DIR_IN) {
|
|
NONFAILING(response_found = lookup_control_response(descs, resps, &event.ctrl, &response_data, &response_length));
|
|
if (!response_found) {
|
|
#if USB_DEBUG
|
|
analyze_control_request(&event.ctrl);
|
|
#endif
|
|
debug("syz_usb_control_io: unknown control IN request\n");
|
|
return -1;
|
|
}
|
|
} else {
|
|
response_length = event.ctrl.wLength;
|
|
}
|
|
|
|
struct usb_fuzzer_ep_io_data response;
|
|
response.inner.ep = 0;
|
|
response.inner.flags = 0;
|
|
if (response_length > sizeof(response.data))
|
|
response_length = 0;
|
|
if (event.ctrl.wLength < response_length)
|
|
response_length = event.ctrl.wLength;
|
|
response.inner.length = response_length;
|
|
if (response_data)
|
|
memcpy(&response.data[0], response_data, response_length);
|
|
else
|
|
memset(&response.data[0], 0, response_length);
|
|
|
|
if (event.ctrl.bRequestType & USB_DIR_IN) {
|
|
debug("syz_usb_control_io: IN, length = %d\n", response.inner.length);
|
|
debug_dump_data(&response.data[0], response.inner.length);
|
|
rv = usb_fuzzer_ep0_write(fd, (struct usb_fuzzer_ep_io*)&response);
|
|
} else {
|
|
rv = usb_fuzzer_ep0_read(fd, (struct usb_fuzzer_ep_io*)&response);
|
|
debug("syz_usb_control_io: OUT, length = %d\n", response.inner.length);
|
|
debug_dump_data(&event.data[0], response.inner.length);
|
|
}
|
|
if (rv < 0) {
|
|
debug("syz_usb_control_io: usb_fuzzer_ep0_read/write failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
sleep_ms(200);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_usb_ep_write
|
|
static volatile long syz_usb_ep_write(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
|
|
{
|
|
int fd = a0;
|
|
uint16 ep = a1;
|
|
uint32 len = a2;
|
|
char* data = (char*)a3;
|
|
|
|
struct usb_fuzzer_ep_io_data io_data;
|
|
io_data.inner.ep = ep;
|
|
io_data.inner.flags = 0;
|
|
if (len > sizeof(io_data.data))
|
|
len = sizeof(io_data.data);
|
|
io_data.inner.length = len;
|
|
NONFAILING(memcpy(&io_data.data[0], data, len));
|
|
|
|
int rv = usb_fuzzer_ep_write(fd, (struct usb_fuzzer_ep_io*)&io_data);
|
|
if (rv < 0) {
|
|
debug("syz_usb_ep_write: usb_fuzzer_ep_write failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
sleep_ms(200);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_usb_ep_read
|
|
static volatile long syz_usb_ep_read(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
|
|
{
|
|
int fd = a0;
|
|
uint16 ep = a1;
|
|
uint32 len = a2;
|
|
char* data = (char*)a3;
|
|
|
|
struct usb_fuzzer_ep_io_data io_data;
|
|
io_data.inner.ep = ep;
|
|
io_data.inner.flags = 0;
|
|
if (len > sizeof(io_data.data))
|
|
len = sizeof(io_data.data);
|
|
io_data.inner.length = len;
|
|
|
|
int rv = usb_fuzzer_ep_read(fd, (struct usb_fuzzer_ep_io*)&io_data);
|
|
if (rv < 0) {
|
|
debug("syz_usb_ep_read: usb_fuzzer_ep_read failed with %d\n", rv);
|
|
return rv;
|
|
}
|
|
|
|
NONFAILING(memcpy(&data[0], &io_data.data[0], io_data.inner.length));
|
|
|
|
debug("syz_usb_ep_read: received data:\n");
|
|
debug_dump_data(&io_data.data[0], io_data.inner.length);
|
|
|
|
sleep_ms(200);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_usb_disconnect
|
|
static volatile long syz_usb_disconnect(volatile long a0)
|
|
{
|
|
int fd = a0;
|
|
|
|
int rv = close(fd);
|
|
|
|
sleep_ms(200);
|
|
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_open_dev
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static long syz_open_dev(volatile long a0, volatile long a1, volatile long a2)
|
|
{
|
|
if (a0 == 0xc || a0 == 0xb) {
|
|
char buf[128];
|
|
sprintf(buf, "/dev/%s/%d:%d", a0 == 0xc ? "char" : "block", (uint8)a1, (uint8)a2);
|
|
return open(buf, O_RDWR, 0);
|
|
} else {
|
|
char buf[1024];
|
|
char* hash;
|
|
NONFAILING(strncpy(buf, (char*)a0, sizeof(buf) - 1));
|
|
buf[sizeof(buf) - 1] = 0;
|
|
while ((hash = strchr(buf, '#'))) {
|
|
*hash = '0' + (char)(a1 % 10);
|
|
a1 /= 10;
|
|
}
|
|
return open(buf, a2, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_open_procfs
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static long syz_open_procfs(volatile long a0, volatile long a1)
|
|
{
|
|
|
|
char buf[128];
|
|
memset(buf, 0, sizeof(buf));
|
|
if (a0 == 0) {
|
|
NONFAILING(snprintf(buf, sizeof(buf), "/proc/self/%s", (char*)a1));
|
|
} else if (a0 == -1) {
|
|
NONFAILING(snprintf(buf, sizeof(buf), "/proc/thread-self/%s", (char*)a1));
|
|
} else {
|
|
NONFAILING(snprintf(buf, sizeof(buf), "/proc/self/task/%d/%s", (int)a0, (char*)a1));
|
|
}
|
|
int fd = open(buf, O_RDWR);
|
|
if (fd == -1)
|
|
fd = open(buf, O_RDONLY);
|
|
return fd;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_open_pts
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static long syz_open_pts(volatile long a0, volatile long a1)
|
|
{
|
|
int ptyno = 0;
|
|
if (ioctl(a0, TIOCGPTN, &ptyno))
|
|
return -1;
|
|
char buf[128];
|
|
sprintf(buf, "/dev/pts/%d", ptyno);
|
|
return open(buf, a1, 0);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_init_net_socket
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
|
|
#include <fcntl.h>
|
|
#include <sched.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
const int kInitNetNsFd = 239;
|
|
static long syz_init_net_socket(volatile long domain, volatile long type, volatile long proto)
|
|
{
|
|
int netns = open("/proc/self/ns/net", O_RDONLY);
|
|
if (netns == -1)
|
|
return netns;
|
|
if (setns(kInitNetNsFd, 0))
|
|
return -1;
|
|
int sock = syscall(__NR_socket, domain, type, proto);
|
|
int err = errno;
|
|
if (setns(netns, 0))
|
|
fail("setns(netns) failed");
|
|
close(netns);
|
|
errno = err;
|
|
return sock;
|
|
}
|
|
#else
|
|
static long syz_init_net_socket(volatile long domain, volatile long type, volatile long proto)
|
|
{
|
|
return syscall(__NR_socket, domain, type, proto);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_genetlink_get_family_id
|
|
#include <errno.h>
|
|
#include <linux/genetlink.h>
|
|
#include <linux/netlink.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
static long syz_genetlink_get_family_id(volatile long name)
|
|
{
|
|
char buf[512] = {0};
|
|
struct nlmsghdr* hdr = (struct nlmsghdr*)buf;
|
|
struct genlmsghdr* genlhdr = (struct genlmsghdr*)NLMSG_DATA(hdr);
|
|
struct nlattr* attr = (struct nlattr*)(genlhdr + 1);
|
|
hdr->nlmsg_len = sizeof(*hdr) + sizeof(*genlhdr) + sizeof(*attr) + GENL_NAMSIZ;
|
|
hdr->nlmsg_type = GENL_ID_CTRL;
|
|
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
genlhdr->cmd = CTRL_CMD_GETFAMILY;
|
|
attr->nla_type = CTRL_ATTR_FAMILY_NAME;
|
|
attr->nla_len = sizeof(*attr) + GENL_NAMSIZ;
|
|
NONFAILING(strncpy((char*)(attr + 1), (char*)name, GENL_NAMSIZ));
|
|
struct iovec iov = {hdr, hdr->nlmsg_len};
|
|
struct sockaddr_nl addr = {0};
|
|
addr.nl_family = AF_NETLINK;
|
|
debug("syz_genetlink_get_family_id(%s)\n", (char*)(attr + 1));
|
|
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
|
|
if (fd == -1) {
|
|
debug("syz_genetlink_get_family_id: socket failed: %d\n", errno);
|
|
return -1;
|
|
}
|
|
struct msghdr msg = {&addr, sizeof(addr), &iov, 1, NULL, 0, 0};
|
|
if (sendmsg(fd, &msg, 0) == -1) {
|
|
debug("syz_genetlink_get_family_id: sendmsg failed: %d\n", errno);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
ssize_t n = recv(fd, buf, sizeof(buf), 0);
|
|
close(fd);
|
|
if (n <= 0) {
|
|
debug("syz_genetlink_get_family_id: recv failed: %d\n", errno);
|
|
return -1;
|
|
}
|
|
if (hdr->nlmsg_type != GENL_ID_CTRL) {
|
|
debug("syz_genetlink_get_family_id: wrong reply type: %d\n", hdr->nlmsg_type);
|
|
return -1;
|
|
}
|
|
for (; (char*)attr < buf + n; attr = (struct nlattr*)((char*)attr + NLMSG_ALIGN(attr->nla_len))) {
|
|
if (attr->nla_type == CTRL_ATTR_FAMILY_ID)
|
|
return *(uint16*)(attr + 1);
|
|
}
|
|
debug("syz_genetlink_get_family_id: no CTRL_ATTR_FAMILY_ID attr\n");
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_mount_image || __NR_syz_read_part_table
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/loop.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
struct fs_image_segment {
|
|
void* data;
|
|
uintptr_t size;
|
|
uintptr_t offset;
|
|
};
|
|
|
|
#define IMAGE_MAX_SEGMENTS 4096
|
|
#define IMAGE_MAX_SIZE (129 << 20)
|
|
|
|
#if GOARCH_386
|
|
#define SYZ_memfd_create 356
|
|
#elif GOARCH_amd64
|
|
#define SYZ_memfd_create 319
|
|
#elif GOARCH_arm
|
|
#define SYZ_memfd_create 385
|
|
#elif GOARCH_arm64
|
|
#define SYZ_memfd_create 279
|
|
#elif GOARCH_ppc64le
|
|
#define SYZ_memfd_create 360
|
|
#endif
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_read_part_table
|
|
static long syz_read_part_table(volatile unsigned long size, volatile unsigned long nsegs, volatile long segments)
|
|
{
|
|
char loopname[64], linkname[64];
|
|
int loopfd, err = 0, res = -1;
|
|
unsigned long i, j;
|
|
struct fs_image_segment* segs = (struct fs_image_segment*)segments;
|
|
|
|
if (nsegs > IMAGE_MAX_SEGMENTS)
|
|
nsegs = IMAGE_MAX_SEGMENTS;
|
|
for (i = 0; i < nsegs; i++) {
|
|
if (segs[i].size > IMAGE_MAX_SIZE)
|
|
segs[i].size = IMAGE_MAX_SIZE;
|
|
segs[i].offset %= IMAGE_MAX_SIZE;
|
|
if (segs[i].offset > IMAGE_MAX_SIZE - segs[i].size)
|
|
segs[i].offset = IMAGE_MAX_SIZE - segs[i].size;
|
|
if (size < segs[i].offset + segs[i].offset)
|
|
size = segs[i].offset + segs[i].offset;
|
|
}
|
|
if (size > IMAGE_MAX_SIZE)
|
|
size = IMAGE_MAX_SIZE;
|
|
int memfd = syscall(SYZ_memfd_create, "syz_read_part_table", 0);
|
|
if (memfd == -1) {
|
|
err = errno;
|
|
goto error;
|
|
}
|
|
if (ftruncate(memfd, size)) {
|
|
err = errno;
|
|
goto error_close_memfd;
|
|
}
|
|
for (i = 0; i < nsegs; i++) {
|
|
if (pwrite(memfd, segs[i].data, segs[i].size, segs[i].offset) < 0) {
|
|
debug("syz_read_part_table: pwrite[%u] failed: %d\n", (int)i, errno);
|
|
}
|
|
}
|
|
snprintf(loopname, sizeof(loopname), "/dev/loop%llu", procid);
|
|
loopfd = open(loopname, O_RDWR);
|
|
if (loopfd == -1) {
|
|
err = errno;
|
|
goto error_close_memfd;
|
|
}
|
|
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
|
|
if (errno != EBUSY) {
|
|
err = errno;
|
|
goto error_close_loop;
|
|
}
|
|
ioctl(loopfd, LOOP_CLR_FD, 0);
|
|
usleep(1000);
|
|
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
|
|
err = errno;
|
|
goto error_close_loop;
|
|
}
|
|
}
|
|
struct loop_info64 info;
|
|
if (ioctl(loopfd, LOOP_GET_STATUS64, &info)) {
|
|
err = errno;
|
|
goto error_clear_loop;
|
|
}
|
|
#if SYZ_EXECUTOR
|
|
cover_reset(0);
|
|
#endif
|
|
info.lo_flags |= LO_FLAGS_PARTSCAN;
|
|
if (ioctl(loopfd, LOOP_SET_STATUS64, &info)) {
|
|
err = errno;
|
|
goto error_clear_loop;
|
|
}
|
|
res = 0;
|
|
for (i = 1, j = 0; i < 8; i++) {
|
|
snprintf(loopname, sizeof(loopname), "/dev/loop%llup%d", procid, (int)i);
|
|
struct stat statbuf;
|
|
if (stat(loopname, &statbuf) == 0) {
|
|
snprintf(linkname, sizeof(linkname), "./file%d", (int)j++);
|
|
if (symlink(loopname, linkname)) {
|
|
debug("syz_read_part_table: symlink(%s, %s) failed: %d\n", loopname, linkname, errno);
|
|
}
|
|
}
|
|
}
|
|
error_clear_loop:
|
|
ioctl(loopfd, LOOP_CLR_FD, 0);
|
|
error_close_loop:
|
|
close(loopfd);
|
|
error_close_memfd:
|
|
close(memfd);
|
|
error:
|
|
errno = err;
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_mount_image
|
|
#include <string.h>
|
|
#include <sys/mount.h>
|
|
static long syz_mount_image(volatile long fsarg, volatile long dir, volatile unsigned long size, volatile unsigned long nsegs, volatile long segments, volatile long flags, volatile long optsarg)
|
|
{
|
|
char loopname[64], fs[32], opts[256];
|
|
int loopfd, err = 0, res = -1;
|
|
unsigned long i;
|
|
struct fs_image_segment* segs = (struct fs_image_segment*)segments;
|
|
|
|
if (nsegs > IMAGE_MAX_SEGMENTS)
|
|
nsegs = IMAGE_MAX_SEGMENTS;
|
|
for (i = 0; i < nsegs; i++) {
|
|
if (segs[i].size > IMAGE_MAX_SIZE)
|
|
segs[i].size = IMAGE_MAX_SIZE;
|
|
segs[i].offset %= IMAGE_MAX_SIZE;
|
|
if (segs[i].offset > IMAGE_MAX_SIZE - segs[i].size)
|
|
segs[i].offset = IMAGE_MAX_SIZE - segs[i].size;
|
|
if (size < segs[i].offset + segs[i].offset)
|
|
size = segs[i].offset + segs[i].offset;
|
|
}
|
|
if (size > IMAGE_MAX_SIZE)
|
|
size = IMAGE_MAX_SIZE;
|
|
int memfd = syscall(SYZ_memfd_create, "syz_mount_image", 0);
|
|
if (memfd == -1) {
|
|
err = errno;
|
|
goto error;
|
|
}
|
|
if (ftruncate(memfd, size)) {
|
|
err = errno;
|
|
goto error_close_memfd;
|
|
}
|
|
for (i = 0; i < nsegs; i++) {
|
|
if (pwrite(memfd, segs[i].data, segs[i].size, segs[i].offset) < 0) {
|
|
debug("syz_mount_image: pwrite[%u] failed: %d\n", (int)i, errno);
|
|
}
|
|
}
|
|
snprintf(loopname, sizeof(loopname), "/dev/loop%llu", procid);
|
|
loopfd = open(loopname, O_RDWR);
|
|
if (loopfd == -1) {
|
|
err = errno;
|
|
goto error_close_memfd;
|
|
}
|
|
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
|
|
if (errno != EBUSY) {
|
|
err = errno;
|
|
goto error_close_loop;
|
|
}
|
|
ioctl(loopfd, LOOP_CLR_FD, 0);
|
|
usleep(1000);
|
|
if (ioctl(loopfd, LOOP_SET_FD, memfd)) {
|
|
err = errno;
|
|
goto error_close_loop;
|
|
}
|
|
}
|
|
mkdir((char*)dir, 0777);
|
|
memset(fs, 0, sizeof(fs));
|
|
NONFAILING(strncpy(fs, (char*)fsarg, sizeof(fs) - 1));
|
|
memset(opts, 0, sizeof(opts));
|
|
NONFAILING(strncpy(opts, (char*)optsarg, sizeof(opts) - 32));
|
|
if (strcmp(fs, "iso9660") == 0) {
|
|
flags |= MS_RDONLY;
|
|
} else if (strncmp(fs, "ext", 3) == 0) {
|
|
if (strstr(opts, "errors=panic") || strstr(opts, "errors=remount-ro") == 0)
|
|
strcat(opts, ",errors=continue");
|
|
} else if (strcmp(fs, "xfs") == 0) {
|
|
strcat(opts, ",nouuid");
|
|
}
|
|
debug("syz_mount_image: size=%llu segs=%llu loop='%s' dir='%s' fs='%s' flags=%llu opts='%s'\n", (uint64)size, (uint64)nsegs, loopname, (char*)dir, fs, (uint64)flags, opts);
|
|
#if SYZ_EXECUTOR
|
|
cover_reset(0);
|
|
#endif
|
|
if (mount(loopname, (char*)dir, fs, flags, opts)) {
|
|
err = errno;
|
|
goto error_clear_loop;
|
|
}
|
|
res = 0;
|
|
error_clear_loop:
|
|
ioctl(loopfd, LOOP_CLR_FD, 0);
|
|
error_close_loop:
|
|
close(loopfd);
|
|
error_close_memfd:
|
|
close(memfd);
|
|
error:
|
|
errno = err;
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/kvm.h>
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
|
|
#if GOARCH_amd64
|
|
const char kvm_asm16_cpl3[] = "\x0f\x20\xc0\x66\x83\xc8\x01\x0f\x22\xc0\xb8\xa0\x00\x0f\x00\xd8\xb8\x2b\x00\x8e\xd8\x8e\xc0\x8e\xe0\x8e\xe8\xbc\x00\x01\xc7\x06\x00\x01\x1d\xba\xc7\x06\x02\x01\x23\x00\xc7\x06\x04\x01\x00\x01\xc7\x06\x06\x01\x2b\x00\xcb";
|
|
const char kvm_asm32_paged[] = "\x0f\x20\xc0\x0d\x00\x00\x00\x80\x0f\x22\xc0";
|
|
const char kvm_asm32_vm86[] = "\x66\xb8\xb8\x00\x0f\x00\xd8\xea\x00\x00\x00\x00\xd0\x00";
|
|
const char kvm_asm32_paged_vm86[] = "\x0f\x20\xc0\x0d\x00\x00\x00\x80\x0f\x22\xc0\x66\xb8\xb8\x00\x0f\x00\xd8\xea\x00\x00\x00\x00\xd0\x00";
|
|
const char kvm_asm64_enable_long[] = "\x0f\x20\xc0\x0d\x00\x00\x00\x80\x0f\x22\xc0\xea\xde\xc0\xad\x0b\x50\x00\x48\xc7\xc0\xd8\x00\x00\x00\x0f\x00\xd8";
|
|
const char kvm_asm64_init_vm[] = "\x0f\x20\xc0\x0d\x00\x00\x00\x80\x0f\x22\xc0\xea\xde\xc0\xad\x0b\x50\x00\x48\xc7\xc0\xd8\x00\x00\x00\x0f\x00\xd8\x48\xc7\xc1\x3a\x00\x00\x00\x0f\x32\x48\x83\xc8\x05\x0f\x30\x0f\x20\xe0\x48\x0d\x00\x20\x00\x00\x0f\x22\xe0\x48\xc7\xc1\x80\x04\x00\x00\x0f\x32\x48\xc7\xc2\x00\x60\x00\x00\x89\x02\x48\xc7\xc2\x00\x70\x00\x00\x89\x02\x48\xc7\xc0\x00\x5f\x00\x00\xf3\x0f\xc7\x30\x48\xc7\xc0\x08\x5f\x00\x00\x66\x0f\xc7\x30\x0f\xc7\x30\x48\xc7\xc1\x81\x04\x00\x00\x0f\x32\x48\x83\xc8\x3f\x48\x21\xd0\x48\xc7\xc2\x00\x40\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x02\x40\x00\x00\x48\xb8\x84\x9e\x99\xf3\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x1e\x40\x00\x00\x48\xc7\xc0\x81\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc1\x83\x04\x00\x00\x0f\x32\x48\x0d\xff\x6f\x03\x00\x48\x21\xd0\x48\xc7\xc2\x0c\x40\x00\x00\x0f\x79\xd0\x48\xc7\xc1\x84\x04\x00\x00\x0f\x32\x48\x0d\xff\x17\x00\x00\x48\x21\xd0\x48\xc7\xc2\x12\x40\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x04\x2c\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x00\x28\x00\x00\x48\xc7\xc0\xff\xff\xff\xff\x0f\x79\xd0\x48\xc7\xc2\x02\x0c\x00\x00\x48\xc7\xc0\x50\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc0\x58\x00\x00\x00\x48\xc7\xc2\x00\x0c\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x04\x0c\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x06\x0c\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x08\x0c\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0a\x0c\x00\x00\x0f\x79\xd0\x48\xc7\xc0\xd8\x00\x00\x00\x48\xc7\xc2\x0c\x0c\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x02\x2c\x00\x00\x48\xc7\xc0\x00\x05\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x00\x4c\x00\x00\x48\xc7\xc0\x50\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x10\x6c\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x12\x6c\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x0f\x20\xc0\x48\xc7\xc2\x00\x6c\x00\x00\x48\x89\xc0\x0f\x79\xd0\x0f\x20\xd8\x48\xc7\xc2\x02\x6c\x00\x00\x48\x89\xc0\x0f\x79\xd0\x0f\x20\xe0\x48\xc7\xc2\x04\x6c\x00\x00\x48\x89\xc0\x0f\x79\xd0\x48\xc7\xc2\x06\x6c\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x08\x6c\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0a\x6c\x00\x00\x48\xc7\xc0\x00\x3a\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0c\x6c\x00\x00\x48\xc7\xc0\x00\x10\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0e\x6c\x00\x00\x48\xc7\xc0\x00\x38\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x14\x6c\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x16\x6c\x00\x00\x48\x8b\x04\x25\x10\x5f\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x00\x00\x00\x00\x48\xc7\xc0\x01\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x02\x00\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x00\x20\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x02\x20\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x04\x20\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x06\x20\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc1\x77\x02\x00\x00\x0f\x32\x48\xc1\xe2\x20\x48\x09\xd0\x48\xc7\xc2\x00\x2c\x00\x00\x48\x89\xc0\x0f\x79\xd0\x48\xc7\xc2\x04\x40\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0a\x40\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0e\x40\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x10\x40\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x16\x40\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x14\x40\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x00\x60\x00\x00\x48\xc7\xc0\xff\xff\xff\xff\x0f\x79\xd0\x48\xc7\xc2\x02\x60\x00\x00\x48\xc7\xc0\xff\xff\xff\xff\x0f\x79\xd0\x48\xc7\xc2\x1c\x20\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x1e\x20\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x20\x20\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x22\x20\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x00\x08\x00\x00\x48\xc7\xc0\x58\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x02\x08\x00\x00\x48\xc7\xc0\x50\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x04\x08\x00\x00\x48\xc7\xc0\x58\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x06\x08\x00\x00\x48\xc7\xc0\x58\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x08\x08\x00\x00\x48\xc7\xc0\x58\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0a\x08\x00\x00\x48\xc7\xc0\x58\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0c\x08\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0e\x08\x00\x00\x48\xc7\xc0\xd8\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x12\x68\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x14\x68\x00\x00\x48\xc7\xc0\x00\x3a\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x16\x68\x00\x00\x48\xc7\xc0\x00\x10\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x18\x68\x00\x00\x48\xc7\xc0\x00\x38\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x00\x48\x00\x00\x48\xc7\xc0\xff\xff\x0f\x00\x0f\x79\xd0\x48\xc7\xc2\x02\x48\x00\x00\x48\xc7\xc0\xff\xff\x0f\x00\x0f\x79\xd0\x48\xc7\xc2\x04\x48\x00\x00\x48\xc7\xc0\xff\xff\x0f\x00\x0f\x79\xd0\x48\xc7\xc2\x06\x48\x00\x00\x48\xc7\xc0\xff\xff\x0f\x00\x0f\x79\xd0\x48\xc7\xc2\x08\x48\x00\x00\x48\xc7\xc0\xff\xff\x0f\x00\x0f\x79\xd0\x48\xc7\xc2\x0a\x48\x00\x00\x48\xc7\xc0\xff\xff\x0f\x00\x0f\x79\xd0\x48\xc7\xc2\x0c\x48\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0e\x48\x00\x00\x48\xc7\xc0\xff\x1f\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x10\x48\x00\x00\x48\xc7\xc0\xff\x1f\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x12\x48\x00\x00\x48\xc7\xc0\xff\x1f\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x14\x48\x00\x00\x48\xc7\xc0\x93\x40\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x16\x48\x00\x00\x48\xc7\xc0\x9b\x20\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x18\x48\x00\x00\x48\xc7\xc0\x93\x40\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x1a\x48\x00\x00\x48\xc7\xc0\x93\x40\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x1c\x48\x00\x00\x48\xc7\xc0\x93\x40\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x1e\x48\x00\x00\x48\xc7\xc0\x93\x40\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x20\x48\x00\x00\x48\xc7\xc0\x82\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x22\x48\x00\x00\x48\xc7\xc0\x8b\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x1c\x68\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x1e\x68\x00\x00\x48\xc7\xc0\x00\x91\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x20\x68\x00\x00\x48\xc7\xc0\x02\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x06\x28\x00\x00\x48\xc7\xc0\x00\x05\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0a\x28\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0c\x28\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x0e\x28\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x48\xc7\xc2\x10\x28\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x79\xd0\x0f\x20\xc0\x48\xc7\xc2\x00\x68\x00\x00\x48\x89\xc0\x0f\x79\xd0\x0f\x20\xd8\x48\xc7\xc2\x02\x68\x00\x00\x48\x89\xc0\x0f\x79\xd0\x0f\x20\xe0\x48\xc7\xc2\x04\x68\x00\x00\x48\x89\xc0\x0f\x79\xd0\x48\xc7\xc0\x18\x5f\x00\x00\x48\x8b\x10\x48\xc7\xc0\x20\x5f\x00\x00\x48\x8b\x08\x48\x31\xc0\x0f\x78\xd0\x48\x31\xc8\x0f\x79\xd0\x0f\x01\xc2\x48\xc7\xc2\x00\x44\x00\x00\x0f\x78\xd0\xf4";
|
|
const char kvm_asm64_vm_exit[] = "\x48\xc7\xc3\x00\x44\x00\x00\x0f\x78\xda\x48\xc7\xc3\x02\x44\x00\x00\x0f\x78\xd9\x48\xc7\xc0\x00\x64\x00\x00\x0f\x78\xc0\x48\xc7\xc3\x1e\x68\x00\x00\x0f\x78\xdb\xf4";
|
|
const char kvm_asm64_cpl3[] = "\x0f\x20\xc0\x0d\x00\x00\x00\x80\x0f\x22\xc0\xea\xde\xc0\xad\x0b\x50\x00\x48\xc7\xc0\xd8\x00\x00\x00\x0f\x00\xd8\x48\xc7\xc0\x6b\x00\x00\x00\x8e\xd8\x8e\xc0\x8e\xe0\x8e\xe8\x48\xc7\xc4\x80\x0f\x00\x00\x48\xc7\x04\x24\x1d\xba\x00\x00\x48\xc7\x44\x24\x04\x63\x00\x00\x00\x48\xc7\x44\x24\x08\x80\x0f\x00\x00\x48\xc7\x44\x24\x0c\x6b\x00\x00\x00\xcb";
|
|
|
|
#define ADDR_TEXT 0x0000
|
|
#define ADDR_GDT 0x1000
|
|
#define ADDR_LDT 0x1800
|
|
#define ADDR_PML4 0x2000
|
|
#define ADDR_PDP 0x3000
|
|
#define ADDR_PD 0x4000
|
|
#define ADDR_STACK0 0x0f80
|
|
#define ADDR_VAR_HLT 0x2800
|
|
#define ADDR_VAR_SYSRET 0x2808
|
|
#define ADDR_VAR_SYSEXIT 0x2810
|
|
#define ADDR_VAR_IDT 0x3800
|
|
#define ADDR_VAR_TSS64 0x3a00
|
|
#define ADDR_VAR_TSS64_CPL3 0x3c00
|
|
#define ADDR_VAR_TSS16 0x3d00
|
|
#define ADDR_VAR_TSS16_2 0x3e00
|
|
#define ADDR_VAR_TSS16_CPL3 0x3f00
|
|
#define ADDR_VAR_TSS32 0x4800
|
|
#define ADDR_VAR_TSS32_2 0x4a00
|
|
#define ADDR_VAR_TSS32_CPL3 0x4c00
|
|
#define ADDR_VAR_TSS32_VM86 0x4e00
|
|
#define ADDR_VAR_VMXON_PTR 0x5f00
|
|
#define ADDR_VAR_VMCS_PTR 0x5f08
|
|
#define ADDR_VAR_VMEXIT_PTR 0x5f10
|
|
#define ADDR_VAR_VMWRITE_FLD 0x5f18
|
|
#define ADDR_VAR_VMWRITE_VAL 0x5f20
|
|
#define ADDR_VAR_VMXON 0x6000
|
|
#define ADDR_VAR_VMCS 0x7000
|
|
#define ADDR_VAR_VMEXIT_CODE 0x9000
|
|
#define ADDR_VAR_USER_CODE 0x9100
|
|
#define ADDR_VAR_USER_CODE2 0x9120
|
|
|
|
#define SEL_LDT (1 << 3)
|
|
#define SEL_CS16 (2 << 3)
|
|
#define SEL_DS16 (3 << 3)
|
|
#define SEL_CS16_CPL3 ((4 << 3) + 3)
|
|
#define SEL_DS16_CPL3 ((5 << 3) + 3)
|
|
#define SEL_CS32 (6 << 3)
|
|
#define SEL_DS32 (7 << 3)
|
|
#define SEL_CS32_CPL3 ((8 << 3) + 3)
|
|
#define SEL_DS32_CPL3 ((9 << 3) + 3)
|
|
#define SEL_CS64 (10 << 3)
|
|
#define SEL_DS64 (11 << 3)
|
|
#define SEL_CS64_CPL3 ((12 << 3) + 3)
|
|
#define SEL_DS64_CPL3 ((13 << 3) + 3)
|
|
#define SEL_CGATE16 (14 << 3)
|
|
#define SEL_TGATE16 (15 << 3)
|
|
#define SEL_CGATE32 (16 << 3)
|
|
#define SEL_TGATE32 (17 << 3)
|
|
#define SEL_CGATE64 (18 << 3)
|
|
#define SEL_CGATE64_HI (19 << 3)
|
|
#define SEL_TSS16 (20 << 3)
|
|
#define SEL_TSS16_2 (21 << 3)
|
|
#define SEL_TSS16_CPL3 ((22 << 3) + 3)
|
|
#define SEL_TSS32 (23 << 3)
|
|
#define SEL_TSS32_2 (24 << 3)
|
|
#define SEL_TSS32_CPL3 ((25 << 3) + 3)
|
|
#define SEL_TSS32_VM86 (26 << 3)
|
|
#define SEL_TSS64 (27 << 3)
|
|
#define SEL_TSS64_HI (28 << 3)
|
|
#define SEL_TSS64_CPL3 ((29 << 3) + 3)
|
|
#define SEL_TSS64_CPL3_HI (30 << 3)
|
|
|
|
#define MSR_IA32_FEATURE_CONTROL 0x3a
|
|
#define MSR_IA32_VMX_BASIC 0x480
|
|
#define MSR_IA32_SMBASE 0x9e
|
|
#define MSR_IA32_SYSENTER_CS 0x174
|
|
#define MSR_IA32_SYSENTER_ESP 0x175
|
|
#define MSR_IA32_SYSENTER_EIP 0x176
|
|
#define MSR_IA32_STAR 0xC0000081
|
|
#define MSR_IA32_LSTAR 0xC0000082
|
|
#define MSR_IA32_VMX_PROCBASED_CTLS2 0x48B
|
|
|
|
#define NEXT_INSN $0xbadc0de
|
|
#define PREFIX_SIZE 0xba1d
|
|
|
|
|
|
#ifndef KVM_SMI
|
|
#define KVM_SMI _IO(KVMIO, 0xb7)
|
|
#endif
|
|
|
|
#define CR0_PE 1
|
|
#define CR0_MP (1 << 1)
|
|
#define CR0_EM (1 << 2)
|
|
#define CR0_TS (1 << 3)
|
|
#define CR0_ET (1 << 4)
|
|
#define CR0_NE (1 << 5)
|
|
#define CR0_WP (1 << 16)
|
|
#define CR0_AM (1 << 18)
|
|
#define CR0_NW (1 << 29)
|
|
#define CR0_CD (1 << 30)
|
|
#define CR0_PG (1 << 31)
|
|
|
|
#define CR4_VME 1
|
|
#define CR4_PVI (1 << 1)
|
|
#define CR4_TSD (1 << 2)
|
|
#define CR4_DE (1 << 3)
|
|
#define CR4_PSE (1 << 4)
|
|
#define CR4_PAE (1 << 5)
|
|
#define CR4_MCE (1 << 6)
|
|
#define CR4_PGE (1 << 7)
|
|
#define CR4_PCE (1 << 8)
|
|
#define CR4_OSFXSR (1 << 8)
|
|
#define CR4_OSXMMEXCPT (1 << 10)
|
|
#define CR4_UMIP (1 << 11)
|
|
#define CR4_VMXE (1 << 13)
|
|
#define CR4_SMXE (1 << 14)
|
|
#define CR4_FSGSBASE (1 << 16)
|
|
#define CR4_PCIDE (1 << 17)
|
|
#define CR4_OSXSAVE (1 << 18)
|
|
#define CR4_SMEP (1 << 20)
|
|
#define CR4_SMAP (1 << 21)
|
|
#define CR4_PKE (1 << 22)
|
|
|
|
#define EFER_SCE 1
|
|
#define EFER_LME (1 << 8)
|
|
#define EFER_LMA (1 << 10)
|
|
#define EFER_NXE (1 << 11)
|
|
#define EFER_SVME (1 << 12)
|
|
#define EFER_LMSLE (1 << 13)
|
|
#define EFER_FFXSR (1 << 14)
|
|
#define EFER_TCE (1 << 15)
|
|
#define PDE32_PRESENT 1
|
|
#define PDE32_RW (1 << 1)
|
|
#define PDE32_USER (1 << 2)
|
|
#define PDE32_PS (1 << 7)
|
|
#define PDE64_PRESENT 1
|
|
#define PDE64_RW (1 << 1)
|
|
#define PDE64_USER (1 << 2)
|
|
#define PDE64_ACCESSED (1 << 5)
|
|
#define PDE64_DIRTY (1 << 6)
|
|
#define PDE64_PS (1 << 7)
|
|
#define PDE64_G (1 << 8)
|
|
|
|
struct tss16 {
|
|
uint16 prev;
|
|
uint16 sp0;
|
|
uint16 ss0;
|
|
uint16 sp1;
|
|
uint16 ss1;
|
|
uint16 sp2;
|
|
uint16 ss2;
|
|
uint16 ip;
|
|
uint16 flags;
|
|
uint16 ax;
|
|
uint16 cx;
|
|
uint16 dx;
|
|
uint16 bx;
|
|
uint16 sp;
|
|
uint16 bp;
|
|
uint16 si;
|
|
uint16 di;
|
|
uint16 es;
|
|
uint16 cs;
|
|
uint16 ss;
|
|
uint16 ds;
|
|
uint16 ldt;
|
|
} __attribute__((packed));
|
|
|
|
struct tss32 {
|
|
uint16 prev, prevh;
|
|
uint32 sp0;
|
|
uint16 ss0, ss0h;
|
|
uint32 sp1;
|
|
uint16 ss1, ss1h;
|
|
uint32 sp2;
|
|
uint16 ss2, ss2h;
|
|
uint32 cr3;
|
|
uint32 ip;
|
|
uint32 flags;
|
|
uint32 ax;
|
|
uint32 cx;
|
|
uint32 dx;
|
|
uint32 bx;
|
|
uint32 sp;
|
|
uint32 bp;
|
|
uint32 si;
|
|
uint32 di;
|
|
uint16 es, esh;
|
|
uint16 cs, csh;
|
|
uint16 ss, ssh;
|
|
uint16 ds, dsh;
|
|
uint16 fs, fsh;
|
|
uint16 gs, gsh;
|
|
uint16 ldt, ldth;
|
|
uint16 trace;
|
|
uint16 io_bitmap;
|
|
} __attribute__((packed));
|
|
|
|
struct tss64 {
|
|
uint32 reserved0;
|
|
uint64 rsp[3];
|
|
uint64 reserved1;
|
|
uint64 ist[7];
|
|
uint64 reserved2;
|
|
uint32 reserved3;
|
|
uint32 io_bitmap;
|
|
} __attribute__((packed));
|
|
|
|
static void fill_segment_descriptor(uint64* dt, uint64* lt, struct kvm_segment* seg)
|
|
{
|
|
uint16 index = seg->selector >> 3;
|
|
uint64 limit = seg->g ? seg->limit >> 12 : seg->limit;
|
|
uint64 sd = (limit & 0xffff) | (seg->base & 0xffffff) << 16 | (uint64)seg->type << 40 | (uint64)seg->s << 44 | (uint64)seg->dpl << 45 | (uint64)seg->present << 47 | (limit & 0xf0000ULL) << 48 | (uint64)seg->avl << 52 | (uint64)seg->l << 53 | (uint64)seg->db << 54 | (uint64)seg->g << 55 | (seg->base & 0xff000000ULL) << 56;
|
|
NONFAILING(dt[index] = sd);
|
|
NONFAILING(lt[index] = sd);
|
|
}
|
|
|
|
static void fill_segment_descriptor_dword(uint64* dt, uint64* lt, struct kvm_segment* seg)
|
|
{
|
|
fill_segment_descriptor(dt, lt, seg);
|
|
uint16 index = seg->selector >> 3;
|
|
NONFAILING(dt[index + 1] = 0);
|
|
NONFAILING(lt[index + 1] = 0);
|
|
}
|
|
|
|
static void setup_syscall_msrs(int cpufd, uint16 sel_cs, uint16 sel_cs_cpl3)
|
|
{
|
|
char buf[sizeof(struct kvm_msrs) + 5 * sizeof(struct kvm_msr_entry)];
|
|
memset(buf, 0, sizeof(buf));
|
|
struct kvm_msrs* msrs = (struct kvm_msrs*)buf;
|
|
struct kvm_msr_entry* entries = msrs->entries;
|
|
msrs->nmsrs = 5;
|
|
entries[0].index = MSR_IA32_SYSENTER_CS;
|
|
entries[0].data = sel_cs;
|
|
entries[1].index = MSR_IA32_SYSENTER_ESP;
|
|
entries[1].data = ADDR_STACK0;
|
|
entries[2].index = MSR_IA32_SYSENTER_EIP;
|
|
entries[2].data = ADDR_VAR_SYSEXIT;
|
|
entries[3].index = MSR_IA32_STAR;
|
|
entries[3].data = ((uint64)sel_cs << 32) | ((uint64)sel_cs_cpl3 << 48);
|
|
entries[4].index = MSR_IA32_LSTAR;
|
|
entries[4].data = ADDR_VAR_SYSRET;
|
|
ioctl(cpufd, KVM_SET_MSRS, msrs);
|
|
}
|
|
|
|
static void setup_32bit_idt(struct kvm_sregs* sregs, char* host_mem, uintptr_t guest_mem)
|
|
{
|
|
sregs->idt.base = guest_mem + ADDR_VAR_IDT;
|
|
sregs->idt.limit = 0x1ff;
|
|
uint64* idt = (uint64*)(host_mem + sregs->idt.base);
|
|
int i;
|
|
for (i = 0; i < 32; i++) {
|
|
struct kvm_segment gate;
|
|
gate.selector = i << 3;
|
|
switch (i % 6) {
|
|
case 0:
|
|
gate.type = 6;
|
|
gate.base = SEL_CS16;
|
|
break;
|
|
case 1:
|
|
gate.type = 7;
|
|
gate.base = SEL_CS16;
|
|
break;
|
|
case 2:
|
|
gate.type = 3;
|
|
gate.base = SEL_TGATE16;
|
|
break;
|
|
case 3:
|
|
gate.type = 14;
|
|
gate.base = SEL_CS32;
|
|
break;
|
|
case 4:
|
|
gate.type = 15;
|
|
gate.base = SEL_CS32;
|
|
break;
|
|
case 6:
|
|
gate.type = 11;
|
|
gate.base = SEL_TGATE32;
|
|
break;
|
|
}
|
|
gate.limit = guest_mem + ADDR_VAR_USER_CODE2;
|
|
gate.present = 1;
|
|
gate.dpl = 0;
|
|
gate.s = 0;
|
|
gate.g = 0;
|
|
gate.db = 0;
|
|
gate.l = 0;
|
|
gate.avl = 0;
|
|
fill_segment_descriptor(idt, idt, &gate);
|
|
}
|
|
}
|
|
|
|
static void setup_64bit_idt(struct kvm_sregs* sregs, char* host_mem, uintptr_t guest_mem)
|
|
{
|
|
sregs->idt.base = guest_mem + ADDR_VAR_IDT;
|
|
sregs->idt.limit = 0x1ff;
|
|
uint64* idt = (uint64*)(host_mem + sregs->idt.base);
|
|
int i;
|
|
for (i = 0; i < 32; i++) {
|
|
struct kvm_segment gate;
|
|
gate.selector = (i * 2) << 3;
|
|
gate.type = (i & 1) ? 14 : 15;
|
|
gate.base = SEL_CS64;
|
|
gate.limit = guest_mem + ADDR_VAR_USER_CODE2;
|
|
gate.present = 1;
|
|
gate.dpl = 0;
|
|
gate.s = 0;
|
|
gate.g = 0;
|
|
gate.db = 0;
|
|
gate.l = 0;
|
|
gate.avl = 0;
|
|
fill_segment_descriptor_dword(idt, idt, &gate);
|
|
}
|
|
}
|
|
|
|
struct kvm_text {
|
|
uintptr_t typ;
|
|
const void* text;
|
|
uintptr_t size;
|
|
};
|
|
|
|
struct kvm_opt {
|
|
uint64 typ;
|
|
uint64 val;
|
|
};
|
|
|
|
#define KVM_SETUP_PAGING (1 << 0)
|
|
#define KVM_SETUP_PAE (1 << 1)
|
|
#define KVM_SETUP_PROTECTED (1 << 2)
|
|
#define KVM_SETUP_CPL3 (1 << 3)
|
|
#define KVM_SETUP_VIRT86 (1 << 4)
|
|
#define KVM_SETUP_SMM (1 << 5)
|
|
#define KVM_SETUP_VM (1 << 6)
|
|
static long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volatile long a2, volatile long a3, volatile long a4, volatile long a5, volatile long a6, volatile long a7)
|
|
{
|
|
const int vmfd = a0;
|
|
const int cpufd = a1;
|
|
char* const host_mem = (char*)a2;
|
|
const struct kvm_text* const text_array_ptr = (struct kvm_text*)a3;
|
|
const uintptr_t text_count = a4;
|
|
const uintptr_t flags = a5;
|
|
const struct kvm_opt* const opt_array_ptr = (struct kvm_opt*)a6;
|
|
uintptr_t opt_count = a7;
|
|
|
|
const uintptr_t page_size = 4 << 10;
|
|
const uintptr_t ioapic_page = 10;
|
|
const uintptr_t guest_mem_size = 24 * page_size;
|
|
const uintptr_t guest_mem = 0;
|
|
|
|
(void)text_count;
|
|
int text_type = 0;
|
|
const void* text = 0;
|
|
uintptr_t text_size = 0;
|
|
NONFAILING(text_type = text_array_ptr[0].typ);
|
|
NONFAILING(text = text_array_ptr[0].text);
|
|
NONFAILING(text_size = text_array_ptr[0].size);
|
|
|
|
uintptr_t i;
|
|
for (i = 0; i < guest_mem_size / page_size; i++) {
|
|
struct kvm_userspace_memory_region memreg;
|
|
memreg.slot = i;
|
|
memreg.flags = 0;
|
|
memreg.guest_phys_addr = guest_mem + i * page_size;
|
|
if (i == ioapic_page)
|
|
memreg.guest_phys_addr = 0xfec00000;
|
|
memreg.memory_size = page_size;
|
|
memreg.userspace_addr = (uintptr_t)host_mem + i * page_size;
|
|
ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg);
|
|
}
|
|
struct kvm_userspace_memory_region memreg;
|
|
memreg.slot = 1 + (1 << 16);
|
|
memreg.flags = 0;
|
|
memreg.guest_phys_addr = 0x30000;
|
|
memreg.memory_size = 64 << 10;
|
|
memreg.userspace_addr = (uintptr_t)host_mem;
|
|
ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg);
|
|
|
|
struct kvm_sregs sregs;
|
|
if (ioctl(cpufd, KVM_GET_SREGS, &sregs))
|
|
return -1;
|
|
|
|
struct kvm_regs regs;
|
|
memset(®s, 0, sizeof(regs));
|
|
regs.rip = guest_mem + ADDR_TEXT;
|
|
regs.rsp = ADDR_STACK0;
|
|
|
|
sregs.gdt.base = guest_mem + ADDR_GDT;
|
|
sregs.gdt.limit = 256 * sizeof(uint64) - 1;
|
|
uint64* gdt = (uint64*)(host_mem + sregs.gdt.base);
|
|
|
|
struct kvm_segment seg_ldt;
|
|
seg_ldt.selector = SEL_LDT;
|
|
seg_ldt.type = 2;
|
|
seg_ldt.base = guest_mem + ADDR_LDT;
|
|
seg_ldt.limit = 256 * sizeof(uint64) - 1;
|
|
seg_ldt.present = 1;
|
|
seg_ldt.dpl = 0;
|
|
seg_ldt.s = 0;
|
|
seg_ldt.g = 0;
|
|
seg_ldt.db = 1;
|
|
seg_ldt.l = 0;
|
|
sregs.ldt = seg_ldt;
|
|
uint64* ldt = (uint64*)(host_mem + sregs.ldt.base);
|
|
|
|
struct kvm_segment seg_cs16;
|
|
seg_cs16.selector = SEL_CS16;
|
|
seg_cs16.type = 11;
|
|
seg_cs16.base = 0;
|
|
seg_cs16.limit = 0xfffff;
|
|
seg_cs16.present = 1;
|
|
seg_cs16.dpl = 0;
|
|
seg_cs16.s = 1;
|
|
seg_cs16.g = 0;
|
|
seg_cs16.db = 0;
|
|
seg_cs16.l = 0;
|
|
|
|
struct kvm_segment seg_ds16 = seg_cs16;
|
|
seg_ds16.selector = SEL_DS16;
|
|
seg_ds16.type = 3;
|
|
|
|
struct kvm_segment seg_cs16_cpl3 = seg_cs16;
|
|
seg_cs16_cpl3.selector = SEL_CS16_CPL3;
|
|
seg_cs16_cpl3.dpl = 3;
|
|
|
|
struct kvm_segment seg_ds16_cpl3 = seg_ds16;
|
|
seg_ds16_cpl3.selector = SEL_DS16_CPL3;
|
|
seg_ds16_cpl3.dpl = 3;
|
|
|
|
struct kvm_segment seg_cs32 = seg_cs16;
|
|
seg_cs32.selector = SEL_CS32;
|
|
seg_cs32.db = 1;
|
|
|
|
struct kvm_segment seg_ds32 = seg_ds16;
|
|
seg_ds32.selector = SEL_DS32;
|
|
seg_ds32.db = 1;
|
|
|
|
struct kvm_segment seg_cs32_cpl3 = seg_cs32;
|
|
seg_cs32_cpl3.selector = SEL_CS32_CPL3;
|
|
seg_cs32_cpl3.dpl = 3;
|
|
|
|
struct kvm_segment seg_ds32_cpl3 = seg_ds32;
|
|
seg_ds32_cpl3.selector = SEL_DS32_CPL3;
|
|
seg_ds32_cpl3.dpl = 3;
|
|
|
|
struct kvm_segment seg_cs64 = seg_cs16;
|
|
seg_cs64.selector = SEL_CS64;
|
|
seg_cs64.l = 1;
|
|
|
|
struct kvm_segment seg_ds64 = seg_ds32;
|
|
seg_ds64.selector = SEL_DS64;
|
|
|
|
struct kvm_segment seg_cs64_cpl3 = seg_cs64;
|
|
seg_cs64_cpl3.selector = SEL_CS64_CPL3;
|
|
seg_cs64_cpl3.dpl = 3;
|
|
|
|
struct kvm_segment seg_ds64_cpl3 = seg_ds64;
|
|
seg_ds64_cpl3.selector = SEL_DS64_CPL3;
|
|
seg_ds64_cpl3.dpl = 3;
|
|
|
|
struct kvm_segment seg_tss32;
|
|
seg_tss32.selector = SEL_TSS32;
|
|
seg_tss32.type = 9;
|
|
seg_tss32.base = ADDR_VAR_TSS32;
|
|
seg_tss32.limit = 0x1ff;
|
|
seg_tss32.present = 1;
|
|
seg_tss32.dpl = 0;
|
|
seg_tss32.s = 0;
|
|
seg_tss32.g = 0;
|
|
seg_tss32.db = 0;
|
|
seg_tss32.l = 0;
|
|
|
|
struct kvm_segment seg_tss32_2 = seg_tss32;
|
|
seg_tss32_2.selector = SEL_TSS32_2;
|
|
seg_tss32_2.base = ADDR_VAR_TSS32_2;
|
|
|
|
struct kvm_segment seg_tss32_cpl3 = seg_tss32;
|
|
seg_tss32_cpl3.selector = SEL_TSS32_CPL3;
|
|
seg_tss32_cpl3.base = ADDR_VAR_TSS32_CPL3;
|
|
|
|
struct kvm_segment seg_tss32_vm86 = seg_tss32;
|
|
seg_tss32_vm86.selector = SEL_TSS32_VM86;
|
|
seg_tss32_vm86.base = ADDR_VAR_TSS32_VM86;
|
|
|
|
struct kvm_segment seg_tss16 = seg_tss32;
|
|
seg_tss16.selector = SEL_TSS16;
|
|
seg_tss16.base = ADDR_VAR_TSS16;
|
|
seg_tss16.limit = 0xff;
|
|
seg_tss16.type = 1;
|
|
|
|
struct kvm_segment seg_tss16_2 = seg_tss16;
|
|
seg_tss16_2.selector = SEL_TSS16_2;
|
|
seg_tss16_2.base = ADDR_VAR_TSS16_2;
|
|
seg_tss16_2.dpl = 0;
|
|
|
|
struct kvm_segment seg_tss16_cpl3 = seg_tss16;
|
|
seg_tss16_cpl3.selector = SEL_TSS16_CPL3;
|
|
seg_tss16_cpl3.base = ADDR_VAR_TSS16_CPL3;
|
|
seg_tss16_cpl3.dpl = 3;
|
|
|
|
struct kvm_segment seg_tss64 = seg_tss32;
|
|
seg_tss64.selector = SEL_TSS64;
|
|
seg_tss64.base = ADDR_VAR_TSS64;
|
|
seg_tss64.limit = 0x1ff;
|
|
|
|
struct kvm_segment seg_tss64_cpl3 = seg_tss64;
|
|
seg_tss64_cpl3.selector = SEL_TSS64_CPL3;
|
|
seg_tss64_cpl3.base = ADDR_VAR_TSS64_CPL3;
|
|
seg_tss64_cpl3.dpl = 3;
|
|
|
|
struct kvm_segment seg_cgate16;
|
|
seg_cgate16.selector = SEL_CGATE16;
|
|
seg_cgate16.type = 4;
|
|
seg_cgate16.base = SEL_CS16 | (2 << 16);
|
|
seg_cgate16.limit = ADDR_VAR_USER_CODE2;
|
|
seg_cgate16.present = 1;
|
|
seg_cgate16.dpl = 0;
|
|
seg_cgate16.s = 0;
|
|
seg_cgate16.g = 0;
|
|
seg_cgate16.db = 0;
|
|
seg_cgate16.l = 0;
|
|
seg_cgate16.avl = 0;
|
|
|
|
struct kvm_segment seg_tgate16 = seg_cgate16;
|
|
seg_tgate16.selector = SEL_TGATE16;
|
|
seg_tgate16.type = 3;
|
|
seg_cgate16.base = SEL_TSS16_2;
|
|
seg_tgate16.limit = 0;
|
|
|
|
struct kvm_segment seg_cgate32 = seg_cgate16;
|
|
seg_cgate32.selector = SEL_CGATE32;
|
|
seg_cgate32.type = 12;
|
|
seg_cgate32.base = SEL_CS32 | (2 << 16);
|
|
|
|
struct kvm_segment seg_tgate32 = seg_cgate32;
|
|
seg_tgate32.selector = SEL_TGATE32;
|
|
seg_tgate32.type = 11;
|
|
seg_tgate32.base = SEL_TSS32_2;
|
|
seg_tgate32.limit = 0;
|
|
|
|
struct kvm_segment seg_cgate64 = seg_cgate16;
|
|
seg_cgate64.selector = SEL_CGATE64;
|
|
seg_cgate64.type = 12;
|
|
seg_cgate64.base = SEL_CS64;
|
|
|
|
int kvmfd = open("/dev/kvm", O_RDWR);
|
|
char buf[sizeof(struct kvm_cpuid2) + 128 * sizeof(struct kvm_cpuid_entry2)];
|
|
memset(buf, 0, sizeof(buf));
|
|
struct kvm_cpuid2* cpuid = (struct kvm_cpuid2*)buf;
|
|
cpuid->nent = 128;
|
|
ioctl(kvmfd, KVM_GET_SUPPORTED_CPUID, cpuid);
|
|
ioctl(cpufd, KVM_SET_CPUID2, cpuid);
|
|
close(kvmfd);
|
|
|
|
const char* text_prefix = 0;
|
|
int text_prefix_size = 0;
|
|
char* host_text = host_mem + ADDR_TEXT;
|
|
|
|
if (text_type == 8) {
|
|
if (flags & KVM_SETUP_SMM) {
|
|
if (flags & KVM_SETUP_PROTECTED) {
|
|
sregs.cs = seg_cs16;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds16;
|
|
sregs.cr0 |= CR0_PE;
|
|
} else {
|
|
sregs.cs.selector = 0;
|
|
sregs.cs.base = 0;
|
|
}
|
|
|
|
NONFAILING(*(host_mem + ADDR_TEXT) = 0xf4);
|
|
host_text = host_mem + 0x8000;
|
|
|
|
ioctl(cpufd, KVM_SMI, 0);
|
|
} else if (flags & KVM_SETUP_VIRT86) {
|
|
sregs.cs = seg_cs32;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds32;
|
|
sregs.cr0 |= CR0_PE;
|
|
sregs.efer |= EFER_SCE;
|
|
|
|
setup_syscall_msrs(cpufd, SEL_CS32, SEL_CS32_CPL3);
|
|
setup_32bit_idt(&sregs, host_mem, guest_mem);
|
|
|
|
if (flags & KVM_SETUP_PAGING) {
|
|
uint64 pd_addr = guest_mem + ADDR_PD;
|
|
uint64* pd = (uint64*)(host_mem + ADDR_PD);
|
|
NONFAILING(pd[0] = PDE32_PRESENT | PDE32_RW | PDE32_USER | PDE32_PS);
|
|
sregs.cr3 = pd_addr;
|
|
sregs.cr4 |= CR4_PSE;
|
|
|
|
text_prefix = kvm_asm32_paged_vm86;
|
|
text_prefix_size = sizeof(kvm_asm32_paged_vm86) - 1;
|
|
} else {
|
|
text_prefix = kvm_asm32_vm86;
|
|
text_prefix_size = sizeof(kvm_asm32_vm86) - 1;
|
|
}
|
|
} else {
|
|
sregs.cs.selector = 0;
|
|
sregs.cs.base = 0;
|
|
}
|
|
} else if (text_type == 16) {
|
|
if (flags & KVM_SETUP_CPL3) {
|
|
sregs.cs = seg_cs16;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds16;
|
|
|
|
text_prefix = kvm_asm16_cpl3;
|
|
text_prefix_size = sizeof(kvm_asm16_cpl3) - 1;
|
|
} else {
|
|
sregs.cr0 |= CR0_PE;
|
|
sregs.cs = seg_cs16;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds16;
|
|
}
|
|
} else if (text_type == 32) {
|
|
sregs.cr0 |= CR0_PE;
|
|
sregs.efer |= EFER_SCE;
|
|
|
|
setup_syscall_msrs(cpufd, SEL_CS32, SEL_CS32_CPL3);
|
|
setup_32bit_idt(&sregs, host_mem, guest_mem);
|
|
|
|
if (flags & KVM_SETUP_SMM) {
|
|
sregs.cs = seg_cs32;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds32;
|
|
|
|
NONFAILING(*(host_mem + ADDR_TEXT) = 0xf4);
|
|
host_text = host_mem + 0x8000;
|
|
|
|
ioctl(cpufd, KVM_SMI, 0);
|
|
} else if (flags & KVM_SETUP_PAGING) {
|
|
sregs.cs = seg_cs32;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds32;
|
|
|
|
uint64 pd_addr = guest_mem + ADDR_PD;
|
|
uint64* pd = (uint64*)(host_mem + ADDR_PD);
|
|
NONFAILING(pd[0] = PDE32_PRESENT | PDE32_RW | PDE32_USER | PDE32_PS);
|
|
sregs.cr3 = pd_addr;
|
|
sregs.cr4 |= CR4_PSE;
|
|
|
|
text_prefix = kvm_asm32_paged;
|
|
text_prefix_size = sizeof(kvm_asm32_paged) - 1;
|
|
} else if (flags & KVM_SETUP_CPL3) {
|
|
sregs.cs = seg_cs32_cpl3;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds32_cpl3;
|
|
} else {
|
|
sregs.cs = seg_cs32;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds32;
|
|
}
|
|
} else {
|
|
sregs.efer |= EFER_LME | EFER_SCE;
|
|
sregs.cr0 |= CR0_PE;
|
|
|
|
setup_syscall_msrs(cpufd, SEL_CS64, SEL_CS64_CPL3);
|
|
setup_64bit_idt(&sregs, host_mem, guest_mem);
|
|
|
|
sregs.cs = seg_cs32;
|
|
sregs.ds = sregs.es = sregs.fs = sregs.gs = sregs.ss = seg_ds32;
|
|
|
|
uint64 pml4_addr = guest_mem + ADDR_PML4;
|
|
uint64* pml4 = (uint64*)(host_mem + ADDR_PML4);
|
|
uint64 pdpt_addr = guest_mem + ADDR_PDP;
|
|
uint64* pdpt = (uint64*)(host_mem + ADDR_PDP);
|
|
uint64 pd_addr = guest_mem + ADDR_PD;
|
|
uint64* pd = (uint64*)(host_mem + ADDR_PD);
|
|
NONFAILING(pml4[0] = PDE64_PRESENT | PDE64_RW | PDE64_USER | pdpt_addr);
|
|
NONFAILING(pdpt[0] = PDE64_PRESENT | PDE64_RW | PDE64_USER | pd_addr);
|
|
NONFAILING(pd[0] = PDE64_PRESENT | PDE64_RW | PDE64_USER | PDE64_PS);
|
|
sregs.cr3 = pml4_addr;
|
|
sregs.cr4 |= CR4_PAE;
|
|
|
|
if (flags & KVM_SETUP_VM) {
|
|
sregs.cr0 |= CR0_NE;
|
|
|
|
NONFAILING(*((uint64*)(host_mem + ADDR_VAR_VMXON_PTR)) = ADDR_VAR_VMXON);
|
|
NONFAILING(*((uint64*)(host_mem + ADDR_VAR_VMCS_PTR)) = ADDR_VAR_VMCS);
|
|
NONFAILING(memcpy(host_mem + ADDR_VAR_VMEXIT_CODE, kvm_asm64_vm_exit, sizeof(kvm_asm64_vm_exit) - 1));
|
|
NONFAILING(*((uint64*)(host_mem + ADDR_VAR_VMEXIT_PTR)) = ADDR_VAR_VMEXIT_CODE);
|
|
|
|
text_prefix = kvm_asm64_init_vm;
|
|
text_prefix_size = sizeof(kvm_asm64_init_vm) - 1;
|
|
} else if (flags & KVM_SETUP_CPL3) {
|
|
text_prefix = kvm_asm64_cpl3;
|
|
text_prefix_size = sizeof(kvm_asm64_cpl3) - 1;
|
|
} else {
|
|
text_prefix = kvm_asm64_enable_long;
|
|
text_prefix_size = sizeof(kvm_asm64_enable_long) - 1;
|
|
}
|
|
}
|
|
|
|
struct tss16 tss16;
|
|
memset(&tss16, 0, sizeof(tss16));
|
|
tss16.ss0 = tss16.ss1 = tss16.ss2 = SEL_DS16;
|
|
tss16.sp0 = tss16.sp1 = tss16.sp2 = ADDR_STACK0;
|
|
tss16.ip = ADDR_VAR_USER_CODE2;
|
|
tss16.flags = (1 << 1);
|
|
tss16.cs = SEL_CS16;
|
|
tss16.es = tss16.ds = tss16.ss = SEL_DS16;
|
|
tss16.ldt = SEL_LDT;
|
|
struct tss16* tss16_addr = (struct tss16*)(host_mem + seg_tss16_2.base);
|
|
NONFAILING(memcpy(tss16_addr, &tss16, sizeof(tss16)));
|
|
|
|
memset(&tss16, 0, sizeof(tss16));
|
|
tss16.ss0 = tss16.ss1 = tss16.ss2 = SEL_DS16;
|
|
tss16.sp0 = tss16.sp1 = tss16.sp2 = ADDR_STACK0;
|
|
tss16.ip = ADDR_VAR_USER_CODE2;
|
|
tss16.flags = (1 << 1);
|
|
tss16.cs = SEL_CS16_CPL3;
|
|
tss16.es = tss16.ds = tss16.ss = SEL_DS16_CPL3;
|
|
tss16.ldt = SEL_LDT;
|
|
struct tss16* tss16_cpl3_addr = (struct tss16*)(host_mem + seg_tss16_cpl3.base);
|
|
NONFAILING(memcpy(tss16_cpl3_addr, &tss16, sizeof(tss16)));
|
|
|
|
struct tss32 tss32;
|
|
memset(&tss32, 0, sizeof(tss32));
|
|
tss32.ss0 = tss32.ss1 = tss32.ss2 = SEL_DS32;
|
|
tss32.sp0 = tss32.sp1 = tss32.sp2 = ADDR_STACK0;
|
|
tss32.ip = ADDR_VAR_USER_CODE;
|
|
tss32.flags = (1 << 1) | (1 << 17);
|
|
tss32.ldt = SEL_LDT;
|
|
tss32.cr3 = sregs.cr3;
|
|
tss32.io_bitmap = offsetof(struct tss32, io_bitmap);
|
|
struct tss32* tss32_addr = (struct tss32*)(host_mem + seg_tss32_vm86.base);
|
|
NONFAILING(memcpy(tss32_addr, &tss32, sizeof(tss32)));
|
|
|
|
memset(&tss32, 0, sizeof(tss32));
|
|
tss32.ss0 = tss32.ss1 = tss32.ss2 = SEL_DS32;
|
|
tss32.sp0 = tss32.sp1 = tss32.sp2 = ADDR_STACK0;
|
|
tss32.ip = ADDR_VAR_USER_CODE;
|
|
tss32.flags = (1 << 1);
|
|
tss32.cr3 = sregs.cr3;
|
|
tss32.es = tss32.ds = tss32.ss = tss32.gs = tss32.fs = SEL_DS32;
|
|
tss32.cs = SEL_CS32;
|
|
tss32.ldt = SEL_LDT;
|
|
tss32.cr3 = sregs.cr3;
|
|
tss32.io_bitmap = offsetof(struct tss32, io_bitmap);
|
|
struct tss32* tss32_cpl3_addr = (struct tss32*)(host_mem + seg_tss32_2.base);
|
|
NONFAILING(memcpy(tss32_cpl3_addr, &tss32, sizeof(tss32)));
|
|
|
|
struct tss64 tss64;
|
|
memset(&tss64, 0, sizeof(tss64));
|
|
tss64.rsp[0] = ADDR_STACK0;
|
|
tss64.rsp[1] = ADDR_STACK0;
|
|
tss64.rsp[2] = ADDR_STACK0;
|
|
tss64.io_bitmap = offsetof(struct tss64, io_bitmap);
|
|
struct tss64* tss64_addr = (struct tss64*)(host_mem + seg_tss64.base);
|
|
NONFAILING(memcpy(tss64_addr, &tss64, sizeof(tss64)));
|
|
|
|
memset(&tss64, 0, sizeof(tss64));
|
|
tss64.rsp[0] = ADDR_STACK0;
|
|
tss64.rsp[1] = ADDR_STACK0;
|
|
tss64.rsp[2] = ADDR_STACK0;
|
|
tss64.io_bitmap = offsetof(struct tss64, io_bitmap);
|
|
struct tss64* tss64_cpl3_addr = (struct tss64*)(host_mem + seg_tss64_cpl3.base);
|
|
NONFAILING(memcpy(tss64_cpl3_addr, &tss64, sizeof(tss64)));
|
|
|
|
if (text_size > 1000)
|
|
text_size = 1000;
|
|
if (text_prefix) {
|
|
NONFAILING(memcpy(host_text, text_prefix, text_prefix_size));
|
|
void* patch = 0;
|
|
NONFAILING(patch = memmem(host_text, text_prefix_size, "\xde\xc0\xad\x0b", 4));
|
|
if (patch)
|
|
NONFAILING(*((uint32*)patch) = guest_mem + ADDR_TEXT + ((char*)patch - host_text) + 6);
|
|
uint16 magic = PREFIX_SIZE;
|
|
patch = 0;
|
|
NONFAILING(patch = memmem(host_text, text_prefix_size, &magic, sizeof(magic)));
|
|
if (patch)
|
|
NONFAILING(*((uint16*)patch) = guest_mem + ADDR_TEXT + text_prefix_size);
|
|
}
|
|
NONFAILING(memcpy((void*)(host_text + text_prefix_size), text, text_size));
|
|
NONFAILING(*(host_text + text_prefix_size + text_size) = 0xf4);
|
|
|
|
NONFAILING(memcpy(host_mem + ADDR_VAR_USER_CODE, text, text_size));
|
|
NONFAILING(*(host_mem + ADDR_VAR_USER_CODE + text_size) = 0xf4);
|
|
|
|
NONFAILING(*(host_mem + ADDR_VAR_HLT) = 0xf4);
|
|
NONFAILING(memcpy(host_mem + ADDR_VAR_SYSRET, "\x0f\x07\xf4", 3));
|
|
NONFAILING(memcpy(host_mem + ADDR_VAR_SYSEXIT, "\x0f\x35\xf4", 3));
|
|
|
|
NONFAILING(*(uint64*)(host_mem + ADDR_VAR_VMWRITE_FLD) = 0);
|
|
NONFAILING(*(uint64*)(host_mem + ADDR_VAR_VMWRITE_VAL) = 0);
|
|
|
|
if (opt_count > 2)
|
|
opt_count = 2;
|
|
for (i = 0; i < opt_count; i++) {
|
|
uint64 typ = 0;
|
|
uint64 val = 0;
|
|
NONFAILING(typ = opt_array_ptr[i].typ);
|
|
NONFAILING(val = opt_array_ptr[i].val);
|
|
switch (typ % 9) {
|
|
case 0:
|
|
sregs.cr0 ^= val & (CR0_MP | CR0_EM | CR0_ET | CR0_NE | CR0_WP | CR0_AM | CR0_NW | CR0_CD);
|
|
break;
|
|
case 1:
|
|
sregs.cr4 ^= val & (CR4_VME | CR4_PVI | CR4_TSD | CR4_DE | CR4_MCE | CR4_PGE | CR4_PCE |
|
|
CR4_OSFXSR | CR4_OSXMMEXCPT | CR4_UMIP | CR4_VMXE | CR4_SMXE | CR4_FSGSBASE | CR4_PCIDE |
|
|
CR4_OSXSAVE | CR4_SMEP | CR4_SMAP | CR4_PKE);
|
|
break;
|
|
case 2:
|
|
sregs.efer ^= val & (EFER_SCE | EFER_NXE | EFER_SVME | EFER_LMSLE | EFER_FFXSR | EFER_TCE);
|
|
break;
|
|
case 3:
|
|
val &= ((1 << 8) | (1 << 9) | (1 << 10) | (1 << 12) | (1 << 13) | (1 << 14) |
|
|
(1 << 15) | (1 << 18) | (1 << 19) | (1 << 20) | (1 << 21));
|
|
regs.rflags ^= val;
|
|
NONFAILING(tss16_addr->flags ^= val);
|
|
NONFAILING(tss16_cpl3_addr->flags ^= val);
|
|
NONFAILING(tss32_addr->flags ^= val);
|
|
NONFAILING(tss32_cpl3_addr->flags ^= val);
|
|
break;
|
|
case 4:
|
|
seg_cs16.type = val & 0xf;
|
|
seg_cs32.type = val & 0xf;
|
|
seg_cs64.type = val & 0xf;
|
|
break;
|
|
case 5:
|
|
seg_cs16_cpl3.type = val & 0xf;
|
|
seg_cs32_cpl3.type = val & 0xf;
|
|
seg_cs64_cpl3.type = val & 0xf;
|
|
break;
|
|
case 6:
|
|
seg_ds16.type = val & 0xf;
|
|
seg_ds32.type = val & 0xf;
|
|
seg_ds64.type = val & 0xf;
|
|
break;
|
|
case 7:
|
|
seg_ds16_cpl3.type = val & 0xf;
|
|
seg_ds32_cpl3.type = val & 0xf;
|
|
seg_ds64_cpl3.type = val & 0xf;
|
|
break;
|
|
case 8:
|
|
NONFAILING(*(uint64*)(host_mem + ADDR_VAR_VMWRITE_FLD) = (val & 0xffff));
|
|
NONFAILING(*(uint64*)(host_mem + ADDR_VAR_VMWRITE_VAL) = (val >> 16));
|
|
break;
|
|
default:
|
|
fail("bad kvm setup opt");
|
|
}
|
|
}
|
|
regs.rflags |= 2;
|
|
|
|
fill_segment_descriptor(gdt, ldt, &seg_ldt);
|
|
fill_segment_descriptor(gdt, ldt, &seg_cs16);
|
|
fill_segment_descriptor(gdt, ldt, &seg_ds16);
|
|
fill_segment_descriptor(gdt, ldt, &seg_cs16_cpl3);
|
|
fill_segment_descriptor(gdt, ldt, &seg_ds16_cpl3);
|
|
fill_segment_descriptor(gdt, ldt, &seg_cs32);
|
|
fill_segment_descriptor(gdt, ldt, &seg_ds32);
|
|
fill_segment_descriptor(gdt, ldt, &seg_cs32_cpl3);
|
|
fill_segment_descriptor(gdt, ldt, &seg_ds32_cpl3);
|
|
fill_segment_descriptor(gdt, ldt, &seg_cs64);
|
|
fill_segment_descriptor(gdt, ldt, &seg_ds64);
|
|
fill_segment_descriptor(gdt, ldt, &seg_cs64_cpl3);
|
|
fill_segment_descriptor(gdt, ldt, &seg_ds64_cpl3);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tss32);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tss32_2);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tss32_cpl3);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tss32_vm86);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tss16);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tss16_2);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tss16_cpl3);
|
|
fill_segment_descriptor_dword(gdt, ldt, &seg_tss64);
|
|
fill_segment_descriptor_dword(gdt, ldt, &seg_tss64_cpl3);
|
|
fill_segment_descriptor(gdt, ldt, &seg_cgate16);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tgate16);
|
|
fill_segment_descriptor(gdt, ldt, &seg_cgate32);
|
|
fill_segment_descriptor(gdt, ldt, &seg_tgate32);
|
|
fill_segment_descriptor_dword(gdt, ldt, &seg_cgate64);
|
|
|
|
if (ioctl(cpufd, KVM_SET_SREGS, &sregs))
|
|
return -1;
|
|
if (ioctl(cpufd, KVM_SET_REGS, ®s))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
#elif GOARCH_arm64
|
|
|
|
struct kvm_text {
|
|
uintptr_t typ;
|
|
const void* text;
|
|
uintptr_t size;
|
|
};
|
|
|
|
struct kvm_opt {
|
|
uint64 typ;
|
|
uint64 val;
|
|
};
|
|
static long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volatile long a2, volatile long a3, volatile long a4, volatile long a5, volatile long a6, volatile long a7)
|
|
{
|
|
const int vmfd = a0;
|
|
const int cpufd = a1;
|
|
char* const host_mem = (char*)a2;
|
|
const struct kvm_text* const text_array_ptr = (struct kvm_text*)a3;
|
|
const uintptr_t text_count = a4;
|
|
const uintptr_t flags = a5;
|
|
const struct kvm_opt* const opt_array_ptr = (struct kvm_opt*)a6;
|
|
uintptr_t opt_count = a7;
|
|
|
|
(void)flags;
|
|
(void)opt_count;
|
|
|
|
const uintptr_t page_size = 4 << 10;
|
|
const uintptr_t guest_mem = 0;
|
|
const uintptr_t guest_mem_size = 24 * page_size;
|
|
|
|
(void)text_count;
|
|
int text_type = 0;
|
|
const void* text = 0;
|
|
int text_size = 0;
|
|
NONFAILING(text_type = text_array_ptr[0].typ);
|
|
NONFAILING(text = text_array_ptr[0].text);
|
|
NONFAILING(text_size = text_array_ptr[0].size);
|
|
(void)text_type;
|
|
(void)opt_array_ptr;
|
|
|
|
uint32 features = 0;
|
|
if (opt_count > 1)
|
|
opt_count = 1;
|
|
uintptr_t i;
|
|
for (i = 0; i < opt_count; i++) {
|
|
uint64 typ = 0;
|
|
uint64 val = 0;
|
|
NONFAILING(typ = opt_array_ptr[i].typ);
|
|
NONFAILING(val = opt_array_ptr[i].val);
|
|
switch (typ) {
|
|
case 1:
|
|
features = val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < guest_mem_size / page_size; i++) {
|
|
struct kvm_userspace_memory_region memreg;
|
|
memreg.slot = i;
|
|
memreg.flags = 0;
|
|
memreg.guest_phys_addr = guest_mem + i * page_size;
|
|
memreg.memory_size = page_size;
|
|
memreg.userspace_addr = (uintptr_t)host_mem + i * page_size;
|
|
ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg);
|
|
}
|
|
|
|
struct kvm_vcpu_init init;
|
|
ioctl(cpufd, KVM_ARM_PREFERRED_TARGET, &init);
|
|
init.features[0] = features;
|
|
ioctl(cpufd, KVM_ARM_VCPU_INIT, &init);
|
|
|
|
if (text_size > 1000)
|
|
text_size = 1000;
|
|
NONFAILING(memcpy(host_mem, text, text_size));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
static long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volatile long a2, volatile long a3, volatile long a4, volatile long a5, volatile long a6, volatile long a7)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_RESET_NET_NAMESPACE
|
|
#include <errno.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <linux/net.h>
|
|
#define XT_TABLE_SIZE 1536
|
|
#define XT_MAX_ENTRIES 10
|
|
|
|
struct xt_counters {
|
|
uint64 pcnt, bcnt;
|
|
};
|
|
|
|
struct ipt_getinfo {
|
|
char name[32];
|
|
unsigned int valid_hooks;
|
|
unsigned int hook_entry[5];
|
|
unsigned int underflow[5];
|
|
unsigned int num_entries;
|
|
unsigned int size;
|
|
};
|
|
|
|
struct ipt_get_entries {
|
|
char name[32];
|
|
unsigned int size;
|
|
void* entrytable[XT_TABLE_SIZE / sizeof(void*)];
|
|
};
|
|
|
|
struct ipt_replace {
|
|
char name[32];
|
|
unsigned int valid_hooks;
|
|
unsigned int num_entries;
|
|
unsigned int size;
|
|
unsigned int hook_entry[5];
|
|
unsigned int underflow[5];
|
|
unsigned int num_counters;
|
|
struct xt_counters* counters;
|
|
char entrytable[XT_TABLE_SIZE];
|
|
};
|
|
|
|
struct ipt_table_desc {
|
|
const char* name;
|
|
struct ipt_getinfo info;
|
|
struct ipt_replace replace;
|
|
};
|
|
|
|
static struct ipt_table_desc ipv4_tables[] = {
|
|
{.name = "filter"},
|
|
{.name = "nat"},
|
|
{.name = "mangle"},
|
|
{.name = "raw"},
|
|
{.name = "security"},
|
|
};
|
|
|
|
static struct ipt_table_desc ipv6_tables[] = {
|
|
{.name = "filter"},
|
|
{.name = "nat"},
|
|
{.name = "mangle"},
|
|
{.name = "raw"},
|
|
{.name = "security"},
|
|
};
|
|
|
|
#define IPT_BASE_CTL 64
|
|
#define IPT_SO_SET_REPLACE (IPT_BASE_CTL)
|
|
#define IPT_SO_GET_INFO (IPT_BASE_CTL)
|
|
#define IPT_SO_GET_ENTRIES (IPT_BASE_CTL + 1)
|
|
|
|
struct arpt_getinfo {
|
|
char name[32];
|
|
unsigned int valid_hooks;
|
|
unsigned int hook_entry[3];
|
|
unsigned int underflow[3];
|
|
unsigned int num_entries;
|
|
unsigned int size;
|
|
};
|
|
|
|
struct arpt_get_entries {
|
|
char name[32];
|
|
unsigned int size;
|
|
void* entrytable[XT_TABLE_SIZE / sizeof(void*)];
|
|
};
|
|
|
|
struct arpt_replace {
|
|
char name[32];
|
|
unsigned int valid_hooks;
|
|
unsigned int num_entries;
|
|
unsigned int size;
|
|
unsigned int hook_entry[3];
|
|
unsigned int underflow[3];
|
|
unsigned int num_counters;
|
|
struct xt_counters* counters;
|
|
char entrytable[XT_TABLE_SIZE];
|
|
};
|
|
|
|
struct arpt_table_desc {
|
|
const char* name;
|
|
struct arpt_getinfo info;
|
|
struct arpt_replace replace;
|
|
};
|
|
|
|
static struct arpt_table_desc arpt_tables[] = {
|
|
{.name = "filter"},
|
|
};
|
|
|
|
#define ARPT_BASE_CTL 96
|
|
#define ARPT_SO_SET_REPLACE (ARPT_BASE_CTL)
|
|
#define ARPT_SO_GET_INFO (ARPT_BASE_CTL)
|
|
#define ARPT_SO_GET_ENTRIES (ARPT_BASE_CTL + 1)
|
|
|
|
static void checkpoint_iptables(struct ipt_table_desc* tables, int num_tables, int family, int level)
|
|
{
|
|
struct ipt_get_entries entries;
|
|
socklen_t optlen;
|
|
int fd, i;
|
|
|
|
fd = socket(family, SOCK_STREAM, IPPROTO_TCP);
|
|
if (fd == -1) {
|
|
switch (errno) {
|
|
case EAFNOSUPPORT:
|
|
case ENOPROTOOPT:
|
|
return;
|
|
}
|
|
fail("iptable checkpoint %d: socket failed", family);
|
|
}
|
|
for (i = 0; i < num_tables; i++) {
|
|
struct ipt_table_desc* table = &tables[i];
|
|
strcpy(table->info.name, table->name);
|
|
strcpy(table->replace.name, table->name);
|
|
optlen = sizeof(table->info);
|
|
if (getsockopt(fd, level, IPT_SO_GET_INFO, &table->info, &optlen)) {
|
|
switch (errno) {
|
|
case EPERM:
|
|
case ENOENT:
|
|
case ENOPROTOOPT:
|
|
continue;
|
|
}
|
|
fail("iptable checkpoint %s/%d: getsockopt(IPT_SO_GET_INFO)", table->name, family);
|
|
}
|
|
debug("iptable checkpoint %s/%d: checkpoint entries=%d hooks=%x size=%d\n",
|
|
table->name, family, table->info.num_entries,
|
|
table->info.valid_hooks, table->info.size);
|
|
if (table->info.size > sizeof(table->replace.entrytable))
|
|
fail("iptable checkpoint %s/%d: table size is too large: %u",
|
|
table->name, family, table->info.size);
|
|
if (table->info.num_entries > XT_MAX_ENTRIES)
|
|
fail("iptable checkpoint %s/%d: too many counters: %u",
|
|
table->name, family, table->info.num_entries);
|
|
memset(&entries, 0, sizeof(entries));
|
|
strcpy(entries.name, table->name);
|
|
entries.size = table->info.size;
|
|
optlen = sizeof(entries) - sizeof(entries.entrytable) + table->info.size;
|
|
if (getsockopt(fd, level, IPT_SO_GET_ENTRIES, &entries, &optlen))
|
|
fail("iptable checkpoint %s/%d: getsockopt(IPT_SO_GET_ENTRIES)",
|
|
table->name, family);
|
|
table->replace.valid_hooks = table->info.valid_hooks;
|
|
table->replace.num_entries = table->info.num_entries;
|
|
table->replace.size = table->info.size;
|
|
memcpy(table->replace.hook_entry, table->info.hook_entry, sizeof(table->replace.hook_entry));
|
|
memcpy(table->replace.underflow, table->info.underflow, sizeof(table->replace.underflow));
|
|
memcpy(table->replace.entrytable, entries.entrytable, table->info.size);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
static void reset_iptables(struct ipt_table_desc* tables, int num_tables, int family, int level)
|
|
{
|
|
struct xt_counters counters[XT_MAX_ENTRIES];
|
|
struct ipt_get_entries entries;
|
|
struct ipt_getinfo info;
|
|
socklen_t optlen;
|
|
int fd, i;
|
|
|
|
fd = socket(family, SOCK_STREAM, IPPROTO_TCP);
|
|
if (fd == -1) {
|
|
switch (errno) {
|
|
case EAFNOSUPPORT:
|
|
case ENOPROTOOPT:
|
|
return;
|
|
}
|
|
fail("iptable %d: socket failed", family);
|
|
}
|
|
for (i = 0; i < num_tables; i++) {
|
|
struct ipt_table_desc* table = &tables[i];
|
|
if (table->info.valid_hooks == 0)
|
|
continue;
|
|
memset(&info, 0, sizeof(info));
|
|
strcpy(info.name, table->name);
|
|
optlen = sizeof(info);
|
|
if (getsockopt(fd, level, IPT_SO_GET_INFO, &info, &optlen))
|
|
fail("iptable %s/%d: getsockopt(IPT_SO_GET_INFO)", table->name, family);
|
|
if (memcmp(&table->info, &info, sizeof(table->info)) == 0) {
|
|
memset(&entries, 0, sizeof(entries));
|
|
strcpy(entries.name, table->name);
|
|
entries.size = table->info.size;
|
|
optlen = sizeof(entries) - sizeof(entries.entrytable) + entries.size;
|
|
if (getsockopt(fd, level, IPT_SO_GET_ENTRIES, &entries, &optlen))
|
|
fail("iptable %s/%d: getsockopt(IPT_SO_GET_ENTRIES)", table->name, family);
|
|
if (memcmp(table->replace.entrytable, entries.entrytable, table->info.size) == 0)
|
|
continue;
|
|
}
|
|
debug("iptable %s/%d: resetting\n", table->name, family);
|
|
table->replace.num_counters = info.num_entries;
|
|
table->replace.counters = counters;
|
|
optlen = sizeof(table->replace) - sizeof(table->replace.entrytable) + table->replace.size;
|
|
if (setsockopt(fd, level, IPT_SO_SET_REPLACE, &table->replace, optlen))
|
|
fail("iptable %s/%d: setsockopt(IPT_SO_SET_REPLACE)", table->name, family);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
static void checkpoint_arptables(void)
|
|
{
|
|
struct arpt_get_entries entries;
|
|
socklen_t optlen;
|
|
unsigned i;
|
|
int fd;
|
|
|
|
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (fd == -1) {
|
|
switch (errno) {
|
|
case EAFNOSUPPORT:
|
|
case ENOPROTOOPT:
|
|
return;
|
|
}
|
|
fail("arptable checkpoint: socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)");
|
|
}
|
|
for (i = 0; i < sizeof(arpt_tables) / sizeof(arpt_tables[0]); i++) {
|
|
struct arpt_table_desc* table = &arpt_tables[i];
|
|
strcpy(table->info.name, table->name);
|
|
strcpy(table->replace.name, table->name);
|
|
optlen = sizeof(table->info);
|
|
if (getsockopt(fd, SOL_IP, ARPT_SO_GET_INFO, &table->info, &optlen)) {
|
|
switch (errno) {
|
|
case EPERM:
|
|
case ENOENT:
|
|
case ENOPROTOOPT:
|
|
continue;
|
|
}
|
|
fail("arptable checkpoint %s: getsockopt(ARPT_SO_GET_INFO)", table->name);
|
|
}
|
|
debug("arptable checkpoint %s: entries=%d hooks=%x size=%d\n",
|
|
table->name, table->info.num_entries, table->info.valid_hooks, table->info.size);
|
|
if (table->info.size > sizeof(table->replace.entrytable))
|
|
fail("arptable checkpoint %s: table size is too large: %u",
|
|
table->name, table->info.size);
|
|
if (table->info.num_entries > XT_MAX_ENTRIES)
|
|
fail("arptable checkpoint %s: too many counters: %u",
|
|
table->name, table->info.num_entries);
|
|
memset(&entries, 0, sizeof(entries));
|
|
strcpy(entries.name, table->name);
|
|
entries.size = table->info.size;
|
|
optlen = sizeof(entries) - sizeof(entries.entrytable) + table->info.size;
|
|
if (getsockopt(fd, SOL_IP, ARPT_SO_GET_ENTRIES, &entries, &optlen))
|
|
fail("arptable checkpoint %s: getsockopt(ARPT_SO_GET_ENTRIES)", table->name);
|
|
table->replace.valid_hooks = table->info.valid_hooks;
|
|
table->replace.num_entries = table->info.num_entries;
|
|
table->replace.size = table->info.size;
|
|
memcpy(table->replace.hook_entry, table->info.hook_entry, sizeof(table->replace.hook_entry));
|
|
memcpy(table->replace.underflow, table->info.underflow, sizeof(table->replace.underflow));
|
|
memcpy(table->replace.entrytable, entries.entrytable, table->info.size);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
static void reset_arptables()
|
|
{
|
|
struct xt_counters counters[XT_MAX_ENTRIES];
|
|
struct arpt_get_entries entries;
|
|
struct arpt_getinfo info;
|
|
socklen_t optlen;
|
|
unsigned i;
|
|
int fd;
|
|
|
|
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (fd == -1) {
|
|
switch (errno) {
|
|
case EAFNOSUPPORT:
|
|
case ENOPROTOOPT:
|
|
return;
|
|
}
|
|
fail("arptable: socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)");
|
|
}
|
|
for (i = 0; i < sizeof(arpt_tables) / sizeof(arpt_tables[0]); i++) {
|
|
struct arpt_table_desc* table = &arpt_tables[i];
|
|
if (table->info.valid_hooks == 0)
|
|
continue;
|
|
memset(&info, 0, sizeof(info));
|
|
strcpy(info.name, table->name);
|
|
optlen = sizeof(info);
|
|
if (getsockopt(fd, SOL_IP, ARPT_SO_GET_INFO, &info, &optlen))
|
|
fail("arptable %s:getsockopt(ARPT_SO_GET_INFO)", table->name);
|
|
if (memcmp(&table->info, &info, sizeof(table->info)) == 0) {
|
|
memset(&entries, 0, sizeof(entries));
|
|
strcpy(entries.name, table->name);
|
|
entries.size = table->info.size;
|
|
optlen = sizeof(entries) - sizeof(entries.entrytable) + entries.size;
|
|
if (getsockopt(fd, SOL_IP, ARPT_SO_GET_ENTRIES, &entries, &optlen))
|
|
fail("arptable %s: getsockopt(ARPT_SO_GET_ENTRIES)", table->name);
|
|
if (memcmp(table->replace.entrytable, entries.entrytable, table->info.size) == 0)
|
|
continue;
|
|
debug("arptable %s: data changed\n", table->name);
|
|
} else {
|
|
debug("arptable %s: header changed\n", table->name);
|
|
}
|
|
debug("arptable %s: resetting\n", table->name);
|
|
table->replace.num_counters = info.num_entries;
|
|
table->replace.counters = counters;
|
|
optlen = sizeof(table->replace) - sizeof(table->replace.entrytable) + table->replace.size;
|
|
if (setsockopt(fd, SOL_IP, ARPT_SO_SET_REPLACE, &table->replace, optlen))
|
|
fail("arptable %s: setsockopt(ARPT_SO_SET_REPLACE)", table->name);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
#define NF_BR_NUMHOOKS 6
|
|
#define EBT_TABLE_MAXNAMELEN 32
|
|
#define EBT_CHAIN_MAXNAMELEN 32
|
|
#define EBT_BASE_CTL 128
|
|
#define EBT_SO_SET_ENTRIES (EBT_BASE_CTL)
|
|
#define EBT_SO_GET_INFO (EBT_BASE_CTL)
|
|
#define EBT_SO_GET_ENTRIES (EBT_SO_GET_INFO + 1)
|
|
#define EBT_SO_GET_INIT_INFO (EBT_SO_GET_ENTRIES + 1)
|
|
#define EBT_SO_GET_INIT_ENTRIES (EBT_SO_GET_INIT_INFO + 1)
|
|
|
|
struct ebt_replace {
|
|
char name[EBT_TABLE_MAXNAMELEN];
|
|
unsigned int valid_hooks;
|
|
unsigned int nentries;
|
|
unsigned int entries_size;
|
|
struct ebt_entries* hook_entry[NF_BR_NUMHOOKS];
|
|
unsigned int num_counters;
|
|
struct ebt_counter* counters;
|
|
char* entries;
|
|
};
|
|
|
|
struct ebt_entries {
|
|
unsigned int distinguisher;
|
|
char name[EBT_CHAIN_MAXNAMELEN];
|
|
unsigned int counter_offset;
|
|
int policy;
|
|
unsigned int nentries;
|
|
char data[0] __attribute__((aligned(__alignof__(struct ebt_replace))));
|
|
};
|
|
|
|
struct ebt_table_desc {
|
|
const char* name;
|
|
struct ebt_replace replace;
|
|
char entrytable[XT_TABLE_SIZE];
|
|
};
|
|
|
|
static struct ebt_table_desc ebt_tables[] = {
|
|
{.name = "filter"},
|
|
{.name = "nat"},
|
|
{.name = "broute"},
|
|
};
|
|
|
|
static void checkpoint_ebtables(void)
|
|
{
|
|
socklen_t optlen;
|
|
unsigned i;
|
|
int fd;
|
|
|
|
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (fd == -1) {
|
|
switch (errno) {
|
|
case EAFNOSUPPORT:
|
|
case ENOPROTOOPT:
|
|
return;
|
|
}
|
|
fail("ebtable checkpoint: socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)");
|
|
}
|
|
for (i = 0; i < sizeof(ebt_tables) / sizeof(ebt_tables[0]); i++) {
|
|
struct ebt_table_desc* table = &ebt_tables[i];
|
|
strcpy(table->replace.name, table->name);
|
|
optlen = sizeof(table->replace);
|
|
if (getsockopt(fd, SOL_IP, EBT_SO_GET_INIT_INFO, &table->replace, &optlen)) {
|
|
switch (errno) {
|
|
case EPERM:
|
|
case ENOENT:
|
|
case ENOPROTOOPT:
|
|
continue;
|
|
}
|
|
fail("ebtable checkpoint %s: getsockopt(EBT_SO_GET_INIT_INFO)", table->name);
|
|
}
|
|
debug("ebtable checkpoint %s: entries=%d hooks=%x size=%d\n",
|
|
table->name, table->replace.nentries, table->replace.valid_hooks,
|
|
table->replace.entries_size);
|
|
if (table->replace.entries_size > sizeof(table->entrytable))
|
|
fail("ebtable checkpoint %s: table size is too large: %u",
|
|
table->name, table->replace.entries_size);
|
|
table->replace.num_counters = 0;
|
|
table->replace.entries = table->entrytable;
|
|
optlen = sizeof(table->replace) + table->replace.entries_size;
|
|
if (getsockopt(fd, SOL_IP, EBT_SO_GET_INIT_ENTRIES, &table->replace, &optlen))
|
|
fail("ebtable checkpoint %s: getsockopt(EBT_SO_GET_INIT_ENTRIES)", table->name);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
static void reset_ebtables()
|
|
{
|
|
struct ebt_replace replace;
|
|
char entrytable[XT_TABLE_SIZE];
|
|
socklen_t optlen;
|
|
unsigned i, j, h;
|
|
int fd;
|
|
|
|
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (fd == -1) {
|
|
switch (errno) {
|
|
case EAFNOSUPPORT:
|
|
case ENOPROTOOPT:
|
|
return;
|
|
}
|
|
fail("ebtable: socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)");
|
|
}
|
|
for (i = 0; i < sizeof(ebt_tables) / sizeof(ebt_tables[0]); i++) {
|
|
struct ebt_table_desc* table = &ebt_tables[i];
|
|
if (table->replace.valid_hooks == 0)
|
|
continue;
|
|
memset(&replace, 0, sizeof(replace));
|
|
strcpy(replace.name, table->name);
|
|
optlen = sizeof(replace);
|
|
if (getsockopt(fd, SOL_IP, EBT_SO_GET_INFO, &replace, &optlen))
|
|
fail("ebtable %s: getsockopt(EBT_SO_GET_INFO)", table->name);
|
|
replace.num_counters = 0;
|
|
table->replace.entries = 0;
|
|
for (h = 0; h < NF_BR_NUMHOOKS; h++)
|
|
table->replace.hook_entry[h] = 0;
|
|
if (memcmp(&table->replace, &replace, sizeof(table->replace)) == 0) {
|
|
memset(&entrytable, 0, sizeof(entrytable));
|
|
replace.entries = entrytable;
|
|
optlen = sizeof(replace) + replace.entries_size;
|
|
if (getsockopt(fd, SOL_IP, EBT_SO_GET_ENTRIES, &replace, &optlen))
|
|
fail("ebtable %s: getsockopt(EBT_SO_GET_ENTRIES)", table->name);
|
|
if (memcmp(table->entrytable, entrytable, replace.entries_size) == 0)
|
|
continue;
|
|
}
|
|
debug("ebtable %s: resetting\n", table->name);
|
|
for (j = 0, h = 0; h < NF_BR_NUMHOOKS; h++) {
|
|
if (table->replace.valid_hooks & (1 << h)) {
|
|
table->replace.hook_entry[h] = (struct ebt_entries*)table->entrytable + j;
|
|
j++;
|
|
}
|
|
}
|
|
table->replace.entries = table->entrytable;
|
|
optlen = sizeof(table->replace) + table->replace.entries_size;
|
|
if (setsockopt(fd, SOL_IP, EBT_SO_SET_ENTRIES, &table->replace, optlen))
|
|
fail("ebtable %s: setsockopt(EBT_SO_SET_ENTRIES)", table->name);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
static void checkpoint_net_namespace(void)
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_net_reset)
|
|
return;
|
|
if (flag_sandbox == sandbox_setuid)
|
|
return;
|
|
#endif
|
|
checkpoint_ebtables();
|
|
checkpoint_arptables();
|
|
checkpoint_iptables(ipv4_tables, sizeof(ipv4_tables) / sizeof(ipv4_tables[0]), AF_INET, SOL_IP);
|
|
checkpoint_iptables(ipv6_tables, sizeof(ipv6_tables) / sizeof(ipv6_tables[0]), AF_INET6, SOL_IPV6);
|
|
}
|
|
|
|
static void reset_net_namespace(void)
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_net_reset)
|
|
return;
|
|
if (flag_sandbox == sandbox_setuid)
|
|
return;
|
|
#endif
|
|
reset_ebtables();
|
|
reset_arptables();
|
|
reset_iptables(ipv4_tables, sizeof(ipv4_tables) / sizeof(ipv4_tables[0]), AF_INET, SOL_IP);
|
|
reset_iptables(ipv6_tables, sizeof(ipv6_tables) / sizeof(ipv6_tables[0]), AF_INET6, SOL_IPV6);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || (SYZ_ENABLE_CGROUPS && (SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP))
|
|
#include <fcntl.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static void setup_cgroups()
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_cgroups)
|
|
return;
|
|
#endif
|
|
if (mkdir("/syzcgroup", 0777)) {
|
|
debug("mkdir(/syzcgroup) failed: %d\n", errno);
|
|
}
|
|
if (mkdir("/syzcgroup/unified", 0777)) {
|
|
debug("mkdir(/syzcgroup/unified) failed: %d\n", errno);
|
|
}
|
|
if (mount("none", "/syzcgroup/unified", "cgroup2", 0, NULL)) {
|
|
debug("mount(cgroup2) failed: %d\n", errno);
|
|
}
|
|
if (chmod("/syzcgroup/unified", 0777)) {
|
|
debug("chmod(/syzcgroup/unified) failed: %d\n", errno);
|
|
}
|
|
write_file("/syzcgroup/unified/cgroup.subtree_control", "+cpu +memory +io +pids +rdma");
|
|
if (mkdir("/syzcgroup/cpu", 0777)) {
|
|
debug("mkdir(/syzcgroup/cpu) failed: %d\n", errno);
|
|
}
|
|
if (mount("none", "/syzcgroup/cpu", "cgroup", 0, "cpuset,cpuacct,perf_event,hugetlb")) {
|
|
debug("mount(cgroup cpu) failed: %d\n", errno);
|
|
}
|
|
write_file("/syzcgroup/cpu/cgroup.clone_children", "1");
|
|
if (chmod("/syzcgroup/cpu", 0777)) {
|
|
debug("chmod(/syzcgroup/cpu) failed: %d\n", errno);
|
|
}
|
|
if (mkdir("/syzcgroup/net", 0777)) {
|
|
debug("mkdir(/syzcgroup/net) failed: %d\n", errno);
|
|
}
|
|
if (mount("none", "/syzcgroup/net", "cgroup", 0, "net_cls,net_prio,devices,freezer")) {
|
|
debug("mount(cgroup net) failed: %d\n", errno);
|
|
}
|
|
if (chmod("/syzcgroup/net", 0777)) {
|
|
debug("chmod(/syzcgroup/net) failed: %d\n", errno);
|
|
}
|
|
}
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT
|
|
static void setup_cgroups_loop()
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_cgroups)
|
|
return;
|
|
#endif
|
|
int pid = getpid();
|
|
char file[128];
|
|
char cgroupdir[64];
|
|
snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/unified/syz%llu", procid);
|
|
if (mkdir(cgroupdir, 0777)) {
|
|
debug("mkdir(%s) failed: %d\n", cgroupdir, errno);
|
|
}
|
|
snprintf(file, sizeof(file), "%s/pids.max", cgroupdir);
|
|
write_file(file, "32");
|
|
snprintf(file, sizeof(file), "%s/memory.low", cgroupdir);
|
|
write_file(file, "%d", 298 << 20);
|
|
snprintf(file, sizeof(file), "%s/memory.high", cgroupdir);
|
|
write_file(file, "%d", 299 << 20);
|
|
snprintf(file, sizeof(file), "%s/memory.max", cgroupdir);
|
|
write_file(file, "%d", 300 << 20);
|
|
snprintf(file, sizeof(file), "%s/cgroup.procs", cgroupdir);
|
|
write_file(file, "%d", pid);
|
|
snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/cpu/syz%llu", procid);
|
|
if (mkdir(cgroupdir, 0777)) {
|
|
debug("mkdir(%s) failed: %d\n", cgroupdir, errno);
|
|
}
|
|
snprintf(file, sizeof(file), "%s/cgroup.procs", cgroupdir);
|
|
write_file(file, "%d", pid);
|
|
snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/net/syz%llu", procid);
|
|
if (mkdir(cgroupdir, 0777)) {
|
|
debug("mkdir(%s) failed: %d\n", cgroupdir, errno);
|
|
}
|
|
snprintf(file, sizeof(file), "%s/cgroup.procs", cgroupdir);
|
|
write_file(file, "%d", pid);
|
|
}
|
|
|
|
static void setup_cgroups_test()
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_cgroups)
|
|
return;
|
|
#endif
|
|
char cgroupdir[64];
|
|
snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/unified/syz%llu", procid);
|
|
if (symlink(cgroupdir, "./cgroup")) {
|
|
debug("symlink(%s, ./cgroup) failed: %d\n", cgroupdir, errno);
|
|
}
|
|
snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/cpu/syz%llu", procid);
|
|
if (symlink(cgroupdir, "./cgroup.cpu")) {
|
|
debug("symlink(%s, ./cgroup.cpu) failed: %d\n", cgroupdir, errno);
|
|
}
|
|
snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/net/syz%llu", procid);
|
|
if (symlink(cgroupdir, "./cgroup.net")) {
|
|
debug("symlink(%s, ./cgroup.net) failed: %d\n", cgroupdir, errno);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NAMESPACE
|
|
void initialize_cgroups()
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_cgroups)
|
|
return;
|
|
#endif
|
|
if (mkdir("./syz-tmp/newroot/syzcgroup", 0700))
|
|
fail("mkdir failed");
|
|
if (mkdir("./syz-tmp/newroot/syzcgroup/unified", 0700))
|
|
fail("mkdir failed");
|
|
if (mkdir("./syz-tmp/newroot/syzcgroup/cpu", 0700))
|
|
fail("mkdir failed");
|
|
if (mkdir("./syz-tmp/newroot/syzcgroup/net", 0700))
|
|
fail("mkdir failed");
|
|
unsigned bind_mount_flags = MS_BIND | MS_REC | MS_PRIVATE;
|
|
if (mount("/syzcgroup/unified", "./syz-tmp/newroot/syzcgroup/unified", NULL, bind_mount_flags, NULL)) {
|
|
debug("mount(cgroup2, MS_BIND) failed: %d\n", errno);
|
|
}
|
|
if (mount("/syzcgroup/cpu", "./syz-tmp/newroot/syzcgroup/cpu", NULL, bind_mount_flags, NULL)) {
|
|
debug("mount(cgroup/cpu, MS_BIND) failed: %d\n", errno);
|
|
}
|
|
if (mount("/syzcgroup/net", "./syz-tmp/newroot/syzcgroup/net", NULL, bind_mount_flags, NULL)) {
|
|
debug("mount(cgroup/net, MS_BIND) failed: %d\n", errno);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
|
|
#include <errno.h>
|
|
#include <sys/mount.h>
|
|
|
|
static void setup_common()
|
|
{
|
|
if (mount(0, "/sys/fs/fuse/connections", "fusectl", 0, 0)) {
|
|
debug("mount(fusectl) failed: %d\n", errno);
|
|
}
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_CGROUPS
|
|
setup_cgroups();
|
|
#endif
|
|
}
|
|
|
|
#include <sched.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/time.h>
|
|
#include <sys/wait.h>
|
|
|
|
static void loop();
|
|
|
|
static void sandbox_common()
|
|
{
|
|
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
|
|
setpgrp();
|
|
setsid();
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_init_net_socket
|
|
int netns = open("/proc/self/ns/net", O_RDONLY);
|
|
if (netns == -1)
|
|
fail("open(/proc/self/ns/net) failed");
|
|
if (dup2(netns, kInitNetNsFd) < 0)
|
|
fail("dup2(netns, kInitNetNsFd) failed");
|
|
close(netns);
|
|
#endif
|
|
|
|
struct rlimit rlim;
|
|
#if SYZ_EXECUTOR
|
|
rlim.rlim_cur = rlim.rlim_max = (200 << 20) +
|
|
(kMaxThreads * kCoverSize + kExtraCoverSize) * sizeof(void*);
|
|
#else
|
|
rlim.rlim_cur = rlim.rlim_max = (200 << 20);
|
|
#endif
|
|
setrlimit(RLIMIT_AS, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 32 << 20;
|
|
setrlimit(RLIMIT_MEMLOCK, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 136 << 20;
|
|
setrlimit(RLIMIT_FSIZE, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 1 << 20;
|
|
setrlimit(RLIMIT_STACK, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 0;
|
|
setrlimit(RLIMIT_CORE, &rlim);
|
|
rlim.rlim_cur = rlim.rlim_max = 256;
|
|
setrlimit(RLIMIT_NOFILE, &rlim);
|
|
if (unshare(CLONE_NEWNS)) {
|
|
debug("unshare(CLONE_NEWNS): %d\n", errno);
|
|
}
|
|
if (unshare(CLONE_NEWIPC)) {
|
|
debug("unshare(CLONE_NEWIPC): %d\n", errno);
|
|
}
|
|
if (unshare(0x02000000)) {
|
|
debug("unshare(CLONE_NEWCGROUP): %d\n", errno);
|
|
}
|
|
if (unshare(CLONE_NEWUTS)) {
|
|
debug("unshare(CLONE_NEWUTS): %d\n", errno);
|
|
}
|
|
if (unshare(CLONE_SYSVSEM)) {
|
|
debug("unshare(CLONE_SYSVSEM): %d\n", errno);
|
|
}
|
|
typedef struct {
|
|
const char* name;
|
|
const char* value;
|
|
} sysctl_t;
|
|
static const sysctl_t sysctls[] = {
|
|
{"/proc/sys/kernel/shmmax", "16777216"},
|
|
{"/proc/sys/kernel/shmall", "536870912"},
|
|
{"/proc/sys/kernel/shmmni", "1024"},
|
|
{"/proc/sys/kernel/msgmax", "8192"},
|
|
{"/proc/sys/kernel/msgmni", "1024"},
|
|
{"/proc/sys/kernel/msgmnb", "1024"},
|
|
{"/proc/sys/kernel/sem", "1024 1048576 500 1024"},
|
|
};
|
|
unsigned i;
|
|
for (i = 0; i < sizeof(sysctls) / sizeof(sysctls[0]); i++)
|
|
write_file(sysctls[i].name, sysctls[i].value);
|
|
}
|
|
|
|
int wait_for_loop(int pid)
|
|
{
|
|
if (pid < 0)
|
|
fail("sandbox fork failed");
|
|
debug("spawned loop pid %d\n", pid);
|
|
int status = 0;
|
|
while (waitpid(-1, &status, __WALL) != pid) {
|
|
}
|
|
return WEXITSTATUS(status);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE || SYZ_SANDBOX_NAMESPACE
|
|
#include <linux/capability.h>
|
|
|
|
static void drop_caps(void)
|
|
{
|
|
struct __user_cap_header_struct cap_hdr = {};
|
|
struct __user_cap_data_struct cap_data[2] = {};
|
|
cap_hdr.version = _LINUX_CAPABILITY_VERSION_3;
|
|
cap_hdr.pid = getpid();
|
|
if (syscall(SYS_capget, &cap_hdr, &cap_data))
|
|
fail("capget failed");
|
|
const int drop = (1 << CAP_SYS_PTRACE) | (1 << CAP_SYS_NICE);
|
|
cap_data[0].effective &= ~drop;
|
|
cap_data[0].permitted &= ~drop;
|
|
cap_data[0].inheritable &= ~drop;
|
|
if (syscall(SYS_capset, &cap_hdr, &cap_data))
|
|
fail("capset failed");
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE
|
|
#include <sched.h>
|
|
#include <sys/types.h>
|
|
|
|
static int do_sandbox_none(void)
|
|
{
|
|
if (unshare(CLONE_NEWPID)) {
|
|
debug("unshare(CLONE_NEWPID): %d\n", errno);
|
|
}
|
|
int pid = fork();
|
|
if (pid != 0)
|
|
return wait_for_loop(pid);
|
|
|
|
setup_common();
|
|
sandbox_common();
|
|
drop_caps();
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
initialize_netdevices_init();
|
|
#endif
|
|
if (unshare(CLONE_NEWNET)) {
|
|
debug("unshare(CLONE_NEWNET): %d\n", errno);
|
|
}
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE
|
|
initialize_tun();
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
initialize_netdevices();
|
|
#endif
|
|
loop();
|
|
doexit(1);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_SETUID
|
|
#include <grp.h>
|
|
#include <sched.h>
|
|
#include <sys/prctl.h>
|
|
|
|
#define SYZ_HAVE_SANDBOX_SETUID 1
|
|
static int do_sandbox_setuid(void)
|
|
{
|
|
if (unshare(CLONE_NEWPID)) {
|
|
debug("unshare(CLONE_NEWPID): %d\n", errno);
|
|
}
|
|
int pid = fork();
|
|
if (pid != 0)
|
|
return wait_for_loop(pid);
|
|
|
|
setup_common();
|
|
sandbox_common();
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
initialize_netdevices_init();
|
|
#endif
|
|
if (unshare(CLONE_NEWNET)) {
|
|
debug("unshare(CLONE_NEWNET): %d\n", errno);
|
|
}
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE
|
|
initialize_tun();
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
initialize_netdevices();
|
|
#endif
|
|
|
|
const int nobody = 65534;
|
|
if (setgroups(0, NULL))
|
|
fail("failed to setgroups");
|
|
if (syscall(SYS_setresgid, nobody, nobody, nobody))
|
|
fail("failed to setresgid");
|
|
if (syscall(SYS_setresuid, nobody, nobody, nobody))
|
|
fail("failed to setresuid");
|
|
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
|
|
|
|
loop();
|
|
doexit(1);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NAMESPACE
|
|
#include <sched.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/mount.h>
|
|
|
|
static int real_uid;
|
|
static int real_gid;
|
|
__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20];
|
|
|
|
static int namespace_sandbox_proc(void* arg)
|
|
{
|
|
sandbox_common();
|
|
write_file("/proc/self/setgroups", "deny");
|
|
if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid))
|
|
fail("write of /proc/self/uid_map failed");
|
|
if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid))
|
|
fail("write of /proc/self/gid_map failed");
|
|
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
initialize_netdevices_init();
|
|
#endif
|
|
if (unshare(CLONE_NEWNET))
|
|
fail("unshare(CLONE_NEWNET)");
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE
|
|
initialize_tun();
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
initialize_netdevices();
|
|
#endif
|
|
|
|
if (mkdir("./syz-tmp", 0777))
|
|
fail("mkdir(syz-tmp) failed");
|
|
if (mount("", "./syz-tmp", "tmpfs", 0, NULL))
|
|
fail("mount(tmpfs) failed");
|
|
if (mkdir("./syz-tmp/newroot", 0777))
|
|
fail("mkdir failed");
|
|
if (mkdir("./syz-tmp/newroot/dev", 0700))
|
|
fail("mkdir failed");
|
|
unsigned bind_mount_flags = MS_BIND | MS_REC | MS_PRIVATE;
|
|
if (mount("/dev", "./syz-tmp/newroot/dev", NULL, bind_mount_flags, NULL))
|
|
fail("mount(dev) failed");
|
|
if (mkdir("./syz-tmp/newroot/proc", 0700))
|
|
fail("mkdir failed");
|
|
if (mount(NULL, "./syz-tmp/newroot/proc", "proc", 0, NULL))
|
|
fail("mount(proc) failed");
|
|
if (mkdir("./syz-tmp/newroot/selinux", 0700))
|
|
fail("mkdir failed");
|
|
const char* selinux_path = "./syz-tmp/newroot/selinux";
|
|
if (mount("/selinux", selinux_path, NULL, bind_mount_flags, NULL)) {
|
|
if (errno != ENOENT)
|
|
fail("mount(/selinux) failed");
|
|
if (mount("/sys/fs/selinux", selinux_path, NULL, bind_mount_flags, NULL) && errno != ENOENT)
|
|
fail("mount(/sys/fs/selinux) failed");
|
|
}
|
|
if (mkdir("./syz-tmp/newroot/sys", 0700))
|
|
fail("mkdir failed");
|
|
if (mount("/sys", "./syz-tmp/newroot/sys", 0, bind_mount_flags, NULL))
|
|
fail("mount(sysfs) failed");
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_CGROUPS
|
|
initialize_cgroups();
|
|
#endif
|
|
if (mkdir("./syz-tmp/pivot", 0777))
|
|
fail("mkdir failed");
|
|
if (syscall(SYS_pivot_root, "./syz-tmp", "./syz-tmp/pivot")) {
|
|
debug("pivot_root failed\n");
|
|
if (chdir("./syz-tmp"))
|
|
fail("chdir failed");
|
|
} else {
|
|
debug("pivot_root OK\n");
|
|
if (chdir("/"))
|
|
fail("chdir failed");
|
|
if (umount2("./pivot", MNT_DETACH))
|
|
fail("umount failed");
|
|
}
|
|
if (chroot("./newroot"))
|
|
fail("chroot failed");
|
|
if (chdir("/"))
|
|
fail("chdir failed");
|
|
drop_caps();
|
|
|
|
loop();
|
|
doexit(1);
|
|
}
|
|
|
|
#define SYZ_HAVE_SANDBOX_NAMESPACE 1
|
|
static int do_sandbox_namespace(void)
|
|
{
|
|
int pid;
|
|
|
|
setup_common();
|
|
real_uid = getuid();
|
|
real_gid = getgid();
|
|
mprotect(sandbox_stack, 4096, PROT_NONE);
|
|
pid = clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64],
|
|
CLONE_NEWUSER | CLONE_NEWPID, 0);
|
|
return wait_for_loop(pid);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
|
|
#include <fcntl.h>
|
|
#include <grp.h>
|
|
#include <sys/xattr.h>
|
|
|
|
#define AID_NET_BT_ADMIN 3001
|
|
#define AID_NET_BT 3002
|
|
#define AID_INET 3003
|
|
#define AID_EVERYBODY 9997
|
|
#define AID_APP 10000
|
|
|
|
#define UNTRUSTED_APP_UID AID_APP + 999
|
|
#define UNTRUSTED_APP_GID AID_APP + 999
|
|
|
|
const char* SELINUX_CONTEXT_UNTRUSTED_APP = "u:r:untrusted_app:s0:c512,c768";
|
|
const char* SELINUX_LABEL_APP_DATA_FILE = "u:object_r:app_data_file:s0:c512,c768";
|
|
const char* SELINUX_CONTEXT_FILE = "/proc/thread-self/attr/current";
|
|
const char* SELINUX_XATTR_NAME = "security.selinux";
|
|
|
|
const gid_t UNTRUSTED_APP_GROUPS[] = {UNTRUSTED_APP_GID, AID_NET_BT_ADMIN, AID_NET_BT, AID_INET, AID_EVERYBODY};
|
|
const size_t UNTRUSTED_APP_NUM_GROUPS = sizeof(UNTRUSTED_APP_GROUPS) / sizeof(UNTRUSTED_APP_GROUPS[0]);
|
|
static void syz_getcon(char* context, size_t context_size)
|
|
{
|
|
int fd = open(SELINUX_CONTEXT_FILE, O_RDONLY);
|
|
|
|
if (fd < 0)
|
|
fail("getcon: Couldn't open %s", SELINUX_CONTEXT_FILE);
|
|
|
|
ssize_t nread = read(fd, context, context_size);
|
|
|
|
close(fd);
|
|
|
|
if (nread <= 0)
|
|
fail("getcon: Failed to read from %s", SELINUX_CONTEXT_FILE);
|
|
if (context[nread - 1] == '\n')
|
|
context[nread - 1] = '\0';
|
|
}
|
|
static void syz_setcon(const char* context)
|
|
{
|
|
char new_context[512];
|
|
int fd = open(SELINUX_CONTEXT_FILE, O_WRONLY);
|
|
|
|
if (fd < 0)
|
|
fail("setcon: Could not open %s", SELINUX_CONTEXT_FILE);
|
|
|
|
ssize_t bytes_written = write(fd, context, strlen(context));
|
|
close(fd);
|
|
|
|
if (bytes_written != (ssize_t)strlen(context))
|
|
fail("setcon: Could not write entire context. Wrote %zi, expected %zu", bytes_written, strlen(context));
|
|
syz_getcon(new_context, sizeof(new_context));
|
|
|
|
if (strcmp(context, new_context) != 0)
|
|
fail("setcon: Failed to change to %s, context is %s", context, new_context);
|
|
}
|
|
static int syz_getfilecon(const char* path, char* context, size_t context_size)
|
|
{
|
|
int length = getxattr(path, SELINUX_XATTR_NAME, context, context_size);
|
|
|
|
if (length == -1)
|
|
fail("getfilecon: getxattr failed");
|
|
|
|
return length;
|
|
}
|
|
static void syz_setfilecon(const char* path, const char* context)
|
|
{
|
|
char new_context[512];
|
|
|
|
if (setxattr(path, SELINUX_XATTR_NAME, context, strlen(context) + 1, 0) != 0)
|
|
fail("setfilecon: setxattr failed");
|
|
|
|
if (syz_getfilecon(path, new_context, sizeof(new_context)) <= 0)
|
|
fail("setfilecon: getfilecon failed");
|
|
|
|
if (strcmp(context, new_context) != 0)
|
|
fail("setfilecon: could not set context to %s, currently %s", context, new_context);
|
|
}
|
|
|
|
#define SYZ_HAVE_SANDBOX_ANDROID_UNTRUSTED_APP 1
|
|
static int do_sandbox_android_untrusted_app(void)
|
|
{
|
|
setup_common();
|
|
sandbox_common();
|
|
|
|
if (chown(".", UNTRUSTED_APP_UID, UNTRUSTED_APP_UID) != 0)
|
|
fail("chmod failed");
|
|
|
|
if (setgroups(UNTRUSTED_APP_NUM_GROUPS, UNTRUSTED_APP_GROUPS) != 0)
|
|
fail("setgroups failed");
|
|
|
|
if (setresgid(UNTRUSTED_APP_GID, UNTRUSTED_APP_GID, UNTRUSTED_APP_GID) != 0)
|
|
fail("setresgid failed");
|
|
|
|
if (setresuid(UNTRUSTED_APP_UID, UNTRUSTED_APP_UID, UNTRUSTED_APP_UID) != 0)
|
|
fail("setresuid failed");
|
|
|
|
syz_setfilecon(".", SELINUX_LABEL_APP_DATA_FILE);
|
|
syz_setcon(SELINUX_CONTEXT_UNTRUSTED_APP);
|
|
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE
|
|
initialize_tun();
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_NETDEV
|
|
initialize_netdevices_init();
|
|
initialize_netdevices();
|
|
#endif
|
|
|
|
loop();
|
|
doexit(1);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT && SYZ_USE_TMP_DIR
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mount.h>
|
|
|
|
#define FS_IOC_SETFLAGS _IOW('f', 2, long)
|
|
static void remove_dir(const char* dir)
|
|
{
|
|
DIR* dp;
|
|
struct dirent* ep;
|
|
int iter = 0;
|
|
retry:
|
|
while (umount2(dir, MNT_DETACH) == 0) {
|
|
debug("umount(%s)\n", dir);
|
|
}
|
|
dp = opendir(dir);
|
|
if (dp == NULL) {
|
|
if (errno == EMFILE) {
|
|
exitf("opendir(%s) failed due to NOFILE, exiting", dir);
|
|
}
|
|
exitf("opendir(%s) failed", dir);
|
|
}
|
|
while ((ep = readdir(dp))) {
|
|
if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0)
|
|
continue;
|
|
char filename[FILENAME_MAX];
|
|
snprintf(filename, sizeof(filename), "%s/%s", dir, ep->d_name);
|
|
while (umount2(filename, MNT_DETACH) == 0) {
|
|
debug("umount(%s)\n", filename);
|
|
}
|
|
struct stat st;
|
|
if (lstat(filename, &st))
|
|
exitf("lstat(%s) failed", filename);
|
|
if (S_ISDIR(st.st_mode)) {
|
|
remove_dir(filename);
|
|
continue;
|
|
}
|
|
int i;
|
|
for (i = 0;; i++) {
|
|
if (unlink(filename) == 0)
|
|
break;
|
|
if (errno == EPERM) {
|
|
int fd = open(filename, O_RDONLY);
|
|
if (fd != -1) {
|
|
long flags = 0;
|
|
if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == 0)
|
|
debug("reset FS_XFLAG_IMMUTABLE\n");
|
|
close(fd);
|
|
continue;
|
|
}
|
|
}
|
|
if (errno == EROFS) {
|
|
debug("ignoring EROFS\n");
|
|
break;
|
|
}
|
|
if (errno != EBUSY || i > 100)
|
|
exitf("unlink(%s) failed", filename);
|
|
debug("umount(%s)\n", filename);
|
|
if (umount2(filename, MNT_DETACH))
|
|
exitf("umount(%s) failed", filename);
|
|
}
|
|
}
|
|
closedir(dp);
|
|
int i;
|
|
for (i = 0;; i++) {
|
|
if (rmdir(dir) == 0)
|
|
break;
|
|
if (i < 100) {
|
|
if (errno == EPERM) {
|
|
int fd = open(dir, O_RDONLY);
|
|
if (fd != -1) {
|
|
long flags = 0;
|
|
if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == 0)
|
|
debug("reset FS_XFLAG_IMMUTABLE\n");
|
|
close(fd);
|
|
continue;
|
|
}
|
|
}
|
|
if (errno == EROFS) {
|
|
debug("ignoring EROFS\n");
|
|
break;
|
|
}
|
|
if (errno == EBUSY) {
|
|
debug("umount(%s)\n", dir);
|
|
if (umount2(dir, MNT_DETACH))
|
|
exitf("umount(%s) failed", dir);
|
|
continue;
|
|
}
|
|
if (errno == ENOTEMPTY) {
|
|
if (iter < 100) {
|
|
iter++;
|
|
goto retry;
|
|
}
|
|
}
|
|
}
|
|
exitf("rmdir(%s) failed", dir);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_FAULT_INJECTION
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static int inject_fault(int nth)
|
|
{
|
|
int fd;
|
|
fd = open("/proc/thread-self/fail-nth", O_RDWR);
|
|
if (fd == -1)
|
|
exitf("failed to open /proc/thread-self/fail-nth");
|
|
char buf[16];
|
|
sprintf(buf, "%d", nth + 1);
|
|
if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf))
|
|
exitf("failed to write /proc/thread-self/fail-nth");
|
|
return fd;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR
|
|
static int fault_injected(int fail_fd)
|
|
{
|
|
char buf[16];
|
|
int n = read(fail_fd, buf, sizeof(buf) - 1);
|
|
if (n <= 0)
|
|
exitf("failed to read /proc/thread-self/fail-nth");
|
|
int res = n == 2 && buf[0] == '0' && buf[1] == '\n';
|
|
buf[0] = '0';
|
|
if (write(fail_fd, buf, 1) != 1)
|
|
exitf("failed to write /proc/thread-self/fail-nth");
|
|
close(fail_fd);
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
static void kill_and_wait(int pid, int* status)
|
|
{
|
|
kill(-pid, SIGKILL);
|
|
kill(pid, SIGKILL);
|
|
int i;
|
|
for (i = 0; i < 100; i++) {
|
|
if (waitpid(-1, status, WNOHANG | __WALL) == pid)
|
|
return;
|
|
usleep(1000);
|
|
}
|
|
debug("kill is not working\n");
|
|
DIR* dir = opendir("/sys/fs/fuse/connections");
|
|
if (dir) {
|
|
for (;;) {
|
|
struct dirent* ent = readdir(dir);
|
|
if (!ent)
|
|
break;
|
|
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
|
|
continue;
|
|
char abort[300];
|
|
snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort", ent->d_name);
|
|
int fd = open(abort, O_WRONLY);
|
|
if (fd == -1) {
|
|
debug("failed to open %s: %d\n", abort, errno);
|
|
continue;
|
|
}
|
|
debug("aborting fuse conn %s\n", ent->d_name);
|
|
if (write(fd, abort, 1) < 0) {
|
|
debug("failed to abort: %d\n", errno);
|
|
}
|
|
close(fd);
|
|
}
|
|
closedir(dir);
|
|
} else {
|
|
debug("failed to open /sys/fs/fuse/connections: %d\n", errno);
|
|
}
|
|
while (waitpid(-1, status, __WALL) != pid) {
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT && (SYZ_ENABLE_CGROUPS || SYZ_RESET_NET_NAMESPACE)
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#define SYZ_HAVE_SETUP_LOOP 1
|
|
static void setup_loop()
|
|
{
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_CGROUPS
|
|
setup_cgroups_loop();
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_RESET_NET_NAMESPACE
|
|
checkpoint_net_namespace();
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT && (SYZ_RESET_NET_NAMESPACE || __NR_syz_mount_image || __NR_syz_read_part_table)
|
|
#define SYZ_HAVE_RESET_LOOP 1
|
|
static void reset_loop()
|
|
{
|
|
#if SYZ_EXECUTOR || __NR_syz_mount_image || __NR_syz_read_part_table
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "/dev/loop%llu", procid);
|
|
int loopfd = open(buf, O_RDWR);
|
|
if (loopfd != -1) {
|
|
ioctl(loopfd, LOOP_CLR_FD, 0);
|
|
close(loopfd);
|
|
}
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_RESET_NET_NAMESPACE
|
|
reset_net_namespace();
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT
|
|
#include <sys/prctl.h>
|
|
|
|
#define SYZ_HAVE_SETUP_TEST 1
|
|
static void setup_test()
|
|
{
|
|
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
|
|
setpgrp();
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_CGROUPS
|
|
setup_cgroups_test();
|
|
#endif
|
|
write_file("/proc/self/oom_score_adj", "1000");
|
|
#if SYZ_EXECUTOR || SYZ_TUN_ENABLE
|
|
flush_tun();
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_CLOSE_FDS
|
|
#define SYZ_HAVE_CLOSE_FDS 1
|
|
static void close_fds()
|
|
{
|
|
#if SYZ_EXECUTOR
|
|
if (!flag_enable_close_fds)
|
|
return;
|
|
#endif
|
|
int fd;
|
|
for (fd = 3; fd < 30; fd++)
|
|
close(fd);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_FAULT_INJECTION
|
|
#include <errno.h>
|
|
|
|
static void setup_fault()
|
|
{
|
|
static struct {
|
|
const char* file;
|
|
const char* val;
|
|
bool fatal;
|
|
} files[] = {
|
|
{"/sys/kernel/debug/failslab/ignore-gfp-wait", "N", true},
|
|
{"/sys/kernel/debug/fail_futex/ignore-private", "N", false},
|
|
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem", "N", false},
|
|
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-wait", "N", false},
|
|
{"/sys/kernel/debug/fail_page_alloc/min-order", "0", false},
|
|
};
|
|
unsigned i;
|
|
for (i = 0; i < sizeof(files) / sizeof(files[0]); i++) {
|
|
if (!write_file(files[i].file, files[i].val)) {
|
|
debug("failed to write %s: %d\n", files[i].file, errno);
|
|
if (files[i].fatal)
|
|
fail("failed to write %s", files[i].file);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_LEAK
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#define KMEMLEAK_FILE "/sys/kernel/debug/kmemleak"
|
|
|
|
static void setup_leak()
|
|
{
|
|
if (!write_file(KMEMLEAK_FILE, "scan"))
|
|
fail("failed to write %s", KMEMLEAK_FILE);
|
|
sleep(5);
|
|
if (!write_file(KMEMLEAK_FILE, "scan"))
|
|
fail("failed to write %s", KMEMLEAK_FILE);
|
|
if (!write_file(KMEMLEAK_FILE, "clear"))
|
|
fail("failed to write %s", KMEMLEAK_FILE);
|
|
}
|
|
|
|
#define SYZ_HAVE_LEAK_CHECK 1
|
|
#if SYZ_EXECUTOR
|
|
static void check_leaks(char** frames, int nframes)
|
|
#else
|
|
static void check_leaks(void)
|
|
#endif
|
|
{
|
|
int fd = open(KMEMLEAK_FILE, O_RDWR);
|
|
if (fd == -1)
|
|
fail("failed to open(\"%s\")", KMEMLEAK_FILE);
|
|
uint64 start = current_time_ms();
|
|
if (write(fd, "scan", 4) != 4)
|
|
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
|
|
sleep(1);
|
|
while (current_time_ms() - start < 4 * 1000)
|
|
sleep(1);
|
|
if (write(fd, "scan", 4) != 4)
|
|
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
|
|
static char buf[128 << 10];
|
|
ssize_t n = read(fd, buf, sizeof(buf) - 1);
|
|
if (n < 0)
|
|
fail("failed to read(%s)", KMEMLEAK_FILE);
|
|
int nleaks = 0;
|
|
if (n != 0) {
|
|
sleep(1);
|
|
if (write(fd, "scan", 4) != 4)
|
|
fail("failed to write(%s, \"scan\")", KMEMLEAK_FILE);
|
|
if (lseek(fd, 0, SEEK_SET) < 0)
|
|
fail("failed to lseek(%s)", KMEMLEAK_FILE);
|
|
n = read(fd, buf, sizeof(buf) - 1);
|
|
if (n < 0)
|
|
fail("failed to read(%s)", KMEMLEAK_FILE);
|
|
buf[n] = 0;
|
|
char* pos = buf;
|
|
char* end = buf + n;
|
|
while (pos < end) {
|
|
char* next = strstr(pos + 1, "unreferenced object");
|
|
if (!next)
|
|
next = end;
|
|
char prev = *next;
|
|
*next = 0;
|
|
#if SYZ_EXECUTOR
|
|
int f;
|
|
for (f = 0; f < nframes; f++) {
|
|
if (strstr(pos, frames[f]))
|
|
break;
|
|
}
|
|
if (f != nframes) {
|
|
*next = prev;
|
|
pos = next;
|
|
continue;
|
|
}
|
|
#endif
|
|
fprintf(stderr, "BUG: memory leak\n%s\n", pos);
|
|
*next = prev;
|
|
pos = next;
|
|
nleaks++;
|
|
}
|
|
}
|
|
if (write(fd, "clear", 5) != 5)
|
|
fail("failed to write(%s, \"clear\")", KMEMLEAK_FILE);
|
|
close(fd);
|
|
if (nleaks)
|
|
doexit(1);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_ENABLE_BINFMT_MISC
|
|
#include <fcntl.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
static void setup_binfmt_misc()
|
|
{
|
|
if (mount(0, "/proc/sys/fs/binfmt_misc", "binfmt_misc", 0, 0)) {
|
|
debug("mount(binfmt_misc) failed: %d\n", errno);
|
|
}
|
|
write_file("/proc/sys/fs/binfmt_misc/register", ":syz0:M:0:\x01::./file0:");
|
|
write_file("/proc/sys/fs/binfmt_misc/register", ":syz1:M:1:\x02::./file0:POC");
|
|
}
|
|
#endif
|
|
|
|
#elif GOOS_test
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_mmap
|
|
#include <sys/mman.h>
|
|
static long syz_mmap(volatile long a0, volatile long a1)
|
|
{
|
|
return (long)mmap((void*)a0, a1, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_errno
|
|
#include <errno.h>
|
|
static long syz_errno(volatile long v)
|
|
{
|
|
errno = v;
|
|
return v == 0 ? 0 : -1;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_exit
|
|
static long syz_exit(volatile long status)
|
|
{
|
|
_exit(status);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_compare
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
static long syz_compare(volatile long want, volatile long want_len, volatile long got, volatile long got_len)
|
|
{
|
|
if (want_len != got_len) {
|
|
debug("syz_compare: want_len=%lu got_len=%lu\n", want_len, got_len);
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
if (memcmp((void*)want, (void*)got, want_len)) {
|
|
debug("syz_compare: data differs, want:\n");
|
|
debug_dump_data((char*)want, want_len);
|
|
debug("got:\n");
|
|
debug_dump_data((char*)got, got_len);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_compare_int
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
static long syz_compare_int(volatile long n, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, n);
|
|
long v0 = va_arg(args, long);
|
|
long v1 = va_arg(args, long);
|
|
long v2 = va_arg(args, long);
|
|
long v3 = va_arg(args, long);
|
|
va_end(args);
|
|
if (n < 2 || n > 4)
|
|
return errno = E2BIG, -1;
|
|
if (n <= 2 && v2 != 0)
|
|
return errno = EFAULT, -1;
|
|
if (n <= 3 && v3 != 0)
|
|
return errno = EFAULT, -1;
|
|
if (v0 != v1)
|
|
return errno = EINVAL, -1;
|
|
if (n > 2 && v0 != v2)
|
|
return errno = EINVAL, -1;
|
|
if (n > 3 && v0 != v3)
|
|
return errno = EINVAL, -1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE
|
|
static void loop();
|
|
static int do_sandbox_none(void)
|
|
{
|
|
loop();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#elif GOOS_windows
|
|
|
|
#include <windows.h>
|
|
|
|
#include "common.h"
|
|
|
|
#if SYZ_EXECUTOR || SYZ_HANDLE_SEGV
|
|
static void install_segv_handler()
|
|
{
|
|
}
|
|
|
|
#define NONFAILING(...) \
|
|
__try { \
|
|
__VA_ARGS__; \
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) { \
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER
|
|
static uint64 current_time_ms()
|
|
{
|
|
return GetTickCount64();
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_THREADED || SYZ_REPEAT && SYZ_EXECUTOR_USES_FORK_SERVER
|
|
static void sleep_ms(uint64 ms)
|
|
{
|
|
Sleep(ms);
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_THREADED
|
|
static void thread_start(void* (*fn)(void*), void* arg)
|
|
{
|
|
HANDLE th = CreateThread(NULL, 128 << 10, (LPTHREAD_START_ROUTINE)fn, arg, 0, NULL);
|
|
if (th == NULL)
|
|
exitf("CreateThread failed");
|
|
}
|
|
|
|
struct event_t {
|
|
CRITICAL_SECTION cs;
|
|
CONDITION_VARIABLE cv;
|
|
int state;
|
|
};
|
|
|
|
static void event_init(event_t* ev)
|
|
{
|
|
InitializeCriticalSection(&ev->cs);
|
|
InitializeConditionVariable(&ev->cv);
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_reset(event_t* ev)
|
|
{
|
|
ev->state = 0;
|
|
}
|
|
|
|
static void event_set(event_t* ev)
|
|
{
|
|
EnterCriticalSection(&ev->cs);
|
|
if (ev->state)
|
|
fail("event already set");
|
|
ev->state = 1;
|
|
LeaveCriticalSection(&ev->cs);
|
|
WakeAllConditionVariable(&ev->cv);
|
|
}
|
|
|
|
static void event_wait(event_t* ev)
|
|
{
|
|
EnterCriticalSection(&ev->cs);
|
|
while (!ev->state)
|
|
SleepConditionVariableCS(&ev->cv, &ev->cs, INFINITE);
|
|
LeaveCriticalSection(&ev->cs);
|
|
}
|
|
|
|
static int event_isset(event_t* ev)
|
|
{
|
|
EnterCriticalSection(&ev->cs);
|
|
int res = ev->state;
|
|
LeaveCriticalSection(&ev->cs);
|
|
return res;
|
|
}
|
|
|
|
static int event_timedwait(event_t* ev, uint64 timeout_ms)
|
|
{
|
|
EnterCriticalSection(&ev->cs);
|
|
uint64 start = current_time_ms();
|
|
for (;;) {
|
|
if (ev->state)
|
|
break;
|
|
uint64 now = current_time_ms();
|
|
if (now - start > timeout_ms)
|
|
break;
|
|
SleepConditionVariableCS(&ev->cv, &ev->cs, timeout_ms - (now - start));
|
|
}
|
|
int res = ev->state;
|
|
LeaveCriticalSection(&ev->cs);
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_SANDBOX_NONE
|
|
static void loop();
|
|
static int do_sandbox_none(void)
|
|
{
|
|
loop();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#else
|
|
#error "unknown OS"
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || __NR_syz_execute_func
|
|
static long syz_execute_func(volatile long text)
|
|
{
|
|
volatile long p[8] = {0};
|
|
(void)p;
|
|
#if GOARCH_amd64
|
|
asm volatile("" ::"r"(0l), "r"(1l), "r"(2l), "r"(3l), "r"(4l), "r"(5l), "r"(6l),
|
|
"r"(7l), "r"(8l), "r"(9l), "r"(10l), "r"(11l), "r"(12l), "r"(13l));
|
|
#endif
|
|
NONFAILING(((void (*)(void))(text))());
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_THREADED
|
|
struct thread_t {
|
|
int created, call;
|
|
event_t ready, done;
|
|
};
|
|
|
|
static struct thread_t threads[16];
|
|
static void execute_call(int call);
|
|
static int running;
|
|
|
|
static void* thr(void* arg)
|
|
{
|
|
struct thread_t* th = (struct thread_t*)arg;
|
|
for (;;) {
|
|
event_wait(&th->ready);
|
|
event_reset(&th->ready);
|
|
execute_call(th->call);
|
|
__atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
|
|
event_set(&th->done);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if SYZ_REPEAT
|
|
static void execute_one(void)
|
|
#else
|
|
static void loop(void)
|
|
#endif
|
|
{
|
|
#if SYZ_REPRO
|
|
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {
|
|
}
|
|
#endif
|
|
#if SYZ_TRACE
|
|
fprintf(stderr, "### start\n");
|
|
#endif
|
|
int i, call, thread;
|
|
#if SYZ_COLLIDE
|
|
int collide = 0;
|
|
again:
|
|
#endif
|
|
for (call = 0; call < /*NUM_CALLS*/; call++) {
|
|
for (thread = 0; thread < (int)(sizeof(threads) / sizeof(threads[0])); thread++) {
|
|
struct thread_t* th = &threads[thread];
|
|
if (!th->created) {
|
|
th->created = 1;
|
|
event_init(&th->ready);
|
|
event_init(&th->done);
|
|
event_set(&th->done);
|
|
thread_start(thr, th);
|
|
}
|
|
if (!event_isset(&th->done))
|
|
continue;
|
|
event_reset(&th->done);
|
|
th->call = call;
|
|
__atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
|
|
event_set(&th->ready);
|
|
#if SYZ_COLLIDE
|
|
if (collide && (call % 2) == 0)
|
|
break;
|
|
#endif
|
|
event_timedwait(&th->done, /*CALL_TIMEOUT*/);
|
|
break;
|
|
}
|
|
}
|
|
for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
|
|
sleep_ms(1);
|
|
#if SYZ_HAVE_CLOSE_FDS
|
|
close_fds();
|
|
#endif
|
|
#if SYZ_COLLIDE
|
|
if (!collide) {
|
|
collide = 1;
|
|
goto again;
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR || SYZ_REPEAT
|
|
static void execute_one(void);
|
|
#if SYZ_EXECUTOR_USES_FORK_SERVER
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
#if GOOS_linux
|
|
#define WAIT_FLAGS __WALL
|
|
#else
|
|
#define WAIT_FLAGS 0
|
|
#endif
|
|
|
|
#if SYZ_EXECUTOR
|
|
static void reply_handshake();
|
|
#endif
|
|
|
|
static void loop(void)
|
|
{
|
|
#if SYZ_HAVE_SETUP_LOOP
|
|
setup_loop();
|
|
#endif
|
|
#if SYZ_EXECUTOR
|
|
reply_handshake();
|
|
#endif
|
|
#if SYZ_EXECUTOR && GOOS_akaros
|
|
int child_pipe[2];
|
|
if (pipe(child_pipe))
|
|
fail("pipe failed");
|
|
#endif
|
|
int iter;
|
|
#if SYZ_REPEAT_TIMES
|
|
for (iter = 0; iter < /*REPEAT_TIMES*/; iter++) {
|
|
#else
|
|
for (iter = 0;; iter++) {
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_USE_TMP_DIR
|
|
char cwdbuf[32];
|
|
sprintf(cwdbuf, "./%d", iter);
|
|
if (mkdir(cwdbuf, 0777))
|
|
fail("failed to mkdir");
|
|
#endif
|
|
#if SYZ_HAVE_RESET_LOOP
|
|
reset_loop();
|
|
#endif
|
|
#if SYZ_EXECUTOR
|
|
receive_execute();
|
|
#endif
|
|
int pid = fork();
|
|
if (pid < 0)
|
|
fail("clone failed");
|
|
if (pid == 0) {
|
|
#if SYZ_EXECUTOR || SYZ_USE_TMP_DIR
|
|
if (chdir(cwdbuf))
|
|
fail("failed to chdir");
|
|
#endif
|
|
#if SYZ_HAVE_SETUP_TEST
|
|
setup_test();
|
|
#endif
|
|
#if GOOS_akaros
|
|
#if SYZ_EXECUTOR
|
|
dup2(child_pipe[0], kInPipeFd);
|
|
close(child_pipe[0]);
|
|
close(child_pipe[1]);
|
|
#endif
|
|
execl(program_name, program_name, "child", NULL);
|
|
fail("execl failed");
|
|
#else
|
|
#if SYZ_EXECUTOR
|
|
close(kInPipeFd);
|
|
#endif
|
|
#if SYZ_EXECUTOR && SYZ_EXECUTOR_USES_SHMEM
|
|
close(kOutPipeFd);
|
|
#endif
|
|
execute_one();
|
|
#if SYZ_HAVE_CLOSE_FDS && !SYZ_THREADED
|
|
close_fds();
|
|
#endif
|
|
doexit(0);
|
|
#endif
|
|
}
|
|
debug("spawned worker pid %d\n", pid);
|
|
|
|
#if SYZ_EXECUTOR && GOOS_akaros
|
|
resend_execute(child_pipe[1]);
|
|
#endif
|
|
int status = 0;
|
|
uint64 start = current_time_ms();
|
|
#if SYZ_EXECUTOR && SYZ_EXECUTOR_USES_SHMEM
|
|
uint64 last_executed = start;
|
|
uint32 executed_calls = __atomic_load_n(output_data, __ATOMIC_RELAXED);
|
|
#endif
|
|
for (;;) {
|
|
if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
|
|
break;
|
|
sleep_ms(1);
|
|
#if SYZ_EXECUTOR && SYZ_EXECUTOR_USES_SHMEM
|
|
uint64 now = current_time_ms();
|
|
uint32 now_executed = __atomic_load_n(output_data, __ATOMIC_RELAXED);
|
|
if (executed_calls != now_executed) {
|
|
executed_calls = now_executed;
|
|
last_executed = now;
|
|
}
|
|
if ((now - start < 5 * 1000) && (now - start < 3 * 1000 || now - last_executed < 1000))
|
|
continue;
|
|
#else
|
|
if (current_time_ms() - start < 5 * 1000)
|
|
continue;
|
|
#endif
|
|
debug("killing hanging pid %d\n", pid);
|
|
kill_and_wait(pid, &status);
|
|
break;
|
|
}
|
|
#if SYZ_EXECUTOR
|
|
if (WEXITSTATUS(status) == kFailStatus) {
|
|
errno = 0;
|
|
fail("child failed");
|
|
}
|
|
reply_execute(0);
|
|
#endif
|
|
#if SYZ_EXECUTOR || SYZ_USE_TMP_DIR
|
|
remove_dir(cwdbuf);
|
|
#endif
|
|
#if SYZ_ENABLE_LEAK
|
|
check_leaks();
|
|
#endif
|
|
}
|
|
}
|
|
#else
|
|
static void loop(void)
|
|
{
|
|
execute_one();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if !SYZ_EXECUTOR
|
|
/*SYSCALL_DEFINES*/
|
|
|
|
/*RESULTS*/
|
|
|
|
#if SYZ_THREADED || SYZ_REPEAT || SYZ_SANDBOX_NONE || SYZ_SANDBOX_SETUID || SYZ_SANDBOX_NAMESPACE || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
|
|
#if SYZ_THREADED
|
|
void execute_call(int call)
|
|
#elif SYZ_REPEAT
|
|
void execute_one(void)
|
|
#else
|
|
void loop(void)
|
|
#endif
|
|
{
|
|
/*SYSCALLS*/
|
|
#if SYZ_HAVE_CLOSE_FDS && !SYZ_THREADED && !SYZ_REPEAT
|
|
close_fds();
|
|
#endif
|
|
}
|
|
#endif
|
|
#if GOOS_akaros && SYZ_REPEAT
|
|
#include <string.h>
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
/*MMAP_DATA*/
|
|
|
|
program_name = argv[0];
|
|
if (argc == 2 && strcmp(argv[1], "child") == 0)
|
|
child();
|
|
#else
|
|
int main(void)
|
|
{
|
|
/*MMAP_DATA*/
|
|
#endif
|
|
|
|
#if SYZ_ENABLE_BINFMT_MISC
|
|
setup_binfmt_misc();
|
|
#endif
|
|
#if SYZ_ENABLE_LEAK
|
|
setup_leak();
|
|
#endif
|
|
#if SYZ_FAULT_INJECTION
|
|
setup_fault();
|
|
#endif
|
|
|
|
#if SYZ_HANDLE_SEGV
|
|
install_segv_handler();
|
|
#endif
|
|
#if SYZ_PROCS
|
|
for (procid = 0; procid < /*PROCS*/; procid++) {
|
|
if (fork() == 0) {
|
|
#endif
|
|
#if SYZ_USE_TMP_DIR || SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
|
|
use_temporary_dir();
|
|
#endif
|
|
/*SANDBOX_FUNC*/
|
|
#if SYZ_HAVE_CLOSE_FDS && !SYZ_THREADED && !SYZ_REPEAT && !SYZ_SANDBOX_NONE && \
|
|
!SYZ_SANDBOX_SETUID && !SYZ_SANDBOX_NAMESPACE && !SYZ_SANDBOX_ANDROID_UNTRUSTED_APP
|
|
close_fds();
|
|
#endif
|
|
#if SYZ_PROCS
|
|
}
|
|
}
|
|
sleep(1000000);
|
|
#endif
|
|
#if !SYZ_PROCS && !SYZ_REPEAT && SYZ_ENABLE_LEAK
|
|
check_leaks();
|
|
#endif
|
|
return 0;
|
|
}
|
|
#endif
|
|
`
|