Skip to content

Commit eb97aa0

Browse files
committed
executor: support fragmentation in syz_emit_ethernet
A recent linux commit "tun: enable napi_gro_frags() for TUN/TAP driver" added support for fragmentation when emitting packets via tun. Support this feature in syz_emit_ethernet.
1 parent ffd2a08 commit eb97aa0

9 files changed

Lines changed: 195 additions & 46 deletions

File tree

executor/common_linux.h

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ static void execute_command(const char* format, ...)
263263
}
264264

265265
static int tunfd = -1;
266+
static int tun_frags_enabled;
266267

267268
// We just need this to be large enough to hold headers that we parse (ethernet/ip/tcp).
268269
// Rest of the packet (if any) will be silently truncated which is fine.
@@ -281,6 +282,13 @@ static int tunfd = -1;
281282
#define LOCAL_IPV6 "fe80::%02hxaa"
282283
#define REMOTE_IPV6 "fe80::%02hxbb"
283284

285+
#ifndef IFF_NAPI
286+
#define IFF_NAPI 0x0010
287+
#endif
288+
#ifndef IFF_NAPI_FRAGS
289+
#define IFF_NAPI_FRAGS 0x0020
290+
#endif
291+
284292
static void initialize_tun(uint64_t pid)
285293
{
286294
if (pid >= MAX_PIDS)
@@ -297,9 +305,19 @@ static void initialize_tun(uint64_t pid)
297305
struct ifreq ifr;
298306
memset(&ifr, 0, sizeof(ifr));
299307
strncpy(ifr.ifr_name, iface, IFNAMSIZ);
300-
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
301-
if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0)
302-
fail("tun: ioctl(TUNSETIFF) failed");
308+
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_NAPI | IFF_NAPI_FRAGS;
309+
if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0) {
310+
// IFF_NAPI_FRAGS requires root, so try without it.
311+
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
312+
if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0)
313+
fail("tun: ioctl(TUNSETIFF) failed");
314+
}
315+
// If IFF_NAPI_FRAGS is not supported it will be silently dropped,
316+
// so query the effective flags.
317+
if (ioctl(tunfd, TUNGETIFF, (void*)&ifr) < 0)
318+
fail("tun: ioctl(TUNGETIFF) failed");
319+
tun_frags_enabled = (ifr.ifr_flags & IFF_NAPI_FRAGS) != 0;
320+
debug("tun_frags_enabled=%d\n", tun_frags_enabled);
303321

304322
char local_mac[ADDR_MAX_LEN];
305323
snprintf_check(local_mac, sizeof(local_mac), LOCAL_MAC, id);
@@ -345,7 +363,10 @@ static int read_tun(char* data, int size)
345363
if (rv < 0) {
346364
if (errno == EAGAIN)
347365
return -1;
348-
fail("tun: read failed with %d, errno: %d", rv, errno);
366+
// Tun sometimes returns this, unclear if it's a kernel bug or not.
367+
if (errno == EBADFD)
368+
return -1;
369+
fail("tun: read failed with %d", rv);
349370
}
350371
return rv;
351372
}
@@ -366,17 +387,60 @@ static void debug_dump_data(const char* data, int length)
366387
#endif
367388

