linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Chenyuan Yang <chenyuan0y@gmail.com>
To: linux-mtd@lists.infradead.org, richard@nod.at,
	miquel.raynal@bootlin.com,  vigneshr@ti.com
Cc: linux-kernel@vger.kernel.org, syzkaller@googlegroups.com,
	 Zijie Zhao <zzjas98@gmail.com>
Subject: [Linux Kernel Bug] memory leak in ubi_attach
Date: Mon, 22 Jan 2024 21:53:14 -0600	[thread overview]
Message-ID: <CALGdzuo8_EXr4aUAw6X9S+yzMPi25Y82netPLWsd+3n-Ov9Pnw@mail.gmail.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 1077 bytes --]

Dear Linux Kernel Developers for UBI,

We encountered "memory leak in ubi_attach" when testing UBI with
Syzkaller and our generated specifications.

syz repro: https://drive.google.com/file/d/17FoGw6akfufz05U-oRBP2wXmOiFF1VUq/view?usp=drive_link
C reproducer: https://drive.google.com/file/d/1ayd3lmHPvqNoI01pQEdU832EktpTUnZ_/view?usp=drive_link
report: https://drive.google.com/file/d/1hC2arY3FbQt-6L5rbDfY-DQ2oH82IIGq/view?usp=drive_link
stats: https://drive.google.com/file/d/1REig9fV0H1fYPWaiicc-JVLlCpo7TTw4/view?usp=drive_link

This memory leak is triggered by `ioctl$UBI_IOCATT`, where
`ubi_attach_info` invokes `kmem_cache_create`
(https://elixir.bootlin.com/linux/v6.7/source/drivers/mtd/ubi/attach.c#L1464).
It seems that the memory leak occurs when the slab cache is
successfully created. I apologize for not being able to conduct a
deeper analysis of the root cause, as my expertise in UBI drivers is
limited.

If you have any questions or require more information, please feel
free to contact us.

Reported-by: Chenyuan Yang <chenyuan0y@gmail.com>

Best,
Chenyuan

[-- Attachment #2: repro.cprog --]
[-- Type: application/octet-stream, Size: 41955 bytes --]

// autogenerated by syzkaller (https://github.com/google/syzkaller)

#define _GNU_SOURCE 

#include <arpa/inet.h>
#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sched.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include <linux/capability.h>
#include <linux/futex.h>
#include <linux/genetlink.h>
#include <linux/if_addr.h>
#include <linux/if_ether.h>
#include <linux/if_link.h>
#include <linux/if_tun.h>
#include <linux/in6.h>
#include <linux/ip.h>
#include <linux/neighbour.h>
#include <linux/net.h>
#include <linux/netlink.h>
#include <linux/nl80211.h>
#include <linux/rfkill.h>
#include <linux/rtnetlink.h>
#include <linux/tcp.h>
#include <linux/veth.h>

static unsigned long long procid;

static __thread int clone_ongoing;
static __thread int skip_segv;
static __thread jmp_buf segv_env;

static void segv_handler(int sig, siginfo_t* info, void* ctx)
{
	if (__atomic_load_n(&clone_ongoing, __ATOMIC_RELAXED) != 0) {
		exit(sig);
	}
	uintptr_t addr = (uintptr_t)info->si_addr;
	const uintptr_t prog_start = 1 << 20;
	const uintptr_t prog_end = 100 << 20;
	int skip = __atomic_load_n(&skip_segv, __ATOMIC_RELAXED) != 0;
	int valid = addr < prog_start || addr > prog_end;
	if (skip && valid) {
		_longjmp(segv_env, 1);
	}
	exit(sig);
}

static void install_segv_handler(void)
{
	struct sigaction sa;
	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);
	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(...) ({ int ok = 1; __atomic_fetch_add(&skip_segv, 1, __ATOMIC_SEQ_CST); if (_setjmp(segv_env) == 0) { __VA_ARGS__; } else ok = 0; __atomic_fetch_sub(&skip_segv, 1, __ATOMIC_SEQ_CST); ok; })

static void sleep_ms(uint64_t ms)
{
	usleep(ms * 1000);
}

static uint64_t current_time_ms(void)
{
	struct timespec ts;
	if (clock_gettime(CLOCK_MONOTONIC, &ts))
	exit(1);
	return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}

static void use_temporary_dir(void)
{
	char tmpdir_template[] = "./syzkaller.XXXXXX";
	char* tmpdir = mkdtemp(tmpdir_template);
	if (!tmpdir)
	exit(1);
	if (chmod(tmpdir, 0777))
	exit(1);
	if (chdir(tmpdir))
	exit(1);
}

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 = 0;
	for (; i < 100; i++) {
		if (pthread_create(&th, &attr, fn, arg) == 0) {
			pthread_attr_destroy(&attr);
			return;
		}
		if (errno == EAGAIN) {
			usleep(50);
			continue;
		}
		break;
	}
	exit(1);
}

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)
	exit(1);
	__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
	syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}

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_t timeout)
{
	uint64_t start = current_time_ms();
	uint64_t now = start;
	for (;;) {
		uint64_t 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_ACQUIRE))
			return 1;
		now = current_time_ms();
		if (now - start > timeout)
			return 0;
	}
}

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);
		errno = err;
		return false;
	}
	close(fd);
	return true;
}

struct nlmsg {
	char* pos;
	int nesting;
	struct nlattr* nested[8];
	char buf[4096];
};

