Since Linux 5.7 (see linux/c427bfec18f21) non-root users are allowed to
bind a socket using SO_BINDTODEVICE as long as the socket is not already
bound.

When using BGP with VRFs, BIRD correctly binds the listening socket to
the VRF but also re-binds the accept()'d socket to the same VRF.
This is not needed as the interface bind is inherited in this case, and
indeed this redundant bind causes an -EPERM if BIRD is running as non-root
making BIRD close the connection and reject the peer.

We change the behaviour of the generic sk_setup to first query the socket
and see if the socket is already correctly bound, and call
setsockopt(SO_BINDTODEVICE) iff it is truly needed. In addition,
since the getsockopt(SO_BINDTODEVICE) was implemented in Linux 3.8 or
otherwise might be blocked in existing installations, we quietly fall
back to the previous behavior if the getsockopt call fails.

Test case:
 Run BIRD as a non-root user (and no extra capabilities) using passive
 BGP inside a VRF. Before the patch observe the error:
 "<ERR> SOCK: Incoming connection: SO_BINDTODEVICE: Operation not permitted"

  protocol bgp AS1234_1 {
    [..]
    vrf "VrfTest";
    passive on;
  }

 After the patch this works as expected.

Patch is attached to this message but if it falls off it can also be found
at:
https://github.com/sonix-network/bird/blob/33a0ac4b5af38d3bf75c78ca62472fff1663945e/patches/0001-IO-Avoid-calling-SO_BINDTODEVICE-if-not-needed.patch

There is also a simple utility to test the behavior of rebinding sockets at:
https://github.com/sonix-network/bird/blob/33a0ac4b5af38d3bf75c78ca62472fff1663945e/patches/0001-IO-Avoid-calling-SO_BINDTODEVICE-if-not-needed.repro.c

Thanks for your consideration,
From b84129c8b9cc1bc42119c1fc398183a3925125bd Mon Sep 17 00:00:00 2001
From: Christian Svensson <[email protected]>
Date: Sat, 27 Jul 2024 11:13:06 +0200
Subject: [PATCH] IO: Avoid calling SO_BINDTODEVICE if not needed

Since Linux 5.7 (see linux/c427bfec18f21) non-root users are allowed to
bind a socket using SO_BINDTODEVICE as long as the socket is not already bound.

When using BGP with VRFs, BIRD correctly binds the listening socket to
the VRF but also re-binds the accept()'d socket to the same VRF.
This is not needed as the interface bind is inherited in this case, and
indeed this redundant bind causes an -EPERM if BIRD is running as non-root
making BIRD close the connection and reject the peer.

We change the behaviour of the generic sk_setup to first query the socket
and see if the socket is already correctly bound, and call
setsockopt(SO_BINDTODEVICE) iff it is truly needed. In addition,
since the getsockopt(SO_BINDTODEVICE) was implemented in Linux 3.8 or
otherwise might be blocked in existing installations, we quietly fall
back to the previous behavior if the getsockopt call fails.

Test case:
 Run BIRD as a non-root user (and no extra capabilities) using passive
 BGP inside a VRF. Before the patch observe the error:
 "<ERR> SOCK: Incoming connection: SO_BINDTODEVICE: Operation not permitted"

  protocol bgp AS1234_1 {
    [..]
    vrf "VrfTest";
    passive on;
  }

 After the patch this works as expected.

Signed-off-by: Christian Svensson <[email protected]>
---
 sysdep/unix/io.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index ba2e1661..c329512f 100644
--- a/sysdep/unix/io.c
+++ b/sysdep/unix/io.c
@@ -977,9 +977,17 @@ sk_setup(sock *s)
        This is Linux-specific, but so is SO_BINDTODEVICE. */
 #ifdef SO_BINDTODEVICE
     struct ifreq ifr = {};
-    strcpy(ifr.ifr_name, s->vrf->name);
-    if (setsockopt(s->fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0)
-      ERR("SO_BINDTODEVICE");
+    /* Re-binding a socket to another (or the same interface) is a high-privileged
+       action. Avoid doing that if the socket is already correctly bound. */
+    socklen_t len = sizeof(ifr.ifr_name);
+    /* getsockopt(SO_BINDTODEVICE) was implemented in Linux 3.8, if the call
+       fails ignore the error and just call setsockopt(SO_BINDTODEVICE). */
+    if (getsockopt(s->fd, SOL_SOCKET, SO_BINDTODEVICE, ifr.ifr_name, &len) < 0 ||
+        strcmp(ifr.ifr_name, s->vrf->name) != 0) {
+      strcpy(ifr.ifr_name, s->vrf->name);
+      if (setsockopt(s->fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0)
+        ERR("SO_BINDTODEVICE");
+    }
 #endif
   }
 
-- 
2.45.2

Reply via email to