368389
#if defined(SYZ_EXECUTOR) || (defined(__NR_syz_emit_ethernet) && defined(SYZ_TUN_ENABLE))
369-
static uintptr_t syz_emit_ethernet(uintptr_t a0, uintptr_t a1)
370-
{
371-
// syz_emit_ethernet(len len[packet], packet ptr[in, eth_packet])
390+
#define MAX_FRAGS 4
391+
struct vnet_fragmentation {
392+
uint32_t full;
393+
uint32_t count;
394+
uint32_t frags[MAX_FRAGS];
395+
};
372396

397+
static uintptr_t syz_emit_ethernet(uintptr_t a0, uintptr_t a1, uintptr_t a2)
398+
{
399+
// syz_emit_ethernet(len len[packet], packet ptr[in, eth_packet], frags ptr[in, vnet_fragmentation, opt])
400+
// vnet_fragmentation {
401+
// full int32[0:1]
402+
// count len[frags, int32]
403+
// frags array[int32[0:4096], 1:4]
404+
// }
373405
if (tunfd < 0)
374406
return (uintptr_t)-1;
375407

376-
int64_t length = a0;
408+
uint32_t length = a0;
377409
char* data = (char*)a1;
378410
debug_dump_data(data, length);
379-
return write(tunfd, data, length);
411+
412+
struct vnet_fragmentation* frags = (struct vnet_fragmentation*)a2;
413+
struct iovec vecs[MAX_FRAGS + 1];
414+
uint32_t nfrags = 0;
415+
if (!tun_frags_enabled || frags == NULL) {
416+
vecs[nfrags].iov_base = data;
417+
vecs[nfrags].iov_len = length;
418+
nfrags++;
419+
} else {
420+
bool full = true;
421+
uint32_t i, count = 0;
422+
NONFAILING(full = frags->full);
423+
NONFAILING(count = frags->count);
424+
if (count > MAX_FRAGS)
425+
count = MAX_FRAGS;
426+
for (i = 0; i < count && length != 0; i++) {
427+
uint32_t size = 0;
428+
NONFAILING(size = frags->frags[i]);
429+
if (size > length)
430+
size = length;
431+
vecs[nfrags].iov_base = data;
432+
vecs[nfrags].iov_len = size;
433+
nfrags++;
434+
data += size;
435+
length -= size;
436+
}
437+
if (length != 0 && (full || nfrags == 0)) {
438+
vecs[nfrags].iov_base = data;
439+
vecs[nfrags].iov_len = length;
440+
nfrags++;
441+
}
442+
}
443+
return writev(tunfd, vecs, nfrags);
380444
}
381445
#endif
382446

@@ -685,8 +749,6 @@ static bool write_file(const char* file, const char* what, ...)
685749
#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE)
686750
static int real_uid;
687751
static int real_gid;
688-
static int epid;
689-
static bool etun;
690752
__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20];
691753

692754
static int namespace_sandbox_proc(void* arg)
@@ -700,12 +762,6 @@ static int namespace_sandbox_proc(void* arg)
700762
if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid))
701763
fail("write of /proc/self/gid_map failed");
702764

703-
#if defined(SYZ_EXECUTOR) || defined(SYZ_TUN_ENABLE)
704-
// For sandbox namespace we setup tun after initializing uid mapping,
705-
// otherwise ip commands fail.
706-
setup_tun(epid, etun);
707-
#endif
708-
709765
if (mkdir("./syz-tmp", 0777))
710766
fail("mkdir(syz-tmp) failed");
711767
if (mount("", "./syz-tmp", "tmpfs", 0, NULL))
@@ -759,10 +815,14 @@ static int namespace_sandbox_proc(void* arg)
759815

760816
static int do_sandbox_namespace(int executor_pid, bool enable_tun)
761817
{
818+
#if defined(SYZ_EXECUTOR) || defined(SYZ_TUN_ENABLE)
819+
// For sandbox namespace we setup tun before dropping privs,
820+
// because IFF_NAPI_FRAGS requires root.
821+
setup_tun(executor_pid, enable_tun);
822+
#endif
823+
762824
real_uid = getuid();
763825
real_gid = getgid();
764-
epid = executor_pid;
765-
etun = enable_tun;
766826
mprotect(sandbox_stack, 4096, PROT_NONE); // to catch stack underflows
767827
return clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64],
768828
CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL);