static void netlink_init(struct nlmsg* nlmsg, 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(struct nlmsg* nlmsg, int typ,
			 const void* data, int size)
{
	struct nlattr* attr = (struct nlattr*)nlmsg->pos;
	attr->nla_len = sizeof(*attr) + size;
	attr->nla_type = typ;
	if (size > 0)
		memcpy(attr + 1, data, size);
	nlmsg->pos += NLMSG_ALIGN(attr->nla_len);
}

static void netlink_nest(struct nlmsg* nlmsg, 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(struct nlmsg* nlmsg)
{
	struct nlattr* attr = nlmsg->nested[--nlmsg->nesting];
	attr->nla_len = nlmsg->pos - (char*)attr;
}

static int netlink_send_ext(struct nlmsg* nlmsg, int sock,
			    uint16_t reply_type, int* reply_len, bool dofail)
{
	if (nlmsg->pos > nlmsg->buf + sizeof(nlmsg->buf) || nlmsg->nesting)
	exit(1);
	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;
	ssize_t n = sendto(sock, nlmsg->buf, hdr->nlmsg_len, 0, (struct sockaddr*)&addr, sizeof(addr));
	if (n != (ssize_t)hdr->nlmsg_len) {
		if (dofail)
	exit(1);
		return -1;
	}
	n = recv(sock, nlmsg->buf, sizeof(nlmsg->buf), 0);
	if (reply_len)
		*reply_len = 0;
	if (n < 0) {
		if (dofail)
	exit(1);
		return -1;
	}
	if (n < (ssize_t)sizeof(struct nlmsghdr)) {
		errno = EINVAL;
		if (dofail)
	exit(1);
		return -1;
	}
	if (hdr->nlmsg_type == NLMSG_DONE)
		return 0;
	if (reply_len && hdr->nlmsg_type == reply_type) {
		*reply_len = n;
		return 0;
	}
	if (n < (ssize_t)(sizeof(struct nlmsghdr) + sizeof(struct nlmsgerr))) {
		errno = EINVAL;
		if (dofail)
	exit(1);
		return -1;
	}
	if (hdr->nlmsg_type != NLMSG_ERROR) {
		errno = EINVAL;
		if (dofail)
	exit(1);
		return -1;
	}
	errno = -((struct nlmsgerr*)(hdr + 1))->error;
	return -errno;
}

static int netlink_send(struct nlmsg* nlmsg, int sock)
{
	return netlink_send_ext(nlmsg, sock, 0, NULL, true);
}

static int netlink_query_family_id(struct nlmsg* nlmsg, int sock, const char* family_name, bool dofail)
{
	struct genlmsghdr genlhdr;
	memset(&genlhdr, 0, sizeof(genlhdr));
	genlhdr.cmd = CTRL_CMD_GETFAMILY;
	netlink_init(nlmsg, GENL_ID_CTRL, 0, &genlhdr, sizeof(genlhdr));
	netlink_attr(nlmsg, CTRL_ATTR_FAMILY_NAME, family_name, strnlen(family_name, GENL_NAMSIZ - 1) + 1);
	int n = 0;
	int err = netlink_send_ext(nlmsg, sock, GENL_ID_CTRL, &n, dofail);
	if (err < 0) {
		return -1;
	}
	uint16_t id = 0;
	struct nlattr* attr = (struct nlattr*)(nlmsg->buf + NLMSG_HDRLEN + NLMSG_ALIGN(sizeof(genlhdr)));
	for (; (char*)attr < nlmsg->buf + n; attr = (struct nlattr*)((char*)attr + NLMSG_ALIGN(attr->nla_len))) {
		if (attr->nla_type == CTRL_ATTR_FAMILY_ID) {
			id = *(uint16_t*)(attr + 1);
			break;
		}
	}
	if (!id) {
		errno = EINVAL;
		return -1;
	}
	recv(sock, nlmsg->buf, sizeof(nlmsg->buf), 0);
	return id;
}

static void netlink_add_device_impl(struct nlmsg* nlmsg, const char* type,
				    const char* name, bool up)
{
	struct ifinfomsg hdr;
	memset(&hdr, 0, sizeof(hdr));
	if (up)
		hdr.ifi_flags = hdr.ifi_change = IFF_UP;
	netlink_init(nlmsg, RTM_NEWLINK, NLM_F_EXCL | NLM_F_CREATE, &hdr, sizeof(hdr));
	if (name)
		netlink_attr(nlmsg, IFLA_IFNAME, name, strlen(name));
	netlink_nest(nlmsg, IFLA_LINKINFO);
	netlink_attr(nlmsg, IFLA_INFO_KIND, type, strlen(type));
}

static void netlink_device_change(struct nlmsg* nlmsg, int sock, const char* name, bool up,
				  const char* master, const void* mac, int macsize,
				  const char* new_name)
{
	struct ifinfomsg hdr;
	memset(&hdr, 0, sizeof(hdr));
	if (up)
		hdr.ifi_flags = hdr.ifi_change = IFF_UP;
	hdr.ifi_index = if_nametoindex(name);
	netlink_init(nlmsg, RTM_NEWLINK, 0, &hdr, sizeof(hdr));
	if (new_name)
		netlink_attr(nlmsg, IFLA_IFNAME, new_name, strlen(new_name));
	if (master) {
		int ifindex = if_nametoindex(master);
		netlink_attr(nlmsg, IFLA_MASTER, &ifindex, sizeof(ifindex));
	}
	if (macsize)
		netlink_attr(nlmsg, IFLA_ADDRESS, mac, macsize);
	int err = netlink_send(nlmsg, sock);
	if (err < 0) {
	}
}

static int netlink_add_addr(struct nlmsg* nlmsg, 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(nlmsg, RTM_NEWADDR, NLM_F_CREATE | NLM_F_REPLACE, &hdr, sizeof(hdr));
	netlink_attr(nlmsg, IFA_LOCAL, addr, addrsize);
	netlink_attr(nlmsg, IFA_ADDRESS, addr, addrsize);
	return netlink_send(nlmsg, sock);
}

static void netlink_add_addr4(struct nlmsg* nlmsg, 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(nlmsg, sock, dev, &in_addr, sizeof(in_addr));
	if (err < 0) {
	}
}

static void netlink_add_addr6(struct nlmsg* nlmsg, 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(nlmsg, sock, dev, &in6_addr, sizeof(in6_addr));
	if (err < 0) {
	}
}

static void netlink_add_neigh(struct nlmsg* nlmsg, 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(nlmsg, RTM_NEWNEIGH, NLM_F_EXCL | NLM_F_CREATE, &hdr, sizeof(hdr));
	netlink_attr(nlmsg, NDA_DST, addr, addrsize);
	netlink_attr(nlmsg, NDA_LLADDR, mac, macsize);
	int err = netlink_send(nlmsg, sock);
	if (err < 0) {
	}
}

static struct nlmsg nlmsg;

static int tunfd = -1;

#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"

#define IFF_NAPI 0x0010

static void initialize_tun(void)
{
	tunfd = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
	if (tunfd == -1) {
		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;
	}
	const int kTunFd = 200;
	if (dup2(tunfd, kTunFd) < 0)
	exit(1);
	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;
	if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0) {
	exit(1);
	}
	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)
	exit(1);
	netlink_add_addr4(&nlmsg, sock, TUN_IFACE, LOCAL_IPV4);
	netlink_add_addr6(&nlmsg, sock, TUN_IFACE, LOCAL_IPV6);
	uint64_t macaddr = REMOTE_MAC;
	struct in_addr in_addr;
	inet_pton(AF_INET, REMOTE_IPV4, &in_addr);
	netlink_add_neigh(&nlmsg, 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(&nlmsg, sock, TUN_IFACE, &in6_addr, sizeof(in6_addr), &macaddr, ETH_ALEN);
	macaddr = LOCAL_MAC;
	netlink_device_change(&nlmsg, sock, TUN_IFACE, true, 0, &macaddr, ETH_ALEN, NULL);
	close(sock);
}

#define WIFI_INITIAL_DEVICE_COUNT 2
#define WIFI_MAC_BASE { 0x08, 0x02, 0x11, 0x00, 0x00, 0x00 }
#define WIFI_IBSS_BSSID { 0x50, 0x50, 0x50, 0x50, 0x50, 0x50 }
#define WIFI_IBSS_SSID { 0x10, 0x10, 0x10, 0x10, 0x10, 0x10 }
#define WIFI_DEFAULT_FREQUENCY 2412
#define WIFI_DEFAULT_SIGNAL 0
#define WIFI_DEFAULT_RX_RATE 1
#define HWSIM_CMD_REGISTER 1
#define HWSIM_CMD_FRAME 2
#define HWSIM_CMD_NEW_RADIO 4
#define HWSIM_ATTR_SUPPORT_P2P_DEVICE 14
#define HWSIM_ATTR_PERM_ADDR 22

#define IF_OPER_UP 6
struct join_ibss_props {
	int wiphy_freq;
	bool wiphy_freq_fixed;
	uint8_t* mac;
	uint8_t* ssid;
	int ssid_len;
};

static int set_interface_state(const char* interface_name, int on)
{
	struct ifreq ifr;
	int sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		return -1;
	}
	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, interface_name);
	int ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
	if (ret < 0) {
		close(sock);
		return -1;
	}
	if (on)
		ifr.ifr_flags |= IFF_UP;
	else
		ifr.ifr_flags &= ~IFF_UP;
	ret = ioctl(sock, SIOCSIFFLAGS, &ifr);
	close(sock);
	if (ret < 0) {
		return -1;
	}
	return 0;
}

