| Message ID | 20260612-landlock-v1-1-77891f63ed7f@est.tech |
|---|---|
| State | New |
| Headers | show |
| Series | [[RFC] 1/2] utils: Add landlock_restrict_network function | expand |
On Fri, 12 Jun 2026 at 14:01, David Nyström via lists.openembedded.org <david.nystrom=est.tech@lists.openembedded.org> wrote: > +def landlock_restrict_network(): > + """Block TCP bind/connect using Landlock LSM (ABI v4+, kernel 6.7+). > + Gracefully skipped on older kernels. Stacks with disable_network().""" > + > + NR_CREATE = 444 # landlock_create_ruleset > + NR_SELF = 446 # landlock_restrict_self > + NET_TCP = 0x3 # BIND_TCP | CONNECT_TCP > + > + libc = ctypes.CDLL('libc.so.6') > + > + abi = libc.syscall(NR_CREATE, 0, 0, 1) > + if abi < 4: > + return False > + > + attr = struct.pack("QQ", 0, NET_TCP) > + buf = ctypes.create_string_buffer(attr) > + fd = libc.syscall(NR_CREATE, buf, len(attr), 0) > + if fd < 0: > + return False > + > + libc.prctl(38, 1, 0, 0, 0) # PR_SET_NO_NEW_PRIVS > + r = libc.syscall(NR_SELF, fd, 0) > + os.close(fd) > + return r == 0 Far too many magic numbers. I would really want to do this with an API. This also needs some kind of test, e.g. that the function indeed has the desired effect. Alex
On Sat, 2026-06-13 at 13:52 +0200, Alexander Kanavin via lists.openembedded.org wrote: > On Fri, 12 Jun 2026 at 14:01, David Nyström via > lists.openembedded.org > <david.nystrom=est.tech@lists.openembedded.org> wrote: > > +def landlock_restrict_network(): > > + """Block TCP bind/connect using Landlock LSM (ABI v4+, kernel > > 6.7+). > > + Gracefully skipped on older kernels. Stacks with > > disable_network().""" > > + > > + NR_CREATE = 444 # landlock_create_ruleset > > + NR_SELF = 446 # landlock_restrict_self > > + NET_TCP = 0x3 # BIND_TCP | CONNECT_TCP > > + > > + libc = ctypes.CDLL('libc.so.6') > > + > > + abi = libc.syscall(NR_CREATE, 0, 0, 1) > > + if abi < 4: > > + return False > > + > > + attr = struct.pack("QQ", 0, NET_TCP) > > + buf = ctypes.create_string_buffer(attr) > > + fd = libc.syscall(NR_CREATE, buf, len(attr), 0) > > + if fd < 0: > > + return False > > + > > + libc.prctl(38, 1, 0, 0, 0) # PR_SET_NO_NEW_PRIVS > > + r = libc.syscall(NR_SELF, fd, 0) > > + os.close(fd) > > + return r == 0 > > Far too many magic numbers. I would really want to do this with an > API. > > This also needs some kind of test, e.g. that the function indeed has > the desired effect. Unfortunately, to use tech like this, we do end up needing to do something like that and utils.py already has quite a bit of it. The plus side is that the kernel is really good about maintaining these APIs so the numbers are unlikely to change. I wouldn't take something like this unless there was a really good case for using it. Network isolation in more builds probably is a strong enough use case... Cheers, Richard
On Fri, 2026-06-12 at 13:38 +0200, David Nyström wrote: > Add landlock_restrict_network() which blocks TCP bind/connect using > Landlock LSM (ABI v4+, kernel 6.7+). Designed to stack with the > existing disable_network() namespace isolation, covering the case > where disable_network() is skipped for non-local UIDs. > > Gracefully returns False on older kernels (ABI < 4). > > Signed-off-by: David Nyström <david.nystrom@est.tech> Hi David, I think adding this is a good idea, but the code needs a few changes to ensure it is maintainable. > --- > lib/bb/utils.py | 26 ++++++++++++++++++++++++++ > 1 file changed, 26 insertions(+) > > diff --git a/lib/bb/utils.py b/lib/bb/utils.py > index 181082c95..1347c29d0 100644 > --- a/lib/bb/utils.py > +++ b/lib/bb/utils.py > @@ -2054,6 +2054,32 @@ def disable_network(uid=None, gid=None): > with open("/proc/self/gid_map", "w") as f: > f.write("%s %s 1" % (gid, gid)) > > +def landlock_restrict_network(): > + """Block TCP bind/connect using Landlock LSM (ABI v4+, kernel 6.7+). > + Gracefully skipped on older kernels. Stacks with disable_network().""" > + > + NR_CREATE = 444 # landlock_create_ruleset > + NR_SELF = 446 # landlock_restrict_self > + NET_TCP = 0x3 # BIND_TCP | CONNECT_TCP We should base these on the names used in the Linux kernel so it's easy to search for things and compare with example C code in the docs. So, NR_landlock_create_ruleset = 444 NR_landlock_add_rule = 445 LANDLOCK_ACCESS_NET_BIND_TCP = 0x1 LANDLOCK_ACCESS_NET_CONNECT_TCP = 0x2 LANDLOCK_CREATE_RULESET_VERSION = 1 > + > + libc = ctypes.CDLL('libc.so.6') > + > + abi = libc.syscall(NR_CREATE, 0, 0, 1) > + if abi < 4: > + return False # Check that landlock is enabled and supports network access # restriction (added in ABI version 4) abi = libc.syscall(NR_landlock_create_ruleset, 0, 0, LANDLOCK_CREATE_RULESET_VERSION) if abi < 4: logger.debug("System doesn't support disabling network via landlock") return False That's a little more verbose, but much clearer. > + > + attr = struct.pack("QQ", 0, NET_TCP) > + buf = ctypes.create_string_buffer(attr) > + fd = libc.syscall(NR_CREATE, buf, len(attr), 0) > + if fd < 0: > + return False We probably also want a logger.debug() call to log the failure here as well. > + > + libc.prctl(38, 1, 0, 0, 0) # PR_SET_NO_NEW_PRIVS The commit message only describes use of landlock, not no_new_privs. We need constants for this call as well. > + r = libc.syscall(NR_SELF, fd, 0) > + os.close(fd) > + return r == 0 > + > + > def export_proxies(d): > from bb.fetch2 import get_fetcher_environment > """ export common proxies variables from datastore to environment """ Thanks,
On Mon, 15 Jun 2026, Paul Barker wrote: > On Fri, 2026-06-12 at 13:38 +0200, David Nyström wrote: >> Add landlock_restrict_network() which blocks TCP bind/connect using >> Landlock LSM (ABI v4+, kernel 6.7+). Designed to stack with the >> existing disable_network() namespace isolation, covering the case >> where disable_network() is skipped for non-local UIDs. >> >> Gracefully returns False on older kernels (ABI < 4). >> >> Signed-off-by: David Nyström <david.nystrom@est.tech> > > Hi David, > > I think adding this is a good idea, but the code needs a few changes to > ensure it is maintainable. Thanks for the review, and good comments. My comments below. >> --- >> lib/bb/utils.py | 26 ++++++++++++++++++++++++++ >> 1 file changed, 26 insertions(+) >> >> diff --git a/lib/bb/utils.py b/lib/bb/utils.py >> index 181082c95..1347c29d0 100644 >> --- a/lib/bb/utils.py >> +++ b/lib/bb/utils.py >> @@ -2054,6 +2054,32 @@ def disable_network(uid=None, gid=None): >> with open("/proc/self/gid_map", "w") as f: >> f.write("%s %s 1" % (gid, gid)) >> >> +def landlock_restrict_network(): >> + """Block TCP bind/connect using Landlock LSM (ABI v4+, kernel 6.7+). >> + Gracefully skipped on older kernels. Stacks with disable_network().""" >> + >> + NR_CREATE = 444 # landlock_create_ruleset >> + NR_SELF = 446 # landlock_restrict_self >> + NET_TCP = 0x3 # BIND_TCP | CONNECT_TCP > > We should base these on the names used in the Linux kernel so it's easy > to search for things and compare with example C code in the docs. So, > > NR_landlock_create_ruleset = 444 > NR_landlock_add_rule = 445 > > LANDLOCK_ACCESS_NET_BIND_TCP = 0x1 > LANDLOCK_ACCESS_NET_CONNECT_TCP = 0x2 > > LANDLOCK_CREATE_RULESET_VERSION = 1 +1. >> + >> + libc = ctypes.CDLL('libc.so.6') >> + >> + abi = libc.syscall(NR_CREATE, 0, 0, 1) >> + if abi < 4: >> + return False > > # Check that landlock is enabled and supports network access > # restriction (added in ABI version 4) > abi = libc.syscall(NR_landlock_create_ruleset, > 0, 0, > LANDLOCK_CREATE_RULESET_VERSION) > if abi < 4: > logger.debug("System doesn't support disabling network via landlock") > return False > > That's a litte more verbose, but much clearer. Good point. >> + >> + attr = struct.pack("QQ", 0, NET_TCP) >> + buf = ctypes.create_string_buffer(attr) >> + fd = libc.syscall(NR_CREATE, buf, len(attr), 0) >> + if fd < 0: >> + return False > > We probably also want a logger.debug() call to log the failure here as > well. > >> + >> + libc.prctl(38, 1, 0, 0, 0) # PR_SET_NO_NEW_PRIVS > > The commit message only describes use of landlock, not no_new_privs. We > need constants for this call as well. Also a very good point, setuid/gid and setcap:ed binaries will silently run without privs, which should be documented also in the commit message. This is a landlock requirement for unpriv use, and the main source of potential sideeffects I would expect from this approach. Since there is no way do disable it, without setting the "network" flag on a failing task, I'd really want to gain more confidence about the potential sideeffects of no_new_privs on various edge cases before I send a non-RFC patch. Testing is ongoing with your comments addressed, but I will not be able to finish before my vacation starts(tomorrow). If considered urgent, feel free to use idea only. >> + r = libc.syscall(NR_SELF, fd, 0) >> + os.close(fd) >> + return r == 0 >> + >> + >> def export_proxies(d): >> from bb.fetch2 import get_fetcher_environment >> """ export common proxies variables from datastore to environment """ > > Thanks, > > -- > Paul Barker > > >
diff --git a/lib/bb/utils.py b/lib/bb/utils.py index 181082c95..1347c29d0 100644 --- a/lib/bb/utils.py +++ b/lib/bb/utils.py @@ -2054,6 +2054,32 @@ def disable_network(uid=None, gid=None): with open("/proc/self/gid_map", "w") as f: f.write("%s %s 1" % (gid, gid)) +def landlock_restrict_network(): + """Block TCP bind/connect using Landlock LSM (ABI v4+, kernel 6.7+). + Gracefully skipped on older kernels. Stacks with disable_network().""" + + NR_CREATE = 444 # landlock_create_ruleset + NR_SELF = 446 # landlock_restrict_self + NET_TCP = 0x3 # BIND_TCP | CONNECT_TCP + + libc = ctypes.CDLL('libc.so.6') + + abi = libc.syscall(NR_CREATE, 0, 0, 1) + if abi < 4: + return False + + attr = struct.pack("QQ", 0, NET_TCP) + buf = ctypes.create_string_buffer(attr) + fd = libc.syscall(NR_CREATE, buf, len(attr), 0) + if fd < 0: + return False + + libc.prctl(38, 1, 0, 0, 0) # PR_SET_NO_NEW_PRIVS + r = libc.syscall(NR_SELF, fd, 0) + os.close(fd) + return r == 0 + + def export_proxies(d): from bb.fetch2 import get_fetcher_environment """ export common proxies variables from datastore to environment """
Add landlock_restrict_network() which blocks TCP bind/connect using Landlock LSM (ABI v4+, kernel 6.7+). Designed to stack with the existing disable_network() namespace isolation, covering the case where disable_network() is skipped for non-local UIDs. Gracefully returns False on older kernels (ABI < 4). Signed-off-by: David Nyström <david.nystrom@est.tech> --- lib/bb/utils.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+)