executor/syscalls_linux.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#if defined(__i386__) || 0
44
#define GOARCH "386"
5-
#define SYZ_REVISION "7b2f1949d48094cf3369932d0743db166049457b"
5+
#define SYZ_REVISION "20782fd14495c17a4a404316929c89e9d66373d6"
66
#define __NR_syz_emit_ethernet 1000000
77
#define __NR_syz_extract_tcp_res 1000001
88
#define __NR_syz_fuse_mount 1000002
@@ -1501,7 +1501,7 @@ call_t syscalls[] = {
15011501

15021502
#if defined(__x86_64__) || 0
15031503
#define GOARCH "amd64"
1504-
#define SYZ_REVISION "8349df62e623f9c8d8bfaefcc8ba3febf463ce92"
1504+
#define SYZ_REVISION "c1ab3550b8fbe92662ee054df76419327b5b72a7"
15051505
#define __NR_syz_emit_ethernet 1000000
15061506
#define __NR_syz_extract_tcp_res 1000001
15071507
#define __NR_syz_fuse_mount 1000002
@@ -3061,7 +3061,7 @@ call_t syscalls[] = {
30613061

30623062
#if defined(__arm__) || 0
30633063
#define GOARCH "arm"
3064-
#define SYZ_REVISION "02567c0623e18214f0be8d059aad68c263064645"
3064+
#define SYZ_REVISION "48d11406b51cb3dad2399863375cfc24d4892473"
30653065
#define __NR_syz_emit_ethernet 1000000
30663066
#define __NR_syz_extract_tcp_res 1000001
30673067
#define __NR_syz_fuse_mount 1000002
@@ -4574,7 +4574,7 @@ call_t syscalls[] = {
45744574

45754575
#if defined(__aarch64__) || 0
45764576
#define GOARCH "arm64"
4577-
#define SYZ_REVISION "0709fe60bdd20ea30d937d14615a40269fadb2b8"
4577+
#define SYZ_REVISION "65746b600515d1f1026ed56961d3450e70d65878"
45784578
#define __NR_syz_emit_ethernet 1000000
45794579
#define __NR_syz_extract_tcp_res 1000001
45804580
#define __NR_syz_fuse_mount 1000002
@@ -6062,7 +6062,7 @@ call_t syscalls[] = {
60626062

60636063
#if defined(__ppc64__) || defined(__PPC64__) || defined(__powerpc64__) || 0
60646064
#define GOARCH "ppc64le"
6065-
#define SYZ_REVISION "8bfb8686625fa3398eb8d1a5d66834dec0d3fa06"
6065+
#define SYZ_REVISION "5e9d29319478130f5d8b9238e869d979870e5cb3"
60666066
#define __NR_syz_emit_ethernet 1000000
60676067
#define __NR_syz_extract_tcp_res 1000001
60686068
#define __NR_syz_fuse_mount 1000002

pkg/csource/common.go

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ static void execute_command(const char* format, ...)
396396
}
397397
398398
static int tunfd = -1;
399+
static int tun_frags_enabled;
399400
400401
#define SYZ_TUN_MAX_PACKET_SIZE 1000
401402
@@ -411,6 +412,13 @@ static int tunfd = -1;
411412
#define LOCAL_IPV6 "fe80::%02hxaa"
412413
#define REMOTE_IPV6 "fe80::%02hxbb"
413414
415+
#ifndef IFF_NAPI
416+
#define IFF_NAPI 0x0010
417+
#endif
418+
#ifndef IFF_NAPI_FRAGS
419+
#define IFF_NAPI_FRAGS 0x0020
420+
#endif
421+
414422
static void initialize_tun(uint64_t pid)
415423
{
416424
if (pid >= MAX_PIDS)
@@ -427,9 +435,16 @@ static void initialize_tun(uint64_t pid)
427435
struct ifreq ifr;
428436
memset(&ifr, 0, sizeof(ifr));
429437
strncpy(ifr.ifr_name, iface, IFNAMSIZ);
430-
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
431-
if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0)
432-
fail("tun: ioctl(TUNSETIFF) failed");
438+
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_NAPI | IFF_NAPI_FRAGS;
439+
if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0) {
440+
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
441+
if (ioctl(tunfd, TUNSETIFF, (void*)&ifr) < 0)
442+
fail("tun: ioctl(TUNSETIFF) failed");
443+
}
444+
if (ioctl(tunfd, TUNGETIFF, (void*)&ifr) < 0)
445+
fail("tun: ioctl(TUNGETIFF) failed");
446+
tun_frags_enabled = (ifr.ifr_flags & IFF_NAPI_FRAGS) != 0;
447+
debug("tun_frags_enabled=%d\n", tun_frags_enabled);
433448
434449
char local_mac[ADDR_MAX_LEN];
435450
snprintf_check(local_mac, sizeof(local_mac), LOCAL_MAC, id);
@@ -472,7 +487,9 @@ static int read_tun(char* data, int size)
472487
if (rv < 0) {
473488
if (errno == EAGAIN)
474489
return -1;
475-
fail("tun: read failed with %d, errno: %d", rv, errno);
490+
if (errno == EBADFD)
491+
return -1;
492+
fail("tun: read failed with %d", rv);
476493
}
477494
return rv;
478495
}
@@ -493,16 +510,54 @@ static void debug_dump_data(const char* data, int length)
493510
#endif
494511
495512
#if defined(SYZ_EXECUTOR) || (defined(__NR_syz_emit_ethernet) && defined(SYZ_TUN_ENABLE))
496-
static uintptr_t syz_emit_ethernet(uintptr_t a0, uintptr_t a1)
497-
{
513+
#define MAX_FRAGS 4
514+
struct vnet_fragmentation {
515+
uint32_t full;
516+
uint32_t count;
517+
uint32_t frags[MAX_FRAGS];
518+
};
498519
520+
static uintptr_t syz_emit_ethernet(uintptr_t a0, uintptr_t a1, uintptr_t a2)
521+
{
499522
if (tunfd < 0)
500523
return (uintptr_t)-1;
501524
502-
int64_t length = a0;
525+
uint32_t length = a0;
503526
char* data = (char*)a1;
504527
debug_dump_data(data, length);
505-
return write(tunfd, data, length);
528+
529+
struct vnet_fragmentation* frags = (struct vnet_fragmentation*)a2;
530+
struct iovec vecs[MAX_FRAGS + 1];
531+
uint32_t nfrags = 0;
532+
if (!tun_frags_enabled || frags == NULL) {
533+
vecs[nfrags].iov_base = data;
534+
vecs[nfrags].iov_len = length;
535+
nfrags++;
536+
} else {
537+
bool full = true;
538+
uint32_t i, count = 0;
539+
NONFAILING(full = frags->full);
540+
NONFAILING(count = frags->count);
541+
if (count > MAX_FRAGS)
542+
count = MAX_FRAGS;
543+
for (i = 0; i < count && length != 0; i++) {
544+
uint32_t size = 0;
545+
NONFAILING(size = frags->frags[i]);
546+
if (size > length)
547+
size = length;
548+
vecs[nfrags].iov_base = data;
549+
vecs[nfrags].iov_len = size;
550+
nfrags++;
551+
data += size;
552+
length -= size;
553+
}
554+
if (length != 0 && (full || nfrags == 0)) {
555+
vecs[nfrags].iov_base = data;
556+
vecs[nfrags].iov_len = length;
557+
nfrags++;
558+
}
559+
}
560+
return writev(tunfd, vecs, nfrags);
506561
}
507562
#endif
508563
@@ -1745,8 +1800,6 @@ static bool write_file(const char* file, const char* what, ...)
17451800
#if defined(SYZ_EXECUTOR) || defined(SYZ_SANDBOX_NAMESPACE)
17461801
static int real_uid;
17471802
static int real_gid;
1748-
static int epid;
1749-
static bool etun;
17501803
__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20];
17511804
17521805
static int namespace_sandbox_proc(void* arg)
@@ -1759,10 +1812,6 @@ static int namespace_sandbox_proc(void* arg)
17591812
if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid))
17601813
fail("write of /proc/self/gid_map failed");
17611814
1762-
#if defined(SYZ_EXECUTOR) || defined(SYZ_TUN_ENABLE)
1763-
setup_tun(epid, etun);
1764-
#endif
1765-
17661815
if (mkdir("./syz-tmp", 0777))
17671816
fail("mkdir(syz-tmp) failed");
17681817
if (mount("", "./syz-tmp", "tmpfs", 0, NULL))
@@ -1812,10 +1861,12 @@ static int namespace_sandbox_proc(void* arg)
18121861
18131862
static int do_sandbox_namespace(int executor_pid, bool enable_tun)
18141863
{
1864+
#if defined(SYZ_EXECUTOR) || defined(SYZ_TUN_ENABLE)
1865+
setup_tun(executor_pid, enable_tun);
1866+
#endif
1867+
18151868
real_uid = getuid();
18161869
real_gid = getgid();
1817-
epid = executor_pid;
1818-
etun = enable_tun;
18191870
mprotect(sandbox_stack, 4096, PROT_NONE);
18201871
return clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64],
18211872
CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWNET, NULL);