static int nl80211_set_interface(struct nlmsg* nlmsg, int sock, int nl80211_family, uint32_t ifindex, uint32_t iftype)
{
	struct genlmsghdr genlhdr;
	memset(&genlhdr, 0, sizeof(genlhdr));
	genlhdr.cmd = NL80211_CMD_SET_INTERFACE;
	netlink_init(nlmsg, nl80211_family, 0, &genlhdr, sizeof(genlhdr));
	netlink_attr(nlmsg, NL80211_ATTR_IFINDEX, &ifindex, sizeof(ifindex));
	netlink_attr(nlmsg, NL80211_ATTR_IFTYPE, &iftype, sizeof(iftype));
	int err = netlink_send(nlmsg, sock);
	if (err < 0) {
	}
	return err;
}

static int nl80211_join_ibss(struct nlmsg* nlmsg, int sock, int nl80211_family, uint32_t ifindex, struct join_ibss_props* props)
{
	struct genlmsghdr genlhdr;
	memset(&genlhdr, 0, sizeof(genlhdr));
	genlhdr.cmd = NL80211_CMD_JOIN_IBSS;
	netlink_init(nlmsg, nl80211_family, 0, &genlhdr, sizeof(genlhdr));
	netlink_attr(nlmsg, NL80211_ATTR_IFINDEX, &ifindex, sizeof(ifindex));
	netlink_attr(nlmsg, NL80211_ATTR_SSID, props->ssid, props->ssid_len);
	netlink_attr(nlmsg, NL80211_ATTR_WIPHY_FREQ, &(props->wiphy_freq), sizeof(props->wiphy_freq));
	if (props->mac)
		netlink_attr(nlmsg, NL80211_ATTR_MAC, props->mac, ETH_ALEN);
	if (props->wiphy_freq_fixed)
		netlink_attr(nlmsg, NL80211_ATTR_FREQ_FIXED, NULL, 0);
	int err = netlink_send(nlmsg, sock);
	if (err < 0) {
	}
	return err;
}

static int get_ifla_operstate(struct nlmsg* nlmsg, int ifindex)
{
	struct ifinfomsg info;
	memset(&info, 0, sizeof(info));
	info.ifi_family = AF_UNSPEC;
	info.ifi_index = ifindex;
	int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	if (sock == -1) {
		return -1;
	}
	netlink_init(nlmsg, RTM_GETLINK, 0, &info, sizeof(info));
	int n;
	int err = netlink_send_ext(nlmsg, sock, RTM_NEWLINK, &n, true);
	close(sock);
	if (err) {
		return -1;
	}
	struct rtattr* attr = IFLA_RTA(NLMSG_DATA(nlmsg->buf));
	for (; RTA_OK(attr, n); attr = RTA_NEXT(attr, n)) {
		if (attr->rta_type == IFLA_OPERSTATE)
			return *((int32_t*)RTA_DATA(attr));
	}
	return -1;
}

static int await_ifla_operstate(struct nlmsg* nlmsg, char* interface, int operstate)
{
	int ifindex = if_nametoindex(interface);
	while (true) {
		usleep(1000);
		int ret = get_ifla_operstate(nlmsg, ifindex);
		if (ret < 0)
			return ret;
		if (ret == operstate)
			return 0;
	}
	return 0;
}

static int nl80211_setup_ibss_interface(struct nlmsg* nlmsg, int sock, int nl80211_family_id, char* interface, struct join_ibss_props* ibss_props)
{
	int ifindex = if_nametoindex(interface);
	if (ifindex == 0) {
		return -1;
	}
	int ret = nl80211_set_interface(nlmsg, sock, nl80211_family_id, ifindex, NL80211_IFTYPE_ADHOC);
	if (ret < 0) {
		return -1;
	}
	ret = set_interface_state(interface, 1);
	if (ret < 0) {
		return -1;
	}
	ret = nl80211_join_ibss(nlmsg, sock, nl80211_family_id, ifindex, ibss_props);
	if (ret < 0) {
		return -1;
	}
	return 0;
}

