Skip to contentSkip to conversation

bpf: Fix use-after-free in offloaded map/prog info fill

PATCH Superseded Jiayuan Chen 3 participants 4 messages Apr 8 – Apr 9 BPF

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 bpf: Fix use-after-free in offloaded map/prog info fill

When querying info for an offloaded BPF map or program, bpf_map_offload_info_fill_ns() and bpf_prog_offload_info_fill_ns() obtain the network namespace with get_net(dev_net(offmap->netdev)). However, the associated netdev's netns may be racing with teardown during netns destruction. If the netns refcount has already reached 0, get_net() performs a refcount_t increment on 0, triggering:

refcount_t: addition on 0; use-after-free.

Although rtnl_lock and bpf_devs_lock ensure the netdev pointer remains valid, they cannot prevent the netns refcount from reaching zero.

Fix this by using maybe_get_net() instead of get_net(). maybe_get_net() uses refcount_inc_not_zero() and returns NULL if the refcount is already zero, which causes ns_get_path_cb() to fail and the caller to return -ENODEV -- the correct behavior when the netns is being destroyed.

Fixes 675fc275a3a2d ("bpf: offload: report device information for offloaded programs")
Fixes 52775b33bb507 ("bpf: offload: report device information about offloaded maps")
---
 kernel/bpf/offload.c | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)
