Memory leak while using NFQUEUE to delegate the decision on TCP packets to userspace processes

Bug #1991774 reported by Chengen Du
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
linux (Ubuntu)
Fix Committed
Undecided
Chengen Du
Bionic
Fix Released
Undecided
Chengen Du

Bug Description

[Impact]

Environment: v4.15.0-177-generic Xenial ESM

Using NFQUEUE to delegate the decision on TCP packets to userspace processes will cause memory leak.
The symptom is that TCP slab objects will accumulate and eventually cause OOM.

[Fix]

There is a discrepancy between backport and upstream commit.

[Upstream Commit c3873070247d9e3c7a6b0cf9bf9b45e8018427b1]
diff --git a/include/net/netfilter/nf_queue.h b/include/net/netfilter/nf_queue.h
index 9eed51e920e8..980daa6e1e3a 100644
--- a/include/net/netfilter/nf_queue.h
+++ b/include/net/netfilter/nf_queue.h
@@ -37,7 +37,7 @@ void nf_register_queue_handler(const struct nf_queue_handler *qh);
 void nf_unregister_queue_handler(void);
 void nf_reinject(struct nf_queue_entry *entry, unsigned int verdict);

-void nf_queue_entry_get_refs(struct nf_queue_entry *entry);
+bool nf_queue_entry_get_refs(struct nf_queue_entry *entry);
 void nf_queue_entry_free(struct nf_queue_entry *entry);

 static inline void init_hashrandom(u32 *jhash_initval)
diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c
index 5ab0680db445..e39549c55945 100644
--- a/net/netfilter/nf_queue.c
+++ b/net/netfilter/nf_queue.c
@@ -96,19 +96,21 @@ static void __nf_queue_entry_init_physdevs(struct nf_queue_entry *entry)
 }

 /* Bump dev refs so they don't vanish while packet is out */
-void nf_queue_entry_get_refs(struct nf_queue_entry *entry)
+bool nf_queue_entry_get_refs(struct nf_queue_entry *entry)
 {
  struct nf_hook_state *state = &entry->state;

+ if (state->sk && !refcount_inc_not_zero(&state->sk->sk_refcnt))
+ return false;
+
  dev_hold(state->in);
  dev_hold(state->out);
- if (state->sk)
- sock_hold(state->sk);

 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
  dev_hold(entry->physin);
  dev_hold(entry->physout);
 #endif
+ return true;
 }
 EXPORT_SYMBOL_GPL(nf_queue_entry_get_refs);

@@ -196,7 +198,10 @@ static int __nf_queue(struct sk_buff *skb, const struct nf_hook_state *state,

  __nf_queue_entry_init_physdevs(entry);

- nf_queue_entry_get_refs(entry);
+ if (!nf_queue_entry_get_refs(entry)) {
+ kfree(entry);
+ return -ENOTCONN;
+ }

  switch (entry->state.pf) {
  case AF_INET:
diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
index ea2d9c2a44cf..64a6acb6aeae 100644
--- a/net/netfilter/nfnetlink_queue.c
+++ b/net/netfilter/nfnetlink_queue.c
@@ -710,9 +710,15 @@ static struct nf_queue_entry *
 nf_queue_entry_dup(struct nf_queue_entry *e)
 {
  struct nf_queue_entry *entry = kmemdup(e, e->size, GFP_ATOMIC);
- if (entry)
- nf_queue_entry_get_refs(entry);
- return entry;
+
+ if (!entry)
+ return NULL;
+
+ if (nf_queue_entry_get_refs(entry))
+ return entry;
+
+ kfree(entry);
+ return NULL;
 }

 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)

[Backport Commit 4d032d60432327f068f03b516f5b6c51ceb17d15]
diff --git a/include/net/netfilter/nf_queue.h b/include/net/netfilter/nf_queue.h
index 814058d0f167..f38cc6092c5a 100644
--- a/include/net/netfilter/nf_queue.h
+++ b/include/net/netfilter/nf_queue.h
@@ -32,7 +32,7 @@ void nf_register_queue_handler(struct net *net, const struct nf_queue_handler *q
 void nf_unregister_queue_handler(struct net *net);
 void nf_reinject(struct nf_queue_entry *entry, unsigned int verdict);

-void nf_queue_entry_get_refs(struct nf_queue_entry *entry);
+bool nf_queue_entry_get_refs(struct nf_queue_entry *entry);
 void nf_queue_entry_release_refs(struct nf_queue_entry *entry);

 static inline void init_hashrandom(u32 *jhash_initval)
diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c
index 59340b3ef7ef..dbc45165c533 100644
--- a/net/netfilter/nf_queue.c
+++ b/net/netfilter/nf_queue.c
@@ -80,10 +80,13 @@ void nf_queue_entry_release_refs(struct nf_queue_entry *entry)
 EXPORT_SYMBOL_GPL(nf_queue_entry_release_refs);

 /* Bump dev refs so they don't vanish while packet is out */
-void nf_queue_entry_get_refs(struct nf_queue_entry *entry)
+bool nf_queue_entry_get_refs(struct nf_queue_entry *entry)
 {
  struct nf_hook_state *state = &entry->state;

+ if (state->sk && !refcount_inc_not_zero(&state->sk->sk_refcnt))
+ return false;
+
  if (state->in)
   dev_hold(state->in);
  if (state->out)
@@ -102,6 +105,7 @@ void nf_queue_entry_get_refs(struct nf_queue_entry *entry)
    dev_hold(physdev);
  }
 #endif
+ return true;
 }
 EXPORT_SYMBOL_GPL(nf_queue_entry_get_refs);

@@ -159,7 +163,11 @@ static int __nf_queue(struct sk_buff *skb, const struct nf_hook_state *state,
   .size = sizeof(*entry) + afinfo->route_key_size,
  };

- nf_queue_entry_get_refs(entry);
+ if (!nf_queue_entry_get_refs(entry)) {
+ kfree(entry);
+ return -ENOTCONN;
+ }
+
  afinfo->saveroute(skb, entry);
  status = qh->outfn(entry, queuenum);

diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
index 48ed30e1b405..abad8bf6fa38 100644
--- a/net/netfilter/nfnetlink_queue.c
+++ b/net/netfilter/nfnetlink_queue.c
@@ -693,9 +693,15 @@ static struct nf_queue_entry *
 nf_queue_entry_dup(struct nf_queue_entry *e)
 {
  struct nf_queue_entry *entry = kmemdup(e, e->size, GFP_ATOMIC);
- if (entry)
- nf_queue_entry_get_refs(entry);
- return entry;
+
+ if (!entry)
+ return NULL;
+
+ if (nf_queue_entry_get_refs(entry))
+ return entry;
+
+ kfree(entry);
+ return NULL;
 }

 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)

