aboutsummaryrefslogtreecommitdiffstats
path: root/executor
diff options
context:
space:
mode:
authorFlorent Revest <revest@chromium.org>2025-11-26 15:03:06 +0100
committerAleksandr Nogikh <nogikh@google.com>2025-11-26 17:10:08 +0000
commite8331348a26e30c511b7bbbd25d071a1862cf6a8 (patch)
tree46dee0b3785b74bddeb74ff9e3b85fa260c51c99 /executor
parentd6526ea3e6ad9081c902859bbb80f9f840377cb4 (diff)
executor: improve startup time on machines with many CPUs
I observed that on machines with many CPUs (480 on my setup), fuzzing with a handful of procs (8 on my setup) would consistently fail to start because syz-executors would fail to respond within the default handshake timeout of 1 minute. Reducing procs to 4 would fix it but sounds ridiculous on such a powerful machine. As part of the default sandbox policy, a syz-executor creates a large number of virtual network interfaces (16 on my kernel config, probably more on other kernels). This step vastly dominates the executor startup time and was clearly responsible for the timeout I observed that prevented me from fuzzing. When fuzzing or reproducing with procs > 1, all executors run their sandbox setup in parallel. Creating network interfaces is done by socket operations to the RTNL (routing netlink) subsystem. Unfortunately, all RTNL operations in the kernel are serialized by a "rtnl_mutex" mega lock so instead of paralellizing the 8*16 interfaces creation, they effectively get serialized and the timing it takes to set up the default sandbox for one executor scales lineraly with the number of executors started "in parallel". This is currently inherent to the rtnl_mutex in the kernel and as far as I can tell there's nothing we can do about it. However, it makes it very important that each critical section guarded by "rtnl_mutex" stays short and snappy, to avoid long waits on the lock. Unfortunately, the default behavior of a virtual network interface creation is to create one RX and one TX queue per CPU. Each queue is associated with a sysfs file whose creation is quite slow and goes through various sanitized paths that take a long time. This means that each critical section scales linearly to the number of CPUs on the host. For example, in my setup, starting fuzzing needs 2 minutes 25. I found that I could bring this down to 10 seconds (15x faster startup time!) by limiting the number of RX and TX queues created per virtual interface to 2 using the IFLA_NUM_*X_QUEUES RTNL attributes. I opportunistically chose 2 to try and keep coverage of the code that exercises multiple queues but I don't have evidences that choosing 1 here would actually reduce the code coverage. As far as I can tell, reducing the number of queues would be problematic in a high performance networking scenario but doesn't matter for fuzzing in a namespace with only one process so this seems like a fair trade-off to me. Ultimately, this lets me start a lot more parallel executors and take better advantage of my beefy machine. Technical detail for review: veth interfaces actually create two interfaces for both side of the virtual ethernet link so both sides need to be configured with a low number of queues.
Diffstat (limited to 'executor')
-rw-r--r--executor/common_linux.h11
1 files changed, 11 insertions, 0 deletions
diff --git a/executor/common_linux.h b/executor/common_linux.h
index 7b35f284f..2cb04060e 100644
--- a/executor/common_linux.h
+++ b/executor/common_linux.h
@@ -370,6 +370,11 @@ static int netlink_next_msg(struct nlmsg* nlmsg, unsigned int offset,
#endif
#if SYZ_EXECUTOR || SYZ_NET_DEVICES || SYZ_802154
+
+// Force few TX and RX queues per interface to avoid creating 2 sysfs entries
+// per CPU per interface which takes a long time on machines with many cores.
+static unsigned int queue_count = 2;
+
static void netlink_add_device_impl(struct nlmsg* nlmsg, const char* type,
const char* name, bool up)
{
@@ -380,6 +385,10 @@ static void netlink_add_device_impl(struct nlmsg* nlmsg, const char* type,
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_attr(nlmsg, IFLA_NUM_TX_QUEUES, &queue_count, sizeof(queue_count));
+ netlink_attr(nlmsg, IFLA_NUM_RX_QUEUES, &queue_count, sizeof(queue_count));
+
netlink_nest(nlmsg, IFLA_LINKINFO);
netlink_attr(nlmsg, IFLA_INFO_KIND, type, strlen(type));
}
@@ -405,6 +414,8 @@ static void netlink_add_veth(struct nlmsg* nlmsg, int sock, const char* name,
netlink_nest(nlmsg, VETH_INFO_PEER);
nlmsg->pos += sizeof(struct ifinfomsg);
netlink_attr(nlmsg, IFLA_IFNAME, peer, strlen(peer));
+ netlink_attr(nlmsg, IFLA_NUM_TX_QUEUES, &queue_count, sizeof(queue_count));
+ netlink_attr(nlmsg, IFLA_NUM_RX_QUEUES, &queue_count, sizeof(queue_count));
netlink_done(nlmsg);
netlink_done(nlmsg);
netlink_done(nlmsg);