sd-device: bound the tag filter BPF program size

The tag-match loop emits 6 instructions per tag into a fixed 512-entry
stack array without a bounds check (unlike the subsystem loop below),
so enough tags overflow the stack. Refuse with -E2BIG instead.

Follow-up for b1c097af8d

(cherry picked from commit 2f72db10d9)
(cherry picked from commit 5867697d6e)
(cherry picked from commit 6986776982)
This commit is contained in:
Luca Boccassi
2026-07-02 17:55:37 +01:00
parent 225b29b996
commit e2703f4eab
2 changed files with 70 additions and 27 deletions

View File

@@ -781,30 +781,57 @@ int device_monitor_send(
return count;
}
static void bpf_stmt(struct sock_filter *ins, unsigned *i,
unsigned short code, unsigned data) {
static int bpf_stmt_impl(struct sock_filter *ins, size_t *i, size_t n_ins,
unsigned short code, unsigned data) {
assert(ins);
assert(i);
if (*i >= n_ins)
return -E2BIG;
ins[(*i)++] = (struct sock_filter) {
.code = code,
.k = data,
};
return 0;
}
static void bpf_jmp(struct sock_filter *ins, unsigned *i,
unsigned short code, unsigned data,
unsigned short jt, unsigned short jf) {
#define bpf_stmt(ins, i, code, data) \
bpf_stmt_impl((ins), (i), ELEMENTSOF(ins), (code), (data))
static int bpf_jmp_impl(struct sock_filter *ins, size_t *i, size_t n_ins,
unsigned short code, unsigned data,
unsigned jt, unsigned jf) {
assert(ins);
assert(i);
if (*i >= n_ins)
return -E2BIG;
/* The jump offsets are stored in single bytes (struct sock_filter.jt/.jf are __u8). A larger
* offset would be silently truncated and make the filter branch to the wrong instruction, i.e.
* drop events that should match. */
if (jt > UINT8_MAX || jf > UINT8_MAX)
return -E2BIG;
ins[(*i)++] = (struct sock_filter) {
.code = code,
.jt = jt,
.jf = jf,
.k = data,
};
return 0;
}
#define bpf_jmp(ins, i, code, data, jt, jf) \
bpf_jmp_impl((ins), (i), ELEMENTSOF(ins), (code), (data), (jt), (jf))
_public_ int sd_device_monitor_filter_update(sd_device_monitor *m) {
struct sock_filter ins[512] = {};
struct sock_fprog filter;
const char *subsystem, *devtype, *tag;
unsigned i = 0;
size_t i = 0;
int r;
assert_return(m, -EINVAL);
@@ -819,11 +846,11 @@ _public_ int sd_device_monitor_filter_update(sd_device_monitor *m) {
}
/* load magic in A */
bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, magic));
r = bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, magic));
/* jump if magic matches */
bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0);
RET_GATHER(r, bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0));
/* wrong magic, pass packet */
bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
RET_GATHER(r, bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff));
if (!set_isempty(m->tag_filter)) {
int tag_matches = set_size(m->tag_filter);
@@ -835,23 +862,23 @@ _public_ int sd_device_monitor_filter_update(sd_device_monitor *m) {
uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff;
/* load device bloom bits in A */
bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_tag_bloom_hi));
RET_GATHER(r, bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_tag_bloom_hi)));
/* clear bits (tag bits & bloom bits) */
bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi);
RET_GATHER(r, bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi));
/* jump to next tag if it does not match */
bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3);
RET_GATHER(r, bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3));
/* load device bloom bits in A */
bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_tag_bloom_lo));
RET_GATHER(r, bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_tag_bloom_lo)));
/* clear bits (tag bits & bloom bits) */
bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo);
RET_GATHER(r, bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo));
/* jump behind end of tag match block if tag matches */
tag_matches--;
bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0);
RET_GATHER(r, bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0));
}
/* nothing matched, drop packet */
bpf_stmt(ins, &i, BPF_RET|BPF_K, 0);
RET_GATHER(r, bpf_stmt(ins, &i, BPF_RET|BPF_K, 0));
}
/* add all subsystem matches */
@@ -860,33 +887,33 @@ _public_ int sd_device_monitor_filter_update(sd_device_monitor *m) {
uint32_t hash = string_hash32(subsystem);
/* load device subsystem value in A */
bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_subsystem_hash));
RET_GATHER(r, bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_subsystem_hash)));
if (!devtype) {
/* jump if subsystem does not match */
bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1);
RET_GATHER(r, bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1));
} else {
/* jump if subsystem does not match */
bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3);
RET_GATHER(r, bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3));
/* load device devtype value in A */
bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_devtype_hash));
RET_GATHER(r, bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(monitor_netlink_header, filter_devtype_hash)));
/* jump if value does not match */
hash = string_hash32(devtype);
bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1);
RET_GATHER(r, bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1));
}
/* matched, pass packet */
bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
if (i+1 >= ELEMENTSOF(ins))
return -E2BIG;
RET_GATHER(r, bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff));
}
/* nothing matched, drop packet */
bpf_stmt(ins, &i, BPF_RET|BPF_K, 0);
RET_GATHER(r, bpf_stmt(ins, &i, BPF_RET|BPF_K, 0));
}
/* matched, pass packet */
bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff);
RET_GATHER(r, bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff));
if (r < 0)
return r;
/* install filter */
filter = (struct sock_fprog) {

View File

@@ -13,6 +13,7 @@
#include "path-util.h"
#include "socket-util.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "tests.h"
#include "time-util.h"
@@ -275,6 +276,21 @@ TEST(sd_device_monitor_filter_add_match_tag) {
ASSERT_EQ(sd_event_loop(sd_device_monitor_get_event(monitor_client)), 100);
}
TEST(sd_device_monitor_filter_update_bounds) {
_cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL;
ASSERT_OK(device_monitor_new_full(&m, MONITOR_GROUP_NONE, -EBADF));
/* Each tag emits 6 BPF instructions into a fixed 512-entry array, too many must be refused */
for (unsigned u = 0; u < 200; u++) {
char t[32];
xsprintf(t, "tag%u", u);
ASSERT_OK(sd_device_monitor_filter_add_match_tag(m, t));
}
ASSERT_ERROR(sd_device_monitor_filter_update(m), E2BIG);
}
TEST(sd_device_monitor_filter_add_match_sysattr) {
_cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor_server = NULL, *monitor_client = NULL;
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;