static int hwsim80211_create_device(struct nlmsg* nlmsg, int sock, int hwsim_family, uint8_t mac_addr[ETH_ALEN])
{
	struct genlmsghdr genlhdr;
	memset(&genlhdr, 0, sizeof(genlhdr));
	genlhdr.cmd = HWSIM_CMD_NEW_RADIO;
	netlink_init(nlmsg, hwsim_family, 0, &genlhdr, sizeof(genlhdr));
	netlink_attr(nlmsg, HWSIM_ATTR_SUPPORT_P2P_DEVICE, NULL, 0);
	netlink_attr(nlmsg, HWSIM_ATTR_PERM_ADDR, mac_addr, ETH_ALEN);
	int err = netlink_send(nlmsg, sock);
	if (err < 0) {
	}
	return err;
}

static void initialize_wifi_devices(void)
{
	int rfkill = open("/dev/rfkill", O_RDWR);
	if (rfkill == -1) {
		if (errno != ENOENT && errno != EACCES)
	exit(1);
	} else {
		struct rfkill_event event = {0};
		event.type = RFKILL_TYPE_ALL;
		event.op = RFKILL_OP_CHANGE_ALL;
		if (write(rfkill, &event, sizeof(event)) != (ssize_t)(sizeof(event)))
	exit(1);
		close(rfkill);
	}
	uint8_t mac_addr[6] = WIFI_MAC_BASE;
	int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
	if (sock < 0) {
		return;
	}
	int hwsim_family_id = netlink_query_family_id(&nlmsg, sock, "MAC80211_HWSIM", true);
	int nl80211_family_id = netlink_query_family_id(&nlmsg, sock, "nl80211", true);
	uint8_t ssid[] = WIFI_IBSS_SSID;
	uint8_t bssid[] = WIFI_IBSS_BSSID;
	struct join_ibss_props ibss_props = {
	    .wiphy_freq = WIFI_DEFAULT_FREQUENCY, .wiphy_freq_fixed = true, .mac = bssid, .ssid = ssid, .ssid_len = sizeof(ssid)};
	for (int device_id = 0; device_id < WIFI_INITIAL_DEVICE_COUNT; device_id++) {
		mac_addr[5] = device_id;
		int ret = hwsim80211_create_device(&nlmsg, sock, hwsim_family_id, mac_addr);
		if (ret < 0)
	exit(1);
		char interface[6] = "wlan0";
		interface[4] += device_id;
		if (nl80211_setup_ibss_interface(&nlmsg, sock, nl80211_family_id, interface, &ibss_props) < 0)
	exit(1);
	}
	for (int device_id = 0; device_id < WIFI_INITIAL_DEVICE_COUNT; device_id++) {
		char interface[6] = "wlan0";
		interface[4] += device_id;
		int ret = await_ifla_operstate(&nlmsg, interface, IF_OPER_UP);
		if (ret < 0)
	exit(1);
	}
	close(sock);
}

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 || errno == EBADFD)
			return -1;
	exit(1);
	}
	return rv;
}

static void flush_tun()
{
	char data[1000];
	while (read_tun(&data[0], sizeof(data)) != -1) {
	}
}

#define MAX_FDS 30

#define BTPROTO_HCI 1
#define ACL_LINK 1
#define SCAN_PAGE 2

typedef struct {
	uint8_t b[6];
} __attribute__((packed)) bdaddr_t;

#define HCI_COMMAND_PKT 1
#define HCI_EVENT_PKT 4
#define HCI_VENDOR_PKT 0xff

struct hci_command_hdr {
	uint16_t opcode;
	uint8_t plen;
} __attribute__((packed));

struct hci_event_hdr {
	uint8_t evt;
	uint8_t plen;
} __attribute__((packed));

#define HCI_EV_CONN_COMPLETE 0x03
struct hci_ev_conn_complete {
	uint8_t status;
	uint16_t handle;
	bdaddr_t bdaddr;
	uint8_t link_type;
	uint8_t encr_mode;
} __attribute__((packed));

#define HCI_EV_CONN_REQUEST 0x04
struct hci_ev_conn_request {
	bdaddr_t bdaddr;
	uint8_t dev_class[3];
	uint8_t link_type;
} __attribute__((packed));

#define HCI_EV_REMOTE_FEATURES 0x0b
struct hci_ev_remote_features {
	uint8_t status;
	uint16_t handle;
	uint8_t features[8];
} __attribute__((packed));

#define HCI_EV_CMD_COMPLETE 0x0e
struct hci_ev_cmd_complete {
	uint8_t ncmd;
	uint16_t opcode;
} __attribute__((packed));

#define HCI_OP_WRITE_SCAN_ENABLE 0x0c1a

#define HCI_OP_READ_BUFFER_SIZE 0x1005
struct hci_rp_read_buffer_size {
	uint8_t status;
	uint16_t acl_mtu;
	uint8_t sco_mtu;
	uint16_t acl_max_pkt;
	uint16_t sco_max_pkt;
} __attribute__((packed));

#define HCI_OP_READ_BD_ADDR 0x1009
struct hci_rp_read_bd_addr {
	uint8_t status;
	bdaddr_t bdaddr;
} __attribute__((packed));

#define HCI_EV_LE_META 0x3e
struct hci_ev_le_meta {
	uint8_t subevent;
} __attribute__((packed));

#define HCI_EV_LE_CONN_COMPLETE 0x01
struct hci_ev_le_conn_complete {
	uint8_t status;
	uint16_t handle;
	uint8_t role;
	uint8_t bdaddr_type;
	bdaddr_t bdaddr;
	uint16_t interval;
	uint16_t latency;
	uint16_t supervision_timeout;
	uint8_t clk_accurancy;
} __attribute__((packed));

struct hci_dev_req {
	uint16_t dev_id;
	uint32_t dev_opt;
};

struct vhci_vendor_pkt_request {
	uint8_t type;
	uint8_t opcode;
} __attribute__((packed));

struct vhci_pkt {
	uint8_t type;
	union {
		struct {
			uint8_t opcode;
			uint16_t id;
		} __attribute__((packed)) vendor_pkt;
		struct hci_command_hdr command_hdr;
	};
} __attribute__((packed));