[Difference between Commits]
50,57c57,62
< dev_hold(state->in);
< dev_hold(state->out);
< - if (state->sk)
< - sock_hold(state->sk);
<
< #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
< dev_hold(entry->physin);
< dev_hold(entry->physout);
---
> if (state->in)
> dev_hold(state->in);
> if (state->out)
> @@ -102,6 +105,7 @@ void nf_queue_entry_get_refs(struct nf_queue_entry *entry)
> dev_hold(physdev);
> }

The sock_hold() logic still remains in the backport commit, which will affect the reference count and result in memory leak.
The fix aligns the logic with the upstream commit.

[Test Plan]

1. Prepare a VM and run a TCP server on host.
2. Enter into the VM and set up a iptables rule.
iptables -I OUTPUT -p tcp --dst <-TCP_SERVER_IP-> -j NFQUEUE --queue-num=1 --queue-bypass
3. Run a nfnetlink client (should listen on NF queue 1) on VM.
4. Keep connecting the TCP server from VM.
while true; do netcat <-TCP_SERVER_IP-> 8080; done
5. The VM's TCP slab objects will accumulate and eventually encounter OOM situation.
cat /proc/slabinfo | grep TCP

[Where problems could occur]

The fix just aligns the logic with the upstream commit, so the regression can be considered as low.

CVE References

Chengen Du (chengendu)
description: updated
Revision history for this message
Ubuntu Kernel Bot (ubuntu-kernel-bot) wrote : Missing required logs.

This bug is missing log files that will aid in diagnosing the problem. While running an Ubuntu kernel (not a mainline or third-party kernel) please enter the following command in a terminal window:

apport-collect 1991774

and then change the status of the bug to 'Confirmed'.

If, due to the nature of the issue you have encountered, you are unable to run this command, please add a comment stating that fact and change the bug status to 'Confirmed'.

This change has been made by an automated script, maintained by the Ubuntu Kernel Team.

Changed in linux (Ubuntu):
status: New → Incomplete
Chengen Du (chengendu)
Changed in linux (Ubuntu Bionic):
assignee: nobody → ChengEn, Du (chengendu)
Changed in linux (Ubuntu):
assignee: nobody → ChengEn, Du (chengendu)
status: Incomplete → In Progress
Changed in linux (Ubuntu Bionic):
status: New → In Progress
tags: added: bionic sts
Changed in linux (Ubuntu Bionic):
status: In Progress → Fix Committed
Chengen Du (chengendu)
Changed in linux (Ubuntu):
status: In Progress → Fix Committed
Revision history for this message
Ubuntu Kernel Bot (ubuntu-kernel-bot) wrote :

This bug is awaiting verification that the linux/4.15.0-195.206 kernel in -proposed solves the problem. Please test the kernel and update this bug with the results. If the problem is solved, change the tag 'verification-needed-bionic' to 'verification-done-bionic'. If the problem still exists, change the tag 'verification-needed-bionic' to 'verification-failed-bionic'.

If verification is not done by 5 working days from today, this fix will be dropped from the source code, and this bug will be closed.