sys/linux/386.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5563,6 +5563,11 @@ var structDescs_386 = []*KeyedStruct{
55635563
&IntType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "int16", FldName: "dei", TypeSize: 2}, BitfieldOff: 3, BitfieldLen: 1, BitfieldMdl: true}},
55645564
&IntType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "int16", FldName: "vid", TypeSize: 2}, BitfieldOff: 4, BitfieldLen: 12}},
55655565
}}},
5566+
{Key: StructKey{Name: "vnet_fragmentation"}, Desc: &StructDesc{TypeCommon: TypeCommon{TypeName: "vnet_fragmentation"}, Fields: []Type{
5567+
&IntType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "int32", FldName: "full", TypeSize: 4}}, Kind: 2, RangeEnd: 1},
5568+
&LenType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "len", FldName: "count", TypeSize: 4}}, Buf: "frags"},
5569+
&ArrayType{TypeCommon: TypeCommon{TypeName: "array", FldName: "frags"}, Type: &IntType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "int32", TypeSize: 4}}, Kind: 2, RangeEnd: 4096}, Kind: 1, RangeBegin: 1, RangeEnd: 4},
5570+
}}},
55665571
{Key: StructKey{Name: "vt_consize"}, Desc: &StructDesc{TypeCommon: TypeCommon{TypeName: "vt_consize", TypeSize: 12}, Fields: []Type{
55675572
&IntType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "int16", FldName: "rows", TypeSize: 2}}},
55685573
&IntType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "int16", FldName: "cols", TypeSize: 2}}},
@@ -13119,6 +13124,7 @@ var syscalls_386 = []*Syscall{
1311913124
{ID: 1333, NR: 1000000, Name: "syz_emit_ethernet", CallName: "syz_emit_ethernet", Args: []Type{
1312013125
&LenType{IntTypeCommon: IntTypeCommon{TypeCommon: TypeCommon{TypeName: "len", FldName: "len", TypeSize: 4}}, Buf: "packet"},
1312113126
&PtrType{TypeCommon: TypeCommon{TypeName: "ptr", FldName: "packet", TypeSize: 4}, Type: &StructType{Key: StructKey{Name: "eth_packet"}}},
13127+
&PtrType{TypeCommon: TypeCommon{TypeName: "ptr", FldName: "frags", TypeSize: 4, IsOptional: true}, Type: &StructType{Key: StructKey{Name: "vnet_fragmentation"}}},
1312213128
}},
1312313129
{ID: 1334, NR: 1000001, Name: "syz_extract_tcp_res", CallName: "syz_extract_tcp_res", Args: []Type{
1312413130
&PtrType{TypeCommon: TypeCommon{TypeName: "ptr", FldName: "res", TypeSize: 4}, Type: &StructType{Key: StructKey{Name: "tcp_resources", Dir: 1}}},
@@ -16734,4 +16740,4 @@ var consts_386 = []ConstValue{
1673416740
{Name: "__WNOTHREAD", Value: 536870912},
1673516741
}
1673616742

16737-
const revision_386 = "7b2f1949d48094cf3369932d0743db166049457b"
16743+
const revision_386 = "20782fd14495c17a4a404316929c89e9d66373d6"

0 commit comments

Comments
 (0)