#define HCIDEVUP _IOW('H', 201, int)
#define HCISETSCAN _IOW('H', 221, int)

static int vhci_fd = -1;

static void rfkill_unblock_all()
{
	int fd = open("/dev/rfkill", O_WRONLY);
	if (fd < 0)
	exit(1);
	struct rfkill_event event = {0};
	event.idx = 0;
	event.type = RFKILL_TYPE_ALL;
	event.op = RFKILL_OP_CHANGE_ALL;
	event.soft = 0;
	event.hard = 0;
	if (write(fd, &event, sizeof(event)) < 0)
	exit(1);
	close(fd);
}

static void hci_send_event_packet(int fd, uint8_t evt, void* data, size_t data_len)
{
	struct iovec iv[3];
	struct hci_event_hdr hdr;
	hdr.evt = evt;
	hdr.plen = data_len;
	uint8_t type = HCI_EVENT_PKT;
	iv[0].iov_base = &type;
	iv[0].iov_len = sizeof(type);
	iv[1].iov_base = &hdr;
	iv[1].iov_len = sizeof(hdr);
	iv[2].iov_base = data;
	iv[2].iov_len = data_len;
	if (writev(fd, iv, sizeof(iv) / sizeof(struct iovec)) < 0)
	exit(1);
}

static void hci_send_event_cmd_complete(int fd, uint16_t opcode, void* data, size_t data_len)
{
	struct iovec iv[4];
	struct hci_event_hdr hdr;
	hdr.evt = HCI_EV_CMD_COMPLETE;
	hdr.plen = sizeof(struct hci_ev_cmd_complete) + data_len;
	struct hci_ev_cmd_complete evt_hdr;
	evt_hdr.ncmd = 1;
	evt_hdr.opcode = opcode;
	uint8_t type = HCI_EVENT_PKT;
	iv[0].iov_base = &type;
	iv[0].iov_len = sizeof(type);
	iv[1].iov_base = &hdr;
	iv[1].iov_len = sizeof(hdr);
	iv[2].iov_base = &evt_hdr;
	iv[2].iov_len = sizeof(evt_hdr);
	iv[3].iov_base = data;
	iv[3].iov_len = data_len;
	if (writev(fd, iv, sizeof(iv) / sizeof(struct iovec)) < 0)
	exit(1);
}

static bool process_command_pkt(int fd, char* buf, ssize_t buf_size)
{
	struct hci_command_hdr* hdr = (struct hci_command_hdr*)buf;
	if (buf_size < (ssize_t)sizeof(struct hci_command_hdr) ||
	    hdr->plen != buf_size - sizeof(struct hci_command_hdr))
	exit(1);
	switch (hdr->opcode) {
	case HCI_OP_WRITE_SCAN_ENABLE: {
		uint8_t status = 0;
		hci_send_event_cmd_complete(fd, hdr->opcode, &status, sizeof(status));
		return true;
	}
	case HCI_OP_READ_BD_ADDR: {
		struct hci_rp_read_bd_addr rp = {0};
		rp.status = 0;
		memset(&rp.bdaddr, 0xaa, 6);
		hci_send_event_cmd_complete(fd, hdr->opcode, &rp, sizeof(rp));
		return false;
	}
	case HCI_OP_READ_BUFFER_SIZE: {
		struct hci_rp_read_buffer_size rp = {0};
		rp.status = 0;
		rp.acl_mtu = 1021;
		rp.sco_mtu = 96;
		rp.acl_max_pkt = 4;
		rp.sco_max_pkt = 6;
		hci_send_event_cmd_complete(fd, hdr->opcode, &rp, sizeof(rp));
		return false;
	}
	}
	char dummy[0xf9] = {0};
	hci_send_event_cmd_complete(fd, hdr->opcode, dummy, sizeof(dummy));
	return false;
}

static void* event_thread(void* arg)
{
	while (1) {
		char buf[1024] = {0};
		ssize_t buf_size = read(vhci_fd, buf, sizeof(buf));
		if (buf_size < 0)
	exit(1);
		if (buf_size > 0 && buf[0] == HCI_COMMAND_PKT) {
			if (process_command_pkt(vhci_fd, buf + 1, buf_size - 1))
				break;
		}
	}
	return NULL;
}
#define HCI_HANDLE_1 200
#define HCI_HANDLE_2 201

#define HCI_PRIMARY 0
#define HCI_OP_RESET 0x0c03