See https://wiki.ubuntu.com/Testing/EnableProposed for documentation how to enable and use -proposed. Thank you!

Revision history for this message
Chengen Du (chengendu) wrote :

I have used the linux/4.15.0-195.206 kernel in -proposed for testing.
The issue no longer exists by testing with the same test plan.

tags: added: verification-done-bionic
Revision history for this message
Launchpad Janitor (janitor) wrote :
Download full text (19.8 KiB)

This bug was fixed in the package linux - 4.15.0-197.208

---------------
linux (4.15.0-197.208) bionic; urgency=medium

  * bionic/linux: 4.15.0-197.208 -proposed tracker (LP: #1994998)

  * Memory leak while using NFQUEUE to delegate the decision on TCP packets to
    userspace processes (LP: #1991774)
    - SAUCE: netfilter: nf_queue: Fix memory leak in nf_queue_entry_get_refs

  * Bionic update: upstream stable patchset 2022-09-23 (LP: #1990698)
    - Bluetooth: L2CAP: Fix use-after-free caused by l2cap_chan_put
    - ntfs: fix use-after-free in ntfs_ucsncmp()
    - ARM: crypto: comment out gcc warning that breaks clang builds
    - mt7601u: add USB device ID for some versions of XiaoDu WiFi Dongle.
    - ACPI: video: Force backlight native for some TongFang devices
    - macintosh/adb: fix oob read in do_adb_query() function
    - Makefile: link with -z noexecstack --no-warn-rwx-segments
    - x86: link vdso and boot with -z noexecstack --no-warn-rwx-segments
    - ALSA: bcd2000: Fix a UAF bug on the error path of probing
    - add barriers to buffer_uptodate and set_buffer_uptodate
    - HID: wacom: Don't register pad_input for touch switch
    - KVM: SVM: Don't BUG if userspace injects an interrupt with GIF=0
    - KVM: x86: Mark TSS busy during LTR emulation _after_ all fault checks
    - KVM: x86: Set error code to segment selector on LLDT/LTR non-canonical #GP
    - ALSA: hda/conexant: Add quirk for LENOVO 20149 Notebook model
    - ALSA: hda/cirrus - support for iMac 12,1 model
    - vfs: Check the truncate maximum size in inode_newsize_ok()
    - fs: Add missing umask strip in vfs_tmpfile
    - usbnet: Fix linkwatch use-after-free on disconnect
    - parisc: Fix device names in /proc/iomem
    - drm/nouveau: fix another off-by-one in nvbios_addr
    - drm/amdgpu: Check BO's requested pinning domains against its
      preferred_domains
    - iio: light: isl29028: Fix the warning in isl29028_remove()
    - fuse: limit nsec
    - md-raid10: fix KASAN warning
    - ia64, processor: fix -Wincompatible-pointer-types in ia64_get_irr()
    - PCI: Add defines for normal and subtractive PCI bridges
    - powerpc/fsl-pci: Fix Class Code of PCIe Root Port
    - powerpc/powernv: Avoid crashing if rng is NULL
    - MIPS: cpuinfo: Fix a warning for CONFIG_CPUMASK_OFFSTACK
    - USB: HCD: Fix URB giveback issue in tasklet function
    - netfilter: nf_tables: fix null deref due to zeroed list head
    - arm64: Do not forget syscall when starting a new thread.
    - arm64: fix oops in concurrently setting insn_emulation sysctls
    - ext2: Add more validity checks for inode counts
    - ARM: dts: imx6ul: add missing properties for sram
    - ARM: dts: imx6ul: fix qspi node compatible
    - ARM: OMAP2+: display: Fix refcount leak bug
    - ACPI: PM: save NVS memory for Lenovo G40-45
    - ACPI: LPSS: Fix missing check in register_device_clock()
    - PM: hibernate: defer device probing when resuming from hibernation
    - selinux: Add boundary check in put_entry()
    - ARM: findbit: fix overflowing offset
    - ARM: bcm: Fix refcount leak in bcm_kona_smc_init
    - x86/pmem: Fix platform-device leak in error path
    - ARM: dts: ast2500-evb: fix bo...

Changed in linux (Ubuntu Bionic):
status: Fix Committed → Fix Released
Revision history for this message
Ubuntu Kernel Bot (ubuntu-kernel-bot) wrote :

This bug is awaiting verification that the linux-azure-4.15/4.15.0-1157.172 kernel in -proposed solves the problem. Please test the kernel and update this bug with the results. If the problem is solved, change the tag 'verification-needed-bionic' to 'verification-done-bionic'. If the problem still exists, change the tag 'verification-needed-bionic' to 'verification-failed-bionic'.

If verification is not done by 5 working days from today, this fix will be dropped from the source code, and this bug will be closed.

See https://wiki.ubuntu.com/Testing/EnableProposed for documentation how to enable and use -proposed. Thank you!

tags: added: kernel-spammed-bionic-linux-azure-4.15 verification-needed-bionic
removed: verification-done-bionic
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.