kernel/bpf/offload.c +12 -6
index 0ad97d643bf49..d6dc684d0e872 100644
@@ -435,9 +435,12 @@ static struct ns_common *bpf_prog_offload_info_fill_ns(void *private_data)
 
 	if (aux->offload) {
 		args->info->ifindex = aux->offload->netdev->ifindex;
-		net = dev_net(aux->offload->netdev);
-		get_net(net);
-		ns = &net->ns;
+		net = maybe_get_net(dev_net(aux->offload->netdev));
+		if (net) {
+			ns = &net->ns;
+		} else {
+			ns = NULL;
+		}
 	} else {
 		args->info->ifindex = 0;
 		ns = NULL;
@@ -647,9 +650,12 @@ static struct ns_common *bpf_map_offload_info_fill_ns(void *private_data)
 
 	if (args->offmap->netdev) {
 		args->info->ifindex = args->offmap->netdev->ifindex;
-		net = dev_net(args->offmap->netdev);
-		get_net(net);
-		ns = &net->ns;
+		net = maybe_get_net(dev_net(args->offmap->netdev));
+		if (net) {
+			ns = &net->ns;
+		} else {
+			ns = NULL;
+		}
 	} else {
 		args->info->ifindex = 0;
 		ns = NULL;
Signature · 1 line
2.43.0
maintainer
#1 lore raw
Quoted 9 lines · Jiayuan Chen

When querying info for an offloaded BPF map or program, bpf_map_offload_info_fill_ns() and bpf_prog_offload_info_fill_ns() obtain the network namespace with get_net(dev_net(offmap->netdev)). However, the associated netdev's netns may be racing with teardown during netns destruction. If the netns refcount has already reached 0, get_net() performs a refcount_t increment on 0, triggering:

refcount_t: addition on 0; use-after-free.

Quoted 7 lines

Although rtnl_lock and bpf_devs_lock ensure the netdev pointer remains valid, they cannot prevent the netns refcount from reaching zero.

Fix this by using maybe_get_net() instead of get_net(). maybe_get_net() uses refcount_inc_not_zero() and returns NULL if the refcount is already zero, which causes ns_get_path_cb() to fail and the caller to return -ENODEV -- the correct behavior when the netns is being destroyed.

nit: -ENOENT

Quoted 18 lines
Fixes 675fc275a3a2d ("bpf: offload: report device information for offloaded programs")
Fixes 52775b33bb507 ("bpf: offload: report device information about offloaded maps")
---
  kernel/bpf/offload.c | 18 ++++++++++++------
  1 file changed, 12 insertions(+), 6 deletions(-)
kernel/bpf/offload.c +0 -0
index 0ad97d643bf49..d6dc684d0e872 100644
@@ -435,9 +435,12 @@ static struct ns_common *bpf_prog_offload_info_fill_ns(void *private_data)
 
  	if (aux->offload) {

kernel/bpf/offload.c static struct ns_common *bpf_prog_offload_info_fill_ns(void *private_data) · line 443

Quoted 10 lines
kernel/bpf/offload.c +6 -3
  		args->info->ifindex = aux->offload->netdev->ifindex;
-		net = dev_net(aux->offload->netdev);
-		get_net(net);
-		ns = &net->ns;
+		net = maybe_get_net(dev_net(aux->offload->netdev));
+		if (net) {
+			ns = &net->ns;
+		} else {
+			ns = NULL;
+		}

nit: ns = net ? &net->ns : NULL;

Quoted 6 lines
kernel/bpf/offload.c +0 -0
  	} else {
  		args->info->ifindex = 0;
  		ns = NULL;
@@ -647,9 +650,12 @@ static struct ns_common *bpf_map_offload_info_fill_ns(void *private_data)
 
  	if (args->offmap->netdev) {

kernel/bpf/offload.c static struct ns_common *bpf_map_offload_info_fill_ns(void *private_data) · line 658

Quoted 10 lines
kernel/bpf/offload.c +6 -3
  		args->info->ifindex = args->offmap->netdev->ifindex;
-		net = dev_net(args->offmap->netdev);
-		get_net(net);
-		ns = &net->ns;
+		net = maybe_get_net(dev_net(args->offmap->netdev));
+		if (net) {
+			ns = &net->ns;
+		} else {
+			ns = NULL;
+		}

ditto, otherwise looks good to me

Quoted 3 lines
kernel/bpf/offload.c +0 -0
  	} else {
  		args->info->ifindex = 0;
  		ns = NULL;
#2 lore raw
Quoted 3 lines · Daniel Borkmann
Quoted 2 lines · Jiayuan Chen

When querying info for an offloaded BPF map or program,

[...]

Quoted 19 lines
Quoted 2 lines

zero, which causes ns_get_path_cb() to fail and the caller to return -ENODEV -- the correct behavior when the netns is being destroyed.

nit: -ENOENT

Quoted 10 lines
Fixes 675fc275a3a2d ("bpf: offload: report device information for offloaded programs")
Fixes 52775b33bb507 ("bpf: offload: report device information about offloaded maps")
[...]

+        net = maybe_get_net(dev_net(aux->offload->netdev)); +        if (net) {
+            ns = &net->ns;
+        } else {
+            ns = NULL;
+        }

nit: ns = net ? &net->ns : NULL;

Quoted 1 line

[...]

Quoted 8 lines
Quoted 1 line

+        }

ditto, otherwise looks good to me

Quoted 3 lines

      } else {
          args->info->ifindex = 0;
          ns = NULL;

 Thanks for the review!

 Both nits are valid -- will fix the commit message to say -ENOENT and simplify to ternary in the next version.

#3 lore raw
In reply to Jiayuan Chen
Should this say -ENOENT rather than -ENODEV? When maybe_get_net() returns NULL, ns_get_path_cb() returns -ENOENT:
Quoted 4 lines

Fix this by using maybe_get_net() instead of get_net(). maybe_get_net() uses refcount_inc_not_zero() and returns NULL if the refcount is already zero, which causes ns_get_path_cb() to fail and the caller to return -ENODEV -- the correct behavior when the netns is being destroyed.

Should this say -ENOENT rather than -ENODEV? When maybe_get_net() returns NULL, ns_get_path_cb() returns -ENOENT:

fs/nsfs.c:ns_get_path_cb() {
    ns = ns_get_cb(private_data);
    if (!ns)
        return -ENOENT;
}

Since the callback sets info->ifindex before maybe_get_net() fails, bpf_prog_offload_info_fill() takes this path:

    if (res) {
        if (!info->ifindex)  /* false, ifindex was set */
            return -ENODEV;
        return res;          /* returns -ENOENT */
    }

Daniel Borkmann also noted this in his review of v1:
https://lore.kernel.org/bpf/[email protected]/

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/24166717291