static void initialize_vhci()
{
	int hci_sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
	if (hci_sock < 0)
	exit(1);
	vhci_fd = open("/dev/vhci", O_RDWR);
	if (vhci_fd == -1)
	exit(1);
	const int kVhciFd = 202;
	if (dup2(vhci_fd, kVhciFd) < 0)
	exit(1);
	close(vhci_fd);
	vhci_fd = kVhciFd;
	struct vhci_vendor_pkt_request vendor_pkt_req = {HCI_VENDOR_PKT, HCI_PRIMARY};
	if (write(vhci_fd, &vendor_pkt_req, sizeof(vendor_pkt_req)) != sizeof(vendor_pkt_req))
	exit(1);
	struct vhci_pkt vhci_pkt;
	if (read(vhci_fd, &vhci_pkt, sizeof(vhci_pkt)) != sizeof(vhci_pkt))
	exit(1);
	if (vhci_pkt.type == HCI_COMMAND_PKT && vhci_pkt.command_hdr.opcode == HCI_OP_RESET) {
		char response[1] = {0};
		hci_send_event_cmd_complete(vhci_fd, HCI_OP_RESET, response, sizeof(response));
		if (read(vhci_fd, &vhci_pkt, sizeof(vhci_pkt)) != sizeof(vhci_pkt))
	exit(1);
	}
	if (vhci_pkt.type != HCI_VENDOR_PKT)
	exit(1);
	int dev_id = vhci_pkt.vendor_pkt.id;
	pthread_t th;
	if (pthread_create(&th, NULL, event_thread, NULL))
	exit(1);
	int ret = ioctl(hci_sock, HCIDEVUP, dev_id);
	if (ret) {
		if (errno == ERFKILL) {
			rfkill_unblock_all();
			ret = ioctl(hci_sock, HCIDEVUP, dev_id);
		}
		if (ret && errno != EALREADY)
	exit(1);
	}
	struct hci_dev_req dr = {0};
	dr.dev_id = dev_id;
	dr.dev_opt = SCAN_PAGE;
	if (ioctl(hci_sock, HCISETSCAN, &dr))
	exit(1);
	struct hci_ev_conn_request request;
	memset(&request, 0, sizeof(request));
	memset(&request.bdaddr, 0xaa, 6);
	*(uint8_t*)&request.bdaddr.b[5] = 0x10;
	request.link_type = ACL_LINK;
	hci_send_event_packet(vhci_fd, HCI_EV_CONN_REQUEST, &request, sizeof(request));
	struct hci_ev_conn_complete complete;
	memset(&complete, 0, sizeof(complete));
	complete.status = 0;
	complete.handle = HCI_HANDLE_1;
	memset(&complete.bdaddr, 0xaa, 6);
	*(uint8_t*)&complete.bdaddr.b[5] = 0x10;
	complete.link_type = ACL_LINK;
	complete.encr_mode = 0;
	hci_send_event_packet(vhci_fd, HCI_EV_CONN_COMPLETE, &complete, sizeof(complete));
	struct hci_ev_remote_features features;
	memset(&features, 0, sizeof(features));
	features.status = 0;
	features.handle = HCI_HANDLE_1;
	hci_send_event_packet(vhci_fd, HCI_EV_REMOTE_FEATURES, &features, sizeof(features));
	struct {
		struct hci_ev_le_meta le_meta;
		struct hci_ev_le_conn_complete le_conn;
	} le_conn;
	memset(&le_conn, 0, sizeof(le_conn));
	le_conn.le_meta.subevent = HCI_EV_LE_CONN_COMPLETE;
	memset(&le_conn.le_conn.bdaddr, 0xaa, 6);
	*(uint8_t*)&le_conn.le_conn.bdaddr.b[5] = 0x11;
	le_conn.le_conn.role = 1;
	le_conn.le_conn.handle = HCI_HANDLE_2;
	hci_send_event_packet(vhci_fd, HCI_EV_LE_META, &le_conn, sizeof(le_conn));
	pthread_join(th, NULL);
	close(hci_sock);
}

static void mount_cgroups(const char* dir, const char** controllers, int count)
{
	if (mkdir(dir, 0777)) {
		return;
	}
	char enabled[128] = {0};
	int i = 0;
	for (; i < count; i++) {
		if (mount("none", dir, "cgroup", 0, controllers[i])) {
			continue;
		}
		umount(dir);
		strcat(enabled, ",");
		strcat(enabled, controllers[i]);
	}
	if (enabled[0] == 0) {
		if (rmdir(dir) && errno != EBUSY)
	exit(1);
		return;
	}
	if (mount("none", dir, "cgroup", 0, enabled + 1)) {
		if (rmdir(dir) && errno != EBUSY)
	exit(1);
	}
	if (chmod(dir, 0777)) {
	}
}

static void mount_cgroups2(const char** controllers, int count)
{
	if (mkdir("/syzcgroup/unified", 0777)) {
		return;
	}
	if (mount("none", "/syzcgroup/unified", "cgroup2", 0, NULL)) {
		if (rmdir("/syzcgroup/unified") && errno != EBUSY)
	exit(1);
		return;
	}
	if (chmod("/syzcgroup/unified", 0777)) {
	}
	int control = open("/syzcgroup/unified/cgroup.subtree_control", O_WRONLY);
	if (control == -1)
		return;
	int i;
	for (i = 0; i < count; i++)
		if (write(control, controllers[i], strlen(controllers[i])) < 0) {
		}
	close(control);
}

static void setup_cgroups()
{
	const char* unified_controllers[] = {"+cpu", "+io", "+pids"};
	const char* net_controllers[] = {"net", "net_prio", "devices", "blkio", "freezer"};
	const char* cpu_controllers[] = {"cpuset", "cpuacct", "hugetlb", "rlimit", "memory"};
	if (mkdir("/syzcgroup", 0777)) {
		return;
	}
	mount_cgroups2(unified_controllers, sizeof(unified_controllers) / sizeof(unified_controllers[0]));
	mount_cgroups("/syzcgroup/net", net_controllers, sizeof(net_controllers) / sizeof(net_controllers[0]));
	mount_cgroups("/syzcgroup/cpu", cpu_controllers, sizeof(cpu_controllers) / sizeof(cpu_controllers[0]));
	write_file("/syzcgroup/cpu/cgroup.clone_children", "1");
	write_file("/syzcgroup/cpu/cpuset.memory_pressure_enabled", "1");
}

static void setup_cgroups_loop()
{
	int pid = getpid();
	char file[128];
	char cgroupdir[64];
	snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/unified/syz%llu", procid);
	if (mkdir(cgroupdir, 0777)) {
	}
	snprintf(file, sizeof(file), "%s/pids.max", cgroupdir);
	write_file(file, "32");
	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)) {
	}
	snprintf(file, sizeof(file), "%s/cgroup.procs", cgroupdir);
	write_file(file, "%d", pid);
	snprintf(file, sizeof(file), "%s/memory.soft_limit_in_bytes", cgroupdir);
	write_file(file, "%d", 299 << 20);
	snprintf(file, sizeof(file), "%s/memory.limit_in_bytes", cgroupdir);
	write_file(file, "%d", 300 << 20);
	snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/net/syz%llu", procid);
	if (mkdir(cgroupdir, 0777)) {
	}
	snprintf(file, sizeof(file), "%s/cgroup.procs", cgroupdir);
	write_file(file, "%d", pid);
}

static void setup_cgroups_test()
{
	char cgroupdir[64];
	snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/unified/syz%llu", procid);
	if (symlink(cgroupdir, "./cgroup")) {
	}
	snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/cpu/syz%llu", procid);
	if (symlink(cgroupdir, "./cgroup.cpu")) {
	}
	snprintf(cgroupdir, sizeof(cgroupdir), "/syzcgroup/net/syz%llu", procid);
	if (symlink(cgroupdir, "./cgroup.net")) {
	}
}

static void setup_common()
{
	if (mount(0, "/sys/fs/fuse/connections", "fusectl", 0, 0)) {
	}
}

static void setup_binderfs()
{
	if (mkdir("/dev/binderfs", 0777)) {
	}
	if (mount("binder", "/dev/binderfs", "binder", 0, NULL)) {
	}
}

static void loop();

static void sandbox_common()
{
	prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
	setsid();
	struct rlimit rlim;
	rlim.rlim_cur = rlim.rlim_max = (200 << 20);
	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 = 128 << 20;
	setrlimit(RLIMIT_CORE, &rlim);
	rlim.rlim_cur = rlim.rlim_max = 256;
	setrlimit(RLIMIT_NOFILE, &rlim);
	if (unshare(CLONE_NEWNS)) {
	}
	if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
	}
	if (unshare(CLONE_NEWIPC)) {
	}
	if (unshare(0x02000000)) {
	}
	if (unshare(CLONE_NEWUTS)) {
	}
	if (unshare(CLONE_SYSVSEM)) {
	}
	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);
}

static int wait_for_loop(int pid)
{
	if (pid < 0)
	exit(1);
	int status = 0;
	while (waitpid(-1, &status, __WALL) != pid) {
	}
	return WEXITSTATUS(status);
}

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))
	exit(1);
	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))
	exit(1);
}

static int do_sandbox_none(void)
{
	if (unshare(CLONE_NEWPID)) {
	}
	int pid = fork();
	if (pid != 0)
		return wait_for_loop(pid);
	setup_common();
	initialize_vhci();
	sandbox_common();
	drop_caps();
	if (unshare(CLONE_NEWNET)) {
	}
	write_file("/proc/sys/net/ipv4/ping_group_range", "0 65535");
	initialize_tun();
	initialize_wifi_devices();
	setup_binderfs();
	loop();
	exit(1);
}

#define FS_IOC_SETFLAGS _IOW('f', 2, long)
static void remove_dir(const char* dir)
{
	int iter = 0;
	DIR* dp = 0;
retry:
		while (umount2(dir, MNT_DETACH | UMOUNT_NOFOLLOW) == 0) {
		}
	dp = opendir(dir);
	if (dp == NULL) {
		if (errno == EMFILE) {
	exit(1);
		}
	exit(1);
	}
	struct dirent* ep = 0;
	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 | UMOUNT_NOFOLLOW) == 0) {
			}
		struct stat st;
		if (lstat(filename, &st))
	exit(1);
		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) {
					}
					close(fd);
					continue;
				}
			}
			if (errno == EROFS) {
				break;
			}
			if (errno != EBUSY || i > 100)
	exit(1);
				if (umount2(filename, MNT_DETACH | UMOUNT_NOFOLLOW))
	exit(1);
		}
	}
	closedir(dp);
	for (int 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) {
					}
					close(fd);
					continue;
				}
			}
			if (errno == EROFS) {
				break;
			}
			if (errno == EBUSY) {
					if (umount2(dir, MNT_DETACH | UMOUNT_NOFOLLOW))
	exit(1);
				continue;
			}
			if (errno == ENOTEMPTY) {
				if (iter < 100) {
					iter++;
					goto retry;
				}
			}
		}
	exit(1);
	}
}

static int inject_fault(int nth)
{
	int fd;
	fd = open("/proc/thread-self/fail-nth", O_RDWR);
	if (fd == -1)
	exit(1);
	char buf[16];
	sprintf(buf, "%d", nth);
	if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf))
	exit(1);
	return fd;
}

static void kill_and_wait(int pid, int* status)
{
	kill(-pid, SIGKILL);
	kill(pid, SIGKILL);
	for (int i = 0; i < 100; i++) {
		if (waitpid(-1, status, WNOHANG | __WALL) == pid)
			return;
		usleep(1000);
	}
	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) {
				continue;
			}
			if (write(fd, abort, 1) < 0) {
			}
			close(fd);
		}
		closedir(dir);
	} else {
	}
	while (waitpid(-1, status, __WALL) != pid) {
	}
}

static void setup_loop()
{
	setup_cgroups_loop();
}

static void setup_test()
{
	prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
	setpgrp();
	setup_cgroups_test();
	write_file("/proc/self/oom_score_adj", "1000");
	flush_tun();
	if (symlink("/dev/binderfs", "./binderfs")) {
	}
}

static void close_fds()
{
	for (int fd = 3; fd < MAX_FDS; fd++)
		close(fd);
}

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)) {
			if (files[i].fatal)
	exit(1);
		}
	}
}

#define KMEMLEAK_FILE "/sys/kernel/debug/kmemleak"

static void setup_leak()
{
	if (!write_file(KMEMLEAK_FILE, "scan"))
	exit(1);
	sleep(5);
	if (!write_file(KMEMLEAK_FILE, "scan"))
	exit(1);
	if (!write_file(KMEMLEAK_FILE, "clear"))
	exit(1);
}

static void check_leaks(void)
{
	int fd = open(KMEMLEAK_FILE, O_RDWR);
	if (fd == -1)
	exit(1);
	uint64_t start = current_time_ms();
	if (write(fd, "scan", 4) != 4)
	exit(1);
	sleep(1);
	while (current_time_ms() - start < 4 * 1000)
		sleep(1);
	if (write(fd, "scan", 4) != 4)
	exit(1);
	static char buf[128 << 10];
	ssize_t n = read(fd, buf, sizeof(buf) - 1);
	if (n < 0)
	exit(1);
	int nleaks = 0;
	if (n != 0) {
		sleep(1);
		if (write(fd, "scan", 4) != 4)
	exit(1);
		if (lseek(fd, 0, SEEK_SET) < 0)
	exit(1);
		n = read(fd, buf, sizeof(buf) - 1);
		if (n < 0)
	exit(1);
		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;
			fprintf(stderr, "BUG: memory leak\n%s\n", pos);
			*next = prev;
			pos = next;
			nleaks++;
		}
	}
	if (write(fd, "clear", 5) != 5)
	exit(1);
	close(fd);
	if (nleaks)
		exit(1);
}

#define NL802154_CMD_SET_SHORT_ADDR 11
#define NL802154_ATTR_IFINDEX 3
#define NL802154_ATTR_SHORT_ADDR 10

static void setup_802154()
{
	int sock_route = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
	if (sock_route == -1)
	exit(1);
	int sock_generic = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
	if (sock_generic < 0)
	exit(1);
	int nl802154_family_id = netlink_query_family_id(&nlmsg, sock_generic, "nl802154", true);
	for (int i = 0; i < 2; i++) {
		char devname[] = "wpan0";
		devname[strlen(devname) - 1] += i;
		uint64_t hwaddr = 0xaaaaaaaaaaaa0002 + (i << 8);
		uint16_t shortaddr = 0xaaa0 + i;
		int ifindex = if_nametoindex(devname);
		struct genlmsghdr genlhdr;
		memset(&genlhdr, 0, sizeof(genlhdr));
		genlhdr.cmd = NL802154_CMD_SET_SHORT_ADDR;
		netlink_init(&nlmsg, nl802154_family_id, 0, &genlhdr, sizeof(genlhdr));
		netlink_attr(&nlmsg, NL802154_ATTR_IFINDEX, &ifindex, sizeof(ifindex));
		netlink_attr(&nlmsg, NL802154_ATTR_SHORT_ADDR, &shortaddr, sizeof(shortaddr));
		int err = netlink_send(&nlmsg, sock_generic);
		if (err < 0) {
		}
		netlink_device_change(&nlmsg, sock_route, devname, true, 0, &hwaddr, sizeof(hwaddr), 0);
		if (i == 0) {
			netlink_add_device_impl(&nlmsg, "lowpan", "lowpan0", false);
			netlink_done(&nlmsg);
			netlink_attr(&nlmsg, IFLA_LINK, &ifindex, sizeof(ifindex));
			int err = netlink_send(&nlmsg, sock_route);
			if (err < 0) {
			}
		}
	}
	close(sock_route);
	close(sock_generic);
}

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;
}

static void execute_one(void)
{
	int i, call, thread;
	for (call = 0; call < 2; 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);
			event_timedwait(&th->done, 50);
			break;
		}
	}
	for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
		sleep_ms(1);
	close_fds();
}

static void execute_one(void);

#define WAIT_FLAGS __WALL

static void loop(void)
{
	setup_loop();
	int iter = 0;
	for (;; iter++) {
		char cwdbuf[32];
		sprintf(cwdbuf, "./%d", iter);
		if (mkdir(cwdbuf, 0777))
	exit(1);
		int pid = fork();
		if (pid < 0)
	exit(1);
		if (pid == 0) {
			if (chdir(cwdbuf))
	exit(1);
			setup_test();
			execute_one();
			exit(0);
		}
		int status = 0;
		uint64_t start = current_time_ms();
		for (;;) {
			if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
				break;
			sleep_ms(1);
			if (current_time_ms() - start < 5000)
				continue;
			kill_and_wait(pid, &status);
			break;
		}
		remove_dir(cwdbuf);
		check_leaks();
	}
}

uint64_t r[1] = {0xffffffffffffffff};

void execute_call(int call)
{
		intptr_t res = 0;
	switch (call) {
	case 0:
		NONFAILING(memcpy((void*)0x20000440, "/dev/ubi_ctrl\000", 14));
		res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/0x20000440ul, /*flags=*/2ul, /*mode=*/0ul);
		if (res != -1)
				r[0] = res;
		break;
	case 1:
		NONFAILING(*(uint32_t*)0x20000480 = 0);
		NONFAILING(*(uint32_t*)0x20000484 = 0);
		NONFAILING(*(uint32_t*)0x20000488 = 0x74);
		NONFAILING(*(uint16_t*)0x2000048c = 0);
		NONFAILING(*(uint8_t*)0x2000048e = 0);
		NONFAILING(*(uint8_t*)0x2000048f = 0);
		NONFAILING(memcpy((void*)0x20000490, "\x26\xd7\xa9\xba\x98\x16\xed\x51", 8));
		inject_fault(21);
		syscall(__NR_ioctl, /*fd=*/r[0], /*cmd=*/0x40186f40, /*arg=*/0x20000480ul);
		break;
	}

}
int main(void)
{
		syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
	syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000ul, /*prot=*/7ul, /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
	syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=*/0x32ul, /*fd=*/-1, /*offset=*/0ul);
	setup_cgroups();
	setup_leak();
	setup_fault();
	setup_802154();
	install_segv_handler();
			use_temporary_dir();
			do_sandbox_none();
	return 0;
}

[-- Attachment #3: repro.prog --]
[-- Type: application/octet-stream, Size: 632 bytes --]

# {Threaded:true Repeat:true RepeatTimes:0 Procs:1 Slowdown:1 Sandbox:none SandboxArg:0 Leak:true NetInjection:true NetDevices:false NetReset:false Cgroups:true BinfmtMisc:false CloseFDs:true KCSAN:false DevlinkPCI:false NicVF:false USB:false VhciInjection:true Wifi:true IEEE802154:true Sysctl:false Swap:false UseTmpDir:true HandleSegv:true Repro:false Trace:false LegacyOptions:{Collide:false Fault:false FaultCall:0 FaultNth:0}}
r0 = openat$KGPT_ubi_ctrl(0xffffffffffffff9c, &(0x7f0000000440), 0x2, 0x0)
ioctl$KGPT_UBI_IOCATT(r0, 0x40186f40, &(0x7f0000000480)={0x0, 0x0, 0x74, 0x0, 0x0, 0x0, "26d7a9ba9816ed51"}) (fail_nth: 21)

             reply	other threads:[~2024-01-23  3:53 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-23  3:53 Chenyuan Yang [this message]
2024-01-23  4:55 ` [Linux Kernel Bug] memory leak in ubi_attach Zhihao Cheng
     [not found]   ` <CALGdzuryBbCqGAhox9ZWaQHD5dSQNApfGmCP+rYZ7O4MCRH+rw@mail.gmail.com>
2024-01-24 12:44     ` Zhihao Cheng
2024-01-24 14:41       ` Chenyuan Yang
2024-01-29 11:51         ` Zhihao Cheng

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CALGdzuo8_EXr4aUAw6X9S+yzMPi25Y82netPLWsd+3n-Ov9Pnw@mail.gmail.com \
    --to=chenyuan0y@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=miquel.raynal@bootlin.com \
    --cc=richard@nod.at \
    --cc=syzkaller@googlegroups.com \
    --cc=vigneshr@ti.com \
    --cc=zzjas98@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).