Message ID | 20230706145543.1284007-1-mic@digikod.net (mailing list archive) |
---|---|
State | Handled Elsewhere |
Headers | show |
Series | [v11.1] selftests/landlock: Add 11 new test suites dedicated to network | expand |
On 06/07/2023 16:55, Mickaël Salaün wrote: > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > This patch is a revamp of the v11 tests [1] with new tests (see the > "Changes since v11" description). I (Mickaël) only added the following > todo list and the "Changes since v11" sections in this commit message. > I think this patch is good but it would appreciate reviews. > You can find the diff of my changes here but it is not really readable: > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > TODO: > - Rename all "net_service" to "net_port". > - Fix the two kernel bugs found with the new tests. > - Update this commit message with a small description of all tests. > > These test suites try to check edge cases for TCP sockets > bind() and connect() actions. > > inet: > * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets. > * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets. > * bind_afunspec: Tests with non-landlocked/landlocked restrictions > for bind action with AF_UNSPEC socket family. > * connect_afunspec: Tests with non-landlocked/landlocked restrictions > for connect action with AF_UNSPEC socket family. > * ruleset_overlap: Tests with overlapping rules for one port. > * ruleset_expanding: Tests with expanding rulesets in which rules are > gradually added one by one, restricting sockets' connections. > * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets > and with port values more than U16_MAX. > > port: > * inval: Tests with invalid user space supplied data: > - out of range ruleset attribute; > - unhandled allowed access; > - zero port value; > - zero access value; > - legitimate access values; > * bind_connect_inval_addrlen: Tests with invalid address length. > * bind_connect_unix_*_socket: Tests to make sure unix sockets' actions > are not restricted by Landlock rules applied to TCP ones. > > layout1: > * with_net: Tests with network bind() socket action within > filesystem directory access test. > > Test coverage for security/landlock is 94.8% of 934 lines according > to gcc/gcov-11. > > Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > Co-developed-by: Mickaël Salaün <mic@digikod.net> > Signed-off-by: Mickaël Salaün <mic@digikod.net> > --- > > Changes since v11 (from Mickaël Salaün): > - Add ipv4.from_unix_to_tcp test suite to check that socket family is > the same between a socket and a sockaddr by trying to connect/bind on > a unix socket (stream or dgram) using an inet family. Landlock should > not change the error code. This found a bug (which needs to be fixed) > with the TCP restriction. > - Revamp the inet.{bind,connect} tests into protocol.{bind,connect}: > - Merge bind_connect_unix_dgram_socket, bind_connect_unix_dgram_socket > and bind_connect_inval_addrlen into it: add a full test matrix of > IPv4/TCP, IPv6/TCP, IPv4/UDP, IPv6/UDP, unix/stream, unix/dgram, all > of them with or without sandboxing. This improve coverage and it > enables to check that a TCP restriction work as expected but doesn't > restrict other stream or datagram protocols. This also enables to > check consistency of the network stack with or without Landlock. > We now have 76 test suites for the network. > - Add full send/recv checks. > - Make a generic framework that will be ready for future > protocol supports. > - Replace most ASSERT with EXPECT according to the criticity of an > action: if we can get more meaningful information with following > checks. For instance, failure to create a kernel object (e.g. > socket(), accept() or fork() call) is critical if it is used by > following checks. For Landlock ruleset building, the following checks > don't make sense if the sandbox is not complete. However, it doesn't > make sense to continue a FIXTURE_SETUP() if any check failed. > - Add a new unspec fixture to replace inet.bind_afunspec with > unspec.bind and inet.connect_afunspec with unspec.connect, factoring > and simplifying code. > - Replace inet.bind_afunspec with protocol.bind_unspec, and > inet.connect_afunspec with protocol.connect_unspec. Extend these > tests with the matrix of all "protocol" variants. Don't test connect > with the same socket which is already binded/listening (I guess this > was an copy-paste error). The protocol.bind_unspec tests found a bug > (which needs to be fixed). > - Add and use set_service() and setup_loopback() helpers to configure > network services. Add and use and test_bind_and_connect() to factor > out a lot of checks. > - Add new types (protocol_variant, service_fixture) and update related > helpers to get more generic test code. > - Replace static (port) arrays with service_fixture variables. > - Add new helpers: {bind,connect}_variant_addrlen() and get_addrlen() to > cover all protocols with previous bind_connect_inval_addrlen tests. > Make them return -errno in case of error. > - Switch from a unix socket path address to an abstract one. This > enables to avoid file cleanup in test teardowns. > - Close all rulesets after enforcement. > - Remove the duplicate "empty access" test. > - Replace inet.ruleset_overlay with tcp_layers.ruleset_overlap and > simplify test: > - Always run sandbox tests because test were always run sandboxed and > it doesn't give more guarantees to do it not sandboxed. > - Rewrite test with variant->num_layers to make it simpler and > configurable. > - Add another test layer to tcp_layers used for ruleset_overlap and > test without sandbox. > - Leverage test_bind_and_connect() and avoid using SO_REUSEADDR > because the socket was not listened to, and don't use the same > socket/FD for server and client. > - Replace inet.ruleset_expanding with tcp_layers.ruleset_expand. > - Drop capabilities in all FIXTURE_SETUP(). > - Change test ports to cover more ranges. > - Add "mini" tests: > - Replace the invalid ruleset attribute test from port.inval with > mini.unknow_access_rights. > - Simplify port.inval and move some code to other mini.* tests. > - Add new mini.network_access_rights test. > - Rewrite inet.inval_port_format into mini.tcp_port_overflow: > - Remove useless is_sandbox checks. > - Extend tests with bind/connect checks. > - Interleave valid requests with invalid ones. > - Add two_srv.port_endianness test, extracted and extended from > inet.inval_port_format . > - Add Microsoft copyright. > - Rename some variables to make them easier to read. > - Constify variables. > - Add minimal logs to help debug test failures. > --- > tools/testing/selftests/landlock/config | 4 + > tools/testing/selftests/landlock/fs_test.c | 64 + > tools/testing/selftests/landlock/net_test.c | 1439 +++++++++++++++++++ > 3 files changed, 1507 insertions(+) > create mode 100644 tools/testing/selftests/landlock/net_test.c [...] > + > +FIXTURE(inet) > +{ > + struct service_fixture srv0, srv1; > +}; > + > +FIXTURE_VARIANT(inet) > +{ > + const bool is_sandboxed; Well, this "is_sandboxed" variable can now be removed, and the variants updated accordingly. > + const struct protocol_variant prot; > +}; > + > +/* clang-format off */ Rename to ipv4 and same for the ipv6 variants. > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { > + /* clang-format on */ > + .is_sandboxed = false, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { > + /* clang-format on */ > + .is_sandboxed = true, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { > + /* clang-format on */ > + .is_sandboxed = false, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { > + /* clang-format on */ > + .is_sandboxed = true, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_STREAM, > + }, > +}; > + >
7/6/2023 7:09 PM, Mickaël Salaün пишет: > > On 06/07/2023 16:55, Mickaël Salaün wrote: >> From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> >> This patch is a revamp of the v11 tests [1] with new tests (see the >> "Changes since v11" description). I (Mickaël) only added the following >> todo list and the "Changes since v11" sections in this commit message. >> I think this patch is good but it would appreciate reviews. >> You can find the diff of my changes here but it is not really readable: >> https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> TODO: >> - Rename all "net_service" to "net_port". >> - Fix the two kernel bugs found with the new tests. >> - Update this commit message with a small description of all tests. >> >> These test suites try to check edge cases for TCP sockets >> bind() and connect() actions. >> >> inet: >> * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets. >> * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets. >> * bind_afunspec: Tests with non-landlocked/landlocked restrictions >> for bind action with AF_UNSPEC socket family. >> * connect_afunspec: Tests with non-landlocked/landlocked restrictions >> for connect action with AF_UNSPEC socket family. >> * ruleset_overlap: Tests with overlapping rules for one port. >> * ruleset_expanding: Tests with expanding rulesets in which rules are >> gradually added one by one, restricting sockets' connections. >> * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets >> and with port values more than U16_MAX. >> >> port: >> * inval: Tests with invalid user space supplied data: >> - out of range ruleset attribute; >> - unhandled allowed access; >> - zero port value; >> - zero access value; >> - legitimate access values; >> * bind_connect_inval_addrlen: Tests with invalid address length. >> * bind_connect_unix_*_socket: Tests to make sure unix sockets' actions >> are not restricted by Landlock rules applied to TCP ones. >> >> layout1: >> * with_net: Tests with network bind() socket action within >> filesystem directory access test. >> >> Test coverage for security/landlock is 94.8% of 934 lines according >> to gcc/gcov-11. >> >> Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> Co-developed-by: Mickaël Salaün <mic@digikod.net> >> Signed-off-by: Mickaël Salaün <mic@digikod.net> >> --- >> >> Changes since v11 (from Mickaël Salaün): >> - Add ipv4.from_unix_to_tcp test suite to check that socket family is >> the same between a socket and a sockaddr by trying to connect/bind on >> a unix socket (stream or dgram) using an inet family. Landlock should >> not change the error code. This found a bug (which needs to be fixed) >> with the TCP restriction. >> - Revamp the inet.{bind,connect} tests into protocol.{bind,connect}: >> - Merge bind_connect_unix_dgram_socket, bind_connect_unix_dgram_socket >> and bind_connect_inval_addrlen into it: add a full test matrix of >> IPv4/TCP, IPv6/TCP, IPv4/UDP, IPv6/UDP, unix/stream, unix/dgram, all >> of them with or without sandboxing. This improve coverage and it >> enables to check that a TCP restriction work as expected but doesn't >> restrict other stream or datagram protocols. This also enables to >> check consistency of the network stack with or without Landlock. >> We now have 76 test suites for the network. >> - Add full send/recv checks. >> - Make a generic framework that will be ready for future >> protocol supports. >> - Replace most ASSERT with EXPECT according to the criticity of an >> action: if we can get more meaningful information with following >> checks. For instance, failure to create a kernel object (e.g. >> socket(), accept() or fork() call) is critical if it is used by >> following checks. For Landlock ruleset building, the following checks >> don't make sense if the sandbox is not complete. However, it doesn't >> make sense to continue a FIXTURE_SETUP() if any check failed. >> - Add a new unspec fixture to replace inet.bind_afunspec with >> unspec.bind and inet.connect_afunspec with unspec.connect, factoring >> and simplifying code. >> - Replace inet.bind_afunspec with protocol.bind_unspec, and >> inet.connect_afunspec with protocol.connect_unspec. Extend these >> tests with the matrix of all "protocol" variants. Don't test connect >> with the same socket which is already binded/listening (I guess this >> was an copy-paste error). The protocol.bind_unspec tests found a bug >> (which needs to be fixed). >> - Add and use set_service() and setup_loopback() helpers to configure >> network services. Add and use and test_bind_and_connect() to factor >> out a lot of checks. >> - Add new types (protocol_variant, service_fixture) and update related >> helpers to get more generic test code. >> - Replace static (port) arrays with service_fixture variables. >> - Add new helpers: {bind,connect}_variant_addrlen() and get_addrlen() to >> cover all protocols with previous bind_connect_inval_addrlen tests. >> Make them return -errno in case of error. >> - Switch from a unix socket path address to an abstract one. This >> enables to avoid file cleanup in test teardowns. >> - Close all rulesets after enforcement. >> - Remove the duplicate "empty access" test. >> - Replace inet.ruleset_overlay with tcp_layers.ruleset_overlap and >> simplify test: >> - Always run sandbox tests because test were always run sandboxed and >> it doesn't give more guarantees to do it not sandboxed. >> - Rewrite test with variant->num_layers to make it simpler and >> configurable. >> - Add another test layer to tcp_layers used for ruleset_overlap and >> test without sandbox. >> - Leverage test_bind_and_connect() and avoid using SO_REUSEADDR >> because the socket was not listened to, and don't use the same >> socket/FD for server and client. >> - Replace inet.ruleset_expanding with tcp_layers.ruleset_expand. >> - Drop capabilities in all FIXTURE_SETUP(). >> - Change test ports to cover more ranges. >> - Add "mini" tests: >> - Replace the invalid ruleset attribute test from port.inval with >> mini.unknow_access_rights. >> - Simplify port.inval and move some code to other mini.* tests. >> - Add new mini.network_access_rights test. >> - Rewrite inet.inval_port_format into mini.tcp_port_overflow: >> - Remove useless is_sandbox checks. >> - Extend tests with bind/connect checks. >> - Interleave valid requests with invalid ones. >> - Add two_srv.port_endianness test, extracted and extended from >> inet.inval_port_format . >> - Add Microsoft copyright. >> - Rename some variables to make them easier to read. >> - Constify variables. >> - Add minimal logs to help debug test failures. >> --- >> tools/testing/selftests/landlock/config | 4 + >> tools/testing/selftests/landlock/fs_test.c | 64 + >> tools/testing/selftests/landlock/net_test.c | 1439 +++++++++++++++++++ >> 3 files changed, 1507 insertions(+) >> create mode 100644 tools/testing/selftests/landlock/net_test.c > > > [...] > >> + >> +FIXTURE(inet) >> +{ >> + struct service_fixture srv0, srv1; >> +}; >> + >> +FIXTURE_VARIANT(inet) >> +{ >> + const bool is_sandboxed; > > Well, this "is_sandboxed" variable can now be removed, and the variants > updated accordingly. Ok. Thanks. Im reviewing the patch now. > >> + const struct protocol_variant prot; >> +}; >> + >> +/* clang-format off */ > > Rename to ipv4 and same for the ipv6 variants. Got it. > >> +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { >> + /* clang-format on */ >> + .is_sandboxed = false, >> + .prot = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { >> + /* clang-format on */ >> + .is_sandboxed = true, >> + .prot = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { >> + /* clang-format on */ >> + .is_sandboxed = false, >> + .prot = { >> + .domain = AF_INET6, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { >> + /* clang-format on */ >> + .is_sandboxed = true, >> + .prot = { >> + .domain = AF_INET6, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> > .
On 06/07/2023 16:55, Mickaël Salaün wrote: > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > This patch is a revamp of the v11 tests [1] with new tests (see the > "Changes since v11" description). I (Mickaël) only added the following > todo list and the "Changes since v11" sections in this commit message. > I think this patch is good but it would appreciate reviews. > You can find the diff of my changes here but it is not really readable: > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > TODO: > - Rename all "net_service" to "net_port". > - Fix the two kernel bugs found with the new tests. > - Update this commit message with a small description of all tests. > [...] > +static int bind_variant_addrlen(const int sock_fd, > + const struct service_fixture *const srv, > + const socklen_t addrlen) > +{ > + int ret; > + > + switch (srv->protocol.domain) { > + case AF_UNSPEC: > + case AF_INET: > + ret = bind(sock_fd, &srv->ipv4_addr, addrlen); > + break; > + > + case AF_INET6: > + ret = bind(sock_fd, &srv->ipv6_addr, addrlen); > + break; > + > + case AF_UNIX: > + ret = bind(sock_fd, &srv->unix_addr, addrlen); > + break; > + > + default: > + errno = -EAFNOSUPPORT; This should be `errno = EAFNOSUPPORT` > + return -errno; > + } > + > + if (ret < 0) > + return -errno; > + return ret;
On 06/07/2023 16:55, Mickaël Salaün wrote: > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > This patch is a revamp of the v11 tests [1] with new tests (see the > "Changes since v11" description). I (Mickaël) only added the following > todo list and the "Changes since v11" sections in this commit message. > I think this patch is good but it would appreciate reviews. > You can find the diff of my changes here but it is not really readable: > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > TODO: > - Rename all "net_service" to "net_port". > - Fix the two kernel bugs found with the new tests. > - Update this commit message with a small description of all tests. [...] > +FIXTURE_SETUP(ipv4) > +{ > + const struct protocol_variant prot = { > + .domain = AF_INET, > + .type = variant->type, > + }; > + > + disable_caps(_metadata); > + > + set_service(&self->srv0, prot, 0); > + set_service(&self->srv1, prot, 1); > + > + setup_loopback(_metadata); > +}; > + > +FIXTURE_TEARDOWN(ipv4) > +{ > +} > + > +// Kernel FIXME: tcp_sandbox_with_tcp and tcp_sandbox_with_udp > +TEST_F(ipv4, from_unix_to_inet) > +{ > + int unix_stream_fd, unix_dgram_fd; > + > + if (variant->sandbox == TCP_SANDBOX) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr tcp_bind_connect_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = self->srv0.port, > + }; > + int ruleset_fd; > + > + /* Denies connect and bind to check errno value. */ > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Allows connect and bind for srv0. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_connect_p0, 0)); > + > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + unix_stream_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); > + ASSERT_LE(0, unix_stream_fd); > + > + unix_dgram_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); > + ASSERT_LE(0, unix_dgram_fd); > + > + /* Checks unix stream bind and connect for srv0. */ > + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv0)); > + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv0)); > + > + /* Checks unix stream bind and connect for srv1. */ > + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv1)) > + { > + TH_LOG("Wrong bind error: %s", strerror(errno)); > + } > + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv1)); > + > + /* Checks unix datagram bind and connect for srv0. */ > + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv0)); > + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv0)); > + > + /* Checks unix datagram bind and connect for srv0. */ > + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv1)); > + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv1)); > +} We should also add a test to make sure errno is the same with and without sandboxing when using port 0 for connect and consistent with bind (using an available port). The test fixture and variants should be quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, which will result in 8 "ip" variants: TEST_F(ip, port_zero) { if (variant->sandbox == TCP_SANDBOX) { /* Denies any connect and bind. */ } /* Checks errno for port 0. */ } [...] > +FIXTURE(inet) > +{ > + struct service_fixture srv0, srv1; > +}; The "inet" variants are useless and should be removed. The "inet" fixture can then be renamed to "ipv4_tcp". > + > +FIXTURE_VARIANT(inet) > +{ > + const bool is_sandboxed; > + const struct protocol_variant prot; > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { > + /* clang-format on */ > + .is_sandboxed = false, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { > + /* clang-format on */ > + .is_sandboxed = true, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { > + /* clang-format on */ > + .is_sandboxed = false, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { > + /* clang-format on */ > + .is_sandboxed = true, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_STREAM, > + }, > +}; > + > +FIXTURE_SETUP(inet) > +{ > + const struct protocol_variant ipv4_tcp = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }; > + > + disable_caps(_metadata); > + > + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); > + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); > + > + setup_loopback(_metadata); > +}; > + > +FIXTURE_TEARDOWN(inet) > +{ > +} > + > +TEST_F(inet, port_endianness) > +{ > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr bind_host_endian_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + /* Host port format. */ > + .port = self->srv0.port, > + }; > + const struct landlock_net_service_attr connect_big_endian_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, > + /* Big endian port format. */ > + .port = htons(self->srv0.port), > + }; > + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + /* Host port format. */ > + .port = self->srv1.port, > + }; > + const unsigned int one = 1; > + const char little_endian = *(const char *)&one; > + int ruleset_fd; > + > + ruleset_fd = > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &bind_host_endian_p0, 0)); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &connect_big_endian_p0, 0)); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &bind_connect_host_endian_p1, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + > + /* No restriction for big endinan CPU. */ > + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); > + > + /* No restriction for any CPU. */ > + test_bind_and_connect(_metadata, &self->srv1, false, false); > +} > + > +TEST_HARNESS_MAIN
7/10/2023 7:06 PM, Mickaël Salaün пишет: > > On 06/07/2023 16:55, Mickaël Salaün wrote: >> From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> >> This patch is a revamp of the v11 tests [1] with new tests (see the >> "Changes since v11" description). I (Mickaël) only added the following >> todo list and the "Changes since v11" sections in this commit message. >> I think this patch is good but it would appreciate reviews. >> You can find the diff of my changes here but it is not really readable: >> https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> TODO: >> - Rename all "net_service" to "net_port". >> - Fix the two kernel bugs found with the new tests. >> - Update this commit message with a small description of all tests. >> > > > [...] > >> +static int bind_variant_addrlen(const int sock_fd, >> + const struct service_fixture *const srv, >> + const socklen_t addrlen) >> +{ >> + int ret; >> + >> + switch (srv->protocol.domain) { >> + case AF_UNSPEC: >> + case AF_INET: >> + ret = bind(sock_fd, &srv->ipv4_addr, addrlen); >> + break; >> + >> + case AF_INET6: >> + ret = bind(sock_fd, &srv->ipv6_addr, addrlen); >> + break; >> + >> + case AF_UNIX: >> + ret = bind(sock_fd, &srv->unix_addr, addrlen); >> + break; >> + >> + default: >> + errno = -EAFNOSUPPORT; > > This should be `errno = EAFNOSUPPORT` Ok. Got it. > >> + return -errno; >> + } >> + >> + if (ret < 0) >> + return -errno; >> + return ret; > .
7/12/2023 10:02 AM, Mickaël Salaün пишет: > > On 06/07/2023 16:55, Mickaël Salaün wrote: >> From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> >> This patch is a revamp of the v11 tests [1] with new tests (see the >> "Changes since v11" description). I (Mickaël) only added the following >> todo list and the "Changes since v11" sections in this commit message. >> I think this patch is good but it would appreciate reviews. >> You can find the diff of my changes here but it is not really readable: >> https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> TODO: >> - Rename all "net_service" to "net_port". >> - Fix the two kernel bugs found with the new tests. >> - Update this commit message with a small description of all tests. > > [...] > >> +FIXTURE_SETUP(ipv4) >> +{ >> + const struct protocol_variant prot = { >> + .domain = AF_INET, >> + .type = variant->type, >> + }; >> + >> + disable_caps(_metadata); >> + >> + set_service(&self->srv0, prot, 0); >> + set_service(&self->srv1, prot, 1); >> + >> + setup_loopback(_metadata); >> +}; >> + >> +FIXTURE_TEARDOWN(ipv4) >> +{ >> +} >> + >> +// Kernel FIXME: tcp_sandbox_with_tcp and tcp_sandbox_with_udp >> +TEST_F(ipv4, from_unix_to_inet) >> +{ >> + int unix_stream_fd, unix_dgram_fd; >> + >> + if (variant->sandbox == TCP_SANDBOX) { >> + const struct landlock_ruleset_attr ruleset_attr = { >> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + }; >> + const struct landlock_net_service_attr tcp_bind_connect_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + .port = self->srv0.port, >> + }; >> + int ruleset_fd; >> + >> + /* Denies connect and bind to check errno value. */ >> + ruleset_fd = landlock_create_ruleset(&ruleset_attr, >> + sizeof(ruleset_attr), 0); >> + ASSERT_LE(0, ruleset_fd); >> + >> + /* Allows connect and bind for srv0. */ >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, >> + LANDLOCK_RULE_NET_SERVICE, >> + &tcp_bind_connect_p0, 0)); >> + >> + enforce_ruleset(_metadata, ruleset_fd); >> + EXPECT_EQ(0, close(ruleset_fd)); >> + } >> + >> + unix_stream_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); >> + ASSERT_LE(0, unix_stream_fd); >> + >> + unix_dgram_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); >> + ASSERT_LE(0, unix_dgram_fd); >> + >> + /* Checks unix stream bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv0)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv0)); >> + >> + /* Checks unix stream bind and connect for srv1. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv1)) >> + { >> + TH_LOG("Wrong bind error: %s", strerror(errno)); >> + } >> + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv1)); >> + >> + /* Checks unix datagram bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv0)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv0)); >> + >> + /* Checks unix datagram bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv1)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv1)); >> +} > > We should also add a test to make sure errno is the same with and > without sandboxing when using port 0 for connect and consistent with > bind (using an available port). The test fixture and variants should be > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, > which will result in 8 "ip" variants: > > TEST_F(ip, port_zero) > { > if (variant->sandbox == TCP_SANDBOX) { > /* Denies any connect and bind. */ > } > /* Checks errno for port 0. */ > } Ok. Got it. > > [...] > >> +FIXTURE(inet) >> +{ >> + struct service_fixture srv0, srv1; >> +}; > > The "inet" variants are useless and should be removed. The "inet" > fixture can then be renamed to "ipv4_tcp". > Right. Thanks. > >> + >> +FIXTURE_VARIANT(inet) >> +{ >> + const bool is_sandboxed; >> + const struct protocol_variant prot; >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { >> + /* clang-format on */ >> + .is_sandboxed = false, >> + .prot = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { >> + /* clang-format on */ >> + .is_sandboxed = true, >> + .prot = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { >> + /* clang-format on */ >> + .is_sandboxed = false, >> + .prot = { >> + .domain = AF_INET6, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { >> + /* clang-format on */ >> + .is_sandboxed = true, >> + .prot = { >> + .domain = AF_INET6, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +FIXTURE_SETUP(inet) >> +{ >> + const struct protocol_variant ipv4_tcp = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }; >> + >> + disable_caps(_metadata); >> + >> + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); >> + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); >> + >> + setup_loopback(_metadata); >> +}; >> + >> +FIXTURE_TEARDOWN(inet) >> +{ >> +} >> + >> +TEST_F(inet, port_endianness) >> +{ >> + const struct landlock_ruleset_attr ruleset_attr = { >> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + }; >> + const struct landlock_net_service_attr bind_host_endian_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, >> + /* Host port format. */ >> + .port = self->srv0.port, >> + }; >> + const struct landlock_net_service_attr connect_big_endian_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + /* Big endian port format. */ >> + .port = htons(self->srv0.port), >> + }; >> + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + /* Host port format. */ >> + .port = self->srv1.port, >> + }; >> + const unsigned int one = 1; >> + const char little_endian = *(const char *)&one; >> + int ruleset_fd; >> + >> + ruleset_fd = >> + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); >> + ASSERT_LE(0, ruleset_fd); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &bind_host_endian_p0, 0)); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &connect_big_endian_p0, 0)); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &bind_connect_host_endian_p1, 0)); >> + enforce_ruleset(_metadata, ruleset_fd); >> + >> + /* No restriction for big endinan CPU. */ >> + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); >> + >> + /* No restriction for any CPU. */ >> + test_bind_and_connect(_metadata, &self->srv1, false, false); >> +} >> + >> +TEST_HARNESS_MAIN > .
7/6/2023 5:55 PM, Mickaël Salaün пишет: > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > This patch is a revamp of the v11 tests [1] with new tests (see the > "Changes since v11" description). I (Mickaël) only added the following > todo list and the "Changes since v11" sections in this commit message. > I think this patch is good but it would appreciate reviews. > You can find the diff of my changes here but it is not really readable: > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > TODO: > - Rename all "net_service" to "net_port". > - Fix the two kernel bugs found with the new tests. > - Update this commit message with a small description of all tests. > > These test suites try to check edge cases for TCP sockets > bind() and connect() actions. > > inet: > * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets. > * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets. > * bind_afunspec: Tests with non-landlocked/landlocked restrictions > for bind action with AF_UNSPEC socket family. > * connect_afunspec: Tests with non-landlocked/landlocked restrictions > for connect action with AF_UNSPEC socket family. > * ruleset_overlap: Tests with overlapping rules for one port. > * ruleset_expanding: Tests with expanding rulesets in which rules are > gradually added one by one, restricting sockets' connections. > * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets > and with port values more than U16_MAX. > > port: > * inval: Tests with invalid user space supplied data: > - out of range ruleset attribute; > - unhandled allowed access; > - zero port value; > - zero access value; > - legitimate access values; > * bind_connect_inval_addrlen: Tests with invalid address length. > * bind_connect_unix_*_socket: Tests to make sure unix sockets' actions > are not restricted by Landlock rules applied to TCP ones. > > layout1: > * with_net: Tests with network bind() socket action within > filesystem directory access test. > > Test coverage for security/landlock is 94.8% of 934 lines according > to gcc/gcov-11. > > Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > Co-developed-by: Mickaël Salaün <mic@digikod.net> > Signed-off-by: Mickaël Salaün <mic@digikod.net> > --- > > Changes since v11 (from Mickaël Salaün): > - Add ipv4.from_unix_to_tcp test suite to check that socket family is > the same between a socket and a sockaddr by trying to connect/bind on > a unix socket (stream or dgram) using an inet family. Landlock should > not change the error code. This found a bug (which needs to be fixed) > with the TCP restriction. > - Revamp the inet.{bind,connect} tests into protocol.{bind,connect}: > - Merge bind_connect_unix_dgram_socket, bind_connect_unix_dgram_socket > and bind_connect_inval_addrlen into it: add a full test matrix of > IPv4/TCP, IPv6/TCP, IPv4/UDP, IPv6/UDP, unix/stream, unix/dgram, all > of them with or without sandboxing. This improve coverage and it > enables to check that a TCP restriction work as expected but doesn't > restrict other stream or datagram protocols. This also enables to > check consistency of the network stack with or without Landlock. > We now have 76 test suites for the network. > - Add full send/recv checks. > - Make a generic framework that will be ready for future > protocol supports. > - Replace most ASSERT with EXPECT according to the criticity of an > action: if we can get more meaningful information with following > checks. For instance, failure to create a kernel object (e.g. > socket(), accept() or fork() call) is critical if it is used by > following checks. For Landlock ruleset building, the following checks > don't make sense if the sandbox is not complete. However, it doesn't > make sense to continue a FIXTURE_SETUP() if any check failed. > - Add a new unspec fixture to replace inet.bind_afunspec with > unspec.bind and inet.connect_afunspec with unspec.connect, factoring > and simplifying code. > - Replace inet.bind_afunspec with protocol.bind_unspec, and > inet.connect_afunspec with protocol.connect_unspec. Extend these > tests with the matrix of all "protocol" variants. Don't test connect > with the same socket which is already binded/listening (I guess this > was an copy-paste error). The protocol.bind_unspec tests found a bug > (which needs to be fixed). > - Add and use set_service() and setup_loopback() helpers to configure > network services. Add and use and test_bind_and_connect() to factor > out a lot of checks. > - Add new types (protocol_variant, service_fixture) and update related > helpers to get more generic test code. > - Replace static (port) arrays with service_fixture variables. > - Add new helpers: {bind,connect}_variant_addrlen() and get_addrlen() to > cover all protocols with previous bind_connect_inval_addrlen tests. > Make them return -errno in case of error. > - Switch from a unix socket path address to an abstract one. This > enables to avoid file cleanup in test teardowns. > - Close all rulesets after enforcement. > - Remove the duplicate "empty access" test. > - Replace inet.ruleset_overlay with tcp_layers.ruleset_overlap and > simplify test: > - Always run sandbox tests because test were always run sandboxed and > it doesn't give more guarantees to do it not sandboxed. > - Rewrite test with variant->num_layers to make it simpler and > configurable. > - Add another test layer to tcp_layers used for ruleset_overlap and > test without sandbox. > - Leverage test_bind_and_connect() and avoid using SO_REUSEADDR > because the socket was not listened to, and don't use the same > socket/FD for server and client. > - Replace inet.ruleset_expanding with tcp_layers.ruleset_expand. > - Drop capabilities in all FIXTURE_SETUP(). > - Change test ports to cover more ranges. > - Add "mini" tests: > - Replace the invalid ruleset attribute test from port.inval with > mini.unknow_access_rights. > - Simplify port.inval and move some code to other mini.* tests. > - Add new mini.network_access_rights test. > - Rewrite inet.inval_port_format into mini.tcp_port_overflow: > - Remove useless is_sandbox checks. > - Extend tests with bind/connect checks. > - Interleave valid requests with invalid ones. > - Add two_srv.port_endianness test, extracted and extended from > inet.inval_port_format . > - Add Microsoft copyright. > - Rename some variables to make them easier to read. > - Constify variables. > - Add minimal logs to help debug test failures. > --- > tools/testing/selftests/landlock/config | 4 + > tools/testing/selftests/landlock/fs_test.c | 64 + > tools/testing/selftests/landlock/net_test.c | 1439 +++++++++++++++++++ > 3 files changed, 1507 insertions(+) > create mode 100644 tools/testing/selftests/landlock/net_test.c > > diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config > index 0f0a65287bac..71f7e9a8a64c 100644 > --- a/tools/testing/selftests/landlock/config > +++ b/tools/testing/selftests/landlock/config > @@ -1,3 +1,7 @@ > +CONFIG_INET=y > +CONFIG_IPV6=y > +CONFIG_NET=y > +CONFIG_NET_NS=y > CONFIG_OVERLAY_FS=y > CONFIG_SECURITY_LANDLOCK=y > CONFIG_SECURITY_PATH=y > diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c > index b762b5419a89..9175ee8adf51 100644 > --- a/tools/testing/selftests/landlock/fs_test.c > +++ b/tools/testing/selftests/landlock/fs_test.c > @@ -8,8 +8,10 @@ > */ > > #define _GNU_SOURCE > +#include <arpa/inet.h> > #include <fcntl.h> > #include <linux/landlock.h> > +#include <netinet/in.h> > #include <sched.h> > #include <stdio.h> > #include <string.h> > @@ -17,6 +19,7 @@ > #include <sys/mount.h> > #include <sys/prctl.h> > #include <sys/sendfile.h> > +#include <sys/socket.h> > #include <sys/stat.h> > #include <sys/sysmacros.h> > #include <unistd.h> > @@ -4413,4 +4416,65 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) > } > } > > +static const char loopback_ipv4[] = "127.0.0.1"; > +const unsigned short sock_port = 15000; > + > +TEST_F_FORK(layout1, with_net) > +{ > + const struct rule rules[] = { > + { > + .path = dir_s1d2, > + .access = ACCESS_RO, > + }, > + {}, > + }; > + struct landlock_ruleset_attr ruleset_attr_net = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + struct landlock_net_service_attr tcp_bind = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + > + .port = sock_port, > + }; > + int sockfd, ruleset_fd, ruleset_fd_net; > + struct sockaddr_in addr4; > + > + addr4.sin_family = AF_INET; > + addr4.sin_port = htons(sock_port); > + addr4.sin_addr.s_addr = inet_addr(loopback_ipv4); > + memset(&addr4.sin_zero, '\0', 8); > + > + /* Creates ruleset for network access. */ > + ruleset_fd_net = landlock_create_ruleset(&ruleset_attr_net, > + sizeof(ruleset_attr_net), 0); > + ASSERT_LE(0, ruleset_fd_net); > + > + /* Adds a network rule. */ > + ASSERT_EQ(0, > + landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind, 0)); > + > + enforce_ruleset(_metadata, ruleset_fd_net); > + ASSERT_EQ(0, close(ruleset_fd_net)); > + > + ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); > + > + ASSERT_LE(0, ruleset_fd); > + enforce_ruleset(_metadata, ruleset_fd); > + ASSERT_EQ(0, close(ruleset_fd)); > + > + /* Tests on a directory with the network rule loaded. */ > + ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); > + ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); > + > + sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); > + ASSERT_LE(0, sockfd); > + /* Binds a socket to port 15000. */ > + ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4))); > + > + /* Closes bounded socket. */ > + ASSERT_EQ(0, close(sockfd)); > +} > + > TEST_HARNESS_MAIN > diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c > new file mode 100644 > index 000000000000..12dc127ea7d1 > --- /dev/null > +++ b/tools/testing/selftests/landlock/net_test.c > @@ -0,0 +1,1439 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Landlock tests - Network > + * > + * Copyright © 2022-2023 Huawei Tech. Co., Ltd. > + * Copyright © 2023 Microsoft Corporation > + */ > + > +#define _GNU_SOURCE > +#include <arpa/inet.h> > +#include <errno.h> > +#include <fcntl.h> > +#include <linux/landlock.h> > +#include <linux/in.h> > +#include <sched.h> > +#include <stdint.h> > +#include <string.h> > +#include <sys/prctl.h> > +#include <sys/socket.h> > +#include <sys/un.h> > + > +#include "common.h" > + > +const short sock_port_start = (1 << 10); > + > +static const char loopback_ipv4[] = "127.0.0.1"; > +static const char loopback_ipv6[] = "::1"; > + > +/* Number pending connections queue to be hold. */ > +const short backlog = 10; > + > +enum sandbox_type { > + NO_SANDBOX, > + /* This may be used to test rules that allow *and* deny accesses. */ > + TCP_SANDBOX, > +}; > + > +struct protocol_variant { > + int domain; > + int type; > +}; > + > +struct service_fixture { > + struct protocol_variant protocol; > + /* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */ > + unsigned short port; > + union { > + struct sockaddr_in ipv4_addr; > + struct sockaddr_in6 ipv6_addr; > + struct { > + struct sockaddr_un unix_addr; > + socklen_t unix_addr_len; > + }; > + }; > +}; > + > +static int set_service(struct service_fixture *const srv, > + const struct protocol_variant prot, > + const unsigned short index) > +{ > + memset(srv, 0, sizeof(*srv)); > + > + /* > + * Copies all protocol properties in case of the variant only contains > + * a subset of them. > + */ > + srv->protocol = prot; > + > + /* Checks for port overflow. */ > + if (index > 2) > + return 1; > + srv->port = sock_port_start << (2 * index); > + > + switch (prot.domain) { > + case AF_UNSPEC: > + case AF_INET: > + srv->ipv4_addr.sin_family = prot.domain; > + srv->ipv4_addr.sin_port = htons(srv->port); > + srv->ipv4_addr.sin_addr.s_addr = inet_addr(loopback_ipv4); > + return 0; > + > + case AF_INET6: > + srv->ipv6_addr.sin6_family = prot.domain; > + srv->ipv6_addr.sin6_port = htons(srv->port); > + inet_pton(AF_INET6, loopback_ipv6, &srv->ipv6_addr.sin6_addr); > + return 0; > + > + case AF_UNIX: > + srv->unix_addr.sun_family = prot.domain; > + sprintf(srv->unix_addr.sun_path, > + "_selftests-landlock-net-tid%d-index%d", gettid(), > + index); > + srv->unix_addr_len = SUN_LEN(&srv->unix_addr); > + srv->unix_addr.sun_path[0] = '\0'; > + return 0; > + } > + return 1; > +} > + > +static void setup_loopback(struct __test_metadata *const _metadata) > +{ > + set_cap(_metadata, CAP_SYS_ADMIN); > + ASSERT_EQ(0, unshare(CLONE_NEWNET)); > + ASSERT_EQ(0, system("ip link set dev lo up")); > + clear_cap(_metadata, CAP_SYS_ADMIN); > +} > + > +static bool is_restricted(const struct protocol_variant *const prot, > + const enum sandbox_type sandbox) > +{ > + switch (prot->domain) { > + case AF_INET: > + case AF_INET6: > + switch (prot->type) { > + case SOCK_STREAM: > + return sandbox == TCP_SANDBOX; > + } > + break; > + } > + return false; > +} > + > +static int socket_variant(const struct service_fixture *const srv) > +{ > + int ret; > + > + ret = socket(srv->protocol.domain, srv->protocol.type | SOCK_CLOEXEC, > + 0); > + if (ret < 0) > + return -errno; > + return ret; > +} > + > +#ifndef SIN6_LEN_RFC2133 > +#define SIN6_LEN_RFC2133 24 > +#endif > + > +static socklen_t get_addrlen(const struct service_fixture *const srv, > + const bool minimal) > +{ > + switch (srv->protocol.domain) { > + case AF_UNSPEC: > + case AF_INET: > + return sizeof(srv->ipv4_addr); > + > + case AF_INET6: > + if (minimal) > + return SIN6_LEN_RFC2133; > + return sizeof(srv->ipv6_addr); > + > + case AF_UNIX: > + if (minimal) > + return sizeof(srv->unix_addr) - > + sizeof(srv->unix_addr.sun_path); > + return srv->unix_addr_len; > + > + default: > + return 0; > + } > +} > + > +static int bind_variant_addrlen(const int sock_fd, > + const struct service_fixture *const srv, > + const socklen_t addrlen) > +{ > + int ret; > + > + switch (srv->protocol.domain) { > + case AF_UNSPEC: > + case AF_INET: > + ret = bind(sock_fd, &srv->ipv4_addr, addrlen); > + break; > + > + case AF_INET6: > + ret = bind(sock_fd, &srv->ipv6_addr, addrlen); > + break; > + > + case AF_UNIX: > + ret = bind(sock_fd, &srv->unix_addr, addrlen); > + break; > + > + default: > + errno = -EAFNOSUPPORT; > + return -errno; > + } > + > + if (ret < 0) > + return -errno; > + return ret; > +} > + > +static int bind_variant(const int sock_fd, > + const struct service_fixture *const srv) > +{ > + return bind_variant_addrlen(sock_fd, srv, get_addrlen(srv, false)); > +} > + > +static int connect_variant_addrlen(const int sock_fd, > + const struct service_fixture *const srv, > + const socklen_t addrlen) > +{ > + int ret; > + > + switch (srv->protocol.domain) { > + case AF_UNSPEC: > + case AF_INET: > + ret = connect(sock_fd, &srv->ipv4_addr, addrlen); > + break; > + > + case AF_INET6: > + ret = connect(sock_fd, &srv->ipv6_addr, addrlen); > + break; > + > + case AF_UNIX: > + ret = connect(sock_fd, &srv->unix_addr, addrlen); > + break; > + > + default: > + errno = -EAFNOSUPPORT; > + return -errno; > + } > + > + if (ret < 0) > + return -errno; > + return ret; > +} > + > +static int connect_variant(const int sock_fd, > + const struct service_fixture *const srv) > +{ > + return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false)); > +} > + > +FIXTURE(protocol) > +{ > + struct service_fixture srv0, srv1, srv2, unspec_any, unspec_srv0; > +}; > + > +FIXTURE_VARIANT(protocol) > +{ > + const enum sandbox_type sandbox; > + const struct protocol_variant prot; > +}; > + > +FIXTURE_SETUP(protocol) > +{ > + const struct protocol_variant prot_unspec = { > + .domain = AF_UNSPEC, > + .type = SOCK_STREAM, > + }; > + > + disable_caps(_metadata); > + > + ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0)); > + ASSERT_EQ(0, set_service(&self->srv1, variant->prot, 1)); > + ASSERT_EQ(0, set_service(&self->srv2, variant->prot, 2)); > + > + ASSERT_EQ(0, set_service(&self->unspec_srv0, prot_unspec, 0)); > + > + ASSERT_EQ(0, set_service(&self->unspec_any, prot_unspec, 0)); > + self->unspec_any.ipv4_addr.sin_addr.s_addr = htonl(INADDR_ANY); > + > + setup_loopback(_metadata); > +}; > + > +FIXTURE_TEARDOWN(protocol) > +{ > +} > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_tcp) { > + /* clang-format on */ > + .sandbox = NO_SANDBOX, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_tcp) { > + /* clang-format on */ > + .sandbox = NO_SANDBOX, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_udp) { > + /* clang-format on */ > + .sandbox = NO_SANDBOX, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_DGRAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_udp) { > + /* clang-format on */ > + .sandbox = NO_SANDBOX, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_DGRAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_unix_stream) { > + /* clang-format on */ > + .sandbox = NO_SANDBOX, > + .prot = { > + .domain = AF_UNIX, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_unix_datagram) { > + /* clang-format on */ > + .sandbox = NO_SANDBOX, > + .prot = { > + .domain = AF_UNIX, > + .type = SOCK_DGRAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_tcp) { > + /* clang-format on */ > + .sandbox = TCP_SANDBOX, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_tcp) { > + /* clang-format on */ > + .sandbox = TCP_SANDBOX, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_udp) { > + /* clang-format on */ > + .sandbox = TCP_SANDBOX, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_DGRAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_udp) { > + /* clang-format on */ > + .sandbox = TCP_SANDBOX, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_DGRAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_stream) { > + /* clang-format on */ > + .sandbox = TCP_SANDBOX, > + .prot = { > + .domain = AF_UNIX, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) { > + /* clang-format on */ > + .sandbox = TCP_SANDBOX, > + .prot = { > + .domain = AF_UNIX, > + .type = SOCK_DGRAM, > + }, > +}; > + > +static void test_bind_and_connect(struct __test_metadata *const _metadata, > + const struct service_fixture *const srv, > + const bool deny_bind, const bool deny_connect) > +{ > + char buf = '\0'; > + int inval_fd, bind_fd, client_fd, status, ret; > + pid_t child; > + > + /* Starts invalid addrlen tests with bind. */ > + inval_fd = socket_variant(srv); > + ASSERT_LE(0, inval_fd) > + { > + TH_LOG("Failed to create socket: %s", strerror(errno)); > + } > + > + /* Tries to bind with zero as addrlen. */ > + EXPECT_EQ(-EINVAL, bind_variant_addrlen(inval_fd, srv, 0)); > + > + /* Tries to bind with too small addrlen. */ > + EXPECT_EQ(-EINVAL, bind_variant_addrlen(inval_fd, srv, > + get_addrlen(srv, true) - 1)); > + > + /* Tries to bind with minimal addrlen. */ > + ret = bind_variant_addrlen(inval_fd, srv, get_addrlen(srv, true)); > + if (deny_bind) { > + EXPECT_EQ(-EACCES, ret); > + } else { > + EXPECT_EQ(0, ret) > + { > + TH_LOG("Failed to bind to socket: %s", strerror(errno)); > + } > + } > + EXPECT_EQ(0, close(inval_fd)); > + > + /* Starts invalid addrlen tests with connect. */ > + inval_fd = socket_variant(srv); > + ASSERT_LE(0, inval_fd); > + > + /* Tries to connect with zero as addrlen. */ > + EXPECT_EQ(-EINVAL, connect_variant_addrlen(inval_fd, srv, 0)); > + > + /* Tries to connect with too small addrlen. */ > + EXPECT_EQ(-EINVAL, connect_variant_addrlen(inval_fd, srv, > + get_addrlen(srv, true) - 1)); > + > + /* Tries to connect with minimal addrlen. */ > + ret = connect_variant_addrlen(inval_fd, srv, get_addrlen(srv, true)); > + if (srv->protocol.domain == AF_UNIX) { > + EXPECT_EQ(-EINVAL, ret); > + } else if (deny_connect) { > + EXPECT_EQ(-EACCES, ret); > + } else if (srv->protocol.type == SOCK_STREAM) { > + /* No listening server, whatever the value of deny_bind. */ > + EXPECT_EQ(-ECONNREFUSED, ret); > + } else { > + EXPECT_EQ(0, ret) > + { > + TH_LOG("Failed to connect to socket: %s", > + strerror(errno)); > + } > + } > + EXPECT_EQ(0, close(inval_fd)); > + > + /* Starts connection tests. */ > + bind_fd = socket_variant(srv); > + ASSERT_LE(0, bind_fd); > + > + ret = bind_variant(bind_fd, srv); > + if (deny_bind) { > + EXPECT_EQ(-EACCES, ret); > + } else { > + EXPECT_EQ(0, ret); > + > + /* Creates a listening socket. */ > + if (srv->protocol.type == SOCK_STREAM) > + EXPECT_EQ(0, listen(bind_fd, backlog)); > + } > + > + child = fork(); > + ASSERT_LE(0, child); > + if (child == 0) { > + int connect_fd, ret; > + > + /* Closes listening socket for the child. */ > + EXPECT_EQ(0, close(bind_fd)); > + > + /* Starts connection tests. */ > + connect_fd = socket_variant(srv); > + ASSERT_LE(0, connect_fd); > + ret = connect_variant(connect_fd, srv); > + if (deny_connect) { > + EXPECT_EQ(-EACCES, ret); > + } else if (deny_bind) { > + /* No listening server. */ > + EXPECT_EQ(-ECONNREFUSED, ret); > + } else { > + EXPECT_EQ(0, ret); > + EXPECT_EQ(1, write(connect_fd, ".", 1)); > + } > + > + EXPECT_EQ(0, close(connect_fd)); > + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); > + return; > + } > + > + /* Accepts connection from the child. */ > + client_fd = bind_fd; > + if (!deny_bind && !deny_connect) { > + if (srv->protocol.type == SOCK_STREAM) { > + client_fd = accept(bind_fd, NULL, 0); > + ASSERT_LE(0, client_fd); > + } > + > + EXPECT_EQ(1, read(client_fd, &buf, 1)); > + EXPECT_EQ('.', buf); > + } > + > + EXPECT_EQ(child, waitpid(child, &status, 0)); > + EXPECT_EQ(1, WIFEXITED(status)); > + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); > + > + /* Closes connection, if any. */ > + if (client_fd != bind_fd) > + EXPECT_LE(0, close(client_fd)); > + > + /* Closes listening socket. */ > + EXPECT_EQ(0, close(bind_fd)); > +} > + > +TEST_F(protocol, bind) > +{ > + if (variant->sandbox == TCP_SANDBOX) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr tcp_bind_connect_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = self->srv0.port, > + }; > + const struct landlock_net_service_attr tcp_connect_p1 = { > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = self->srv1.port, > + }; > + int ruleset_fd; > + > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Allows connect and bind for the first port. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_connect_p0, 0)); > + > + /* Allows connect and denies bind for the second port. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_connect_p1, 0)); > + > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + /* Binds a socket to the first port. */ > + test_bind_and_connect(_metadata, &self->srv0, false, false); > + > + /* Binds a socket to the second port. */ > + test_bind_and_connect(_metadata, &self->srv1, > + is_restricted(&variant->prot, variant->sandbox), > + false); > + > + /* Binds a socket to the third port. */ > + test_bind_and_connect(_metadata, &self->srv2, > + is_restricted(&variant->prot, variant->sandbox), > + is_restricted(&variant->prot, variant->sandbox)); > +} > + > +TEST_F(protocol, connect) > +{ > + if (variant->sandbox == TCP_SANDBOX) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr tcp_bind_connect_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = self->srv0.port, > + }; > + const struct landlock_net_service_attr tcp_bind_p1 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = self->srv1.port, > + }; > + int ruleset_fd; > + > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Allows connect and bind for the first port. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_connect_p0, 0)); > + > + /* Allows bind and denies connect for the second port. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_p1, 0)); > + > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + test_bind_and_connect(_metadata, &self->srv0, false, false); > + > + test_bind_and_connect(_metadata, &self->srv1, false, > + is_restricted(&variant->prot, variant->sandbox)); > + > + test_bind_and_connect(_metadata, &self->srv2, > + is_restricted(&variant->prot, variant->sandbox), > + is_restricted(&variant->prot, variant->sandbox)); > +} > + > +// Kernel FIXME: tcp_sandbox_with_ipv6_tcp and tcp_sandbox_with_unix_stream > +TEST_F(protocol, bind_unspec) > +{ > + int bind_fd, ret; > + > + if (variant->sandbox == TCP_SANDBOX) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, > + }; > + const struct landlock_net_service_attr tcp_bind = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = self->srv0.port, > + }; > + int ruleset_fd; > + > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Allows bind. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + bind_fd = socket_variant(&self->srv0); > + ASSERT_LE(0, bind_fd); > + > + /* Binds on AF_UNSPEC/INADDR_ANY. */ > + ret = bind_variant(bind_fd, &self->unspec_any); > + if (variant->prot.domain == AF_INET) { > + EXPECT_EQ(0, ret) > + { > + TH_LOG("Failed to bind to unspec/any socket: %s", > + strerror(errno)); > + } > + } else { > + EXPECT_EQ(-EINVAL, ret); > + } > + EXPECT_EQ(0, close(bind_fd)); > + > + bind_fd = socket_variant(&self->srv0); > + ASSERT_LE(0, bind_fd); > + ret = bind_variant(bind_fd, &self->unspec_srv0); > + if (variant->prot.domain == AF_INET) { > + EXPECT_EQ(-EAFNOSUPPORT, ret); > + } else { > + EXPECT_EQ(-EINVAL, ret) > + { > + TH_LOG("Wrong bind error: %s", strerror(errno)); > + } > + } > + EXPECT_EQ(0, close(bind_fd)); > +} > + > +TEST_F(protocol, connect_unspec) > +{ > + int bind_fd, client_fd, status; > + pid_t child; > + > + if (variant->sandbox == TCP_SANDBOX) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr tcp_connect = { > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = self->srv0.port, > + }; > + int ruleset_fd; > + > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Allows connect. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_connect, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + /* Generic connection tests. */ > + test_bind_and_connect(_metadata, &self->srv0, false, false); > + > + /* Specific connection tests. */ > + bind_fd = socket_variant(&self->srv0); > + ASSERT_LE(0, bind_fd); > + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0)); > + if (self->srv0.protocol.type == SOCK_STREAM) > + EXPECT_EQ(0, listen(bind_fd, backlog)); > + > + child = fork(); > + ASSERT_LE(0, child); > + if (child == 0) { > + int connect_fd, ret; > + > + /* Closes listening socket for the child. */ > + EXPECT_EQ(0, close(bind_fd)); > + > + connect_fd = socket_variant(&self->srv0); > + ASSERT_LE(0, connect_fd); > + EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0)); > + > + /* Tries to connect again, or set peer. */ > + ret = connect_variant(connect_fd, &self->srv0); > + if (self->srv0.protocol.type == SOCK_STREAM) { > + EXPECT_EQ(-EISCONN, ret); > + } else { > + EXPECT_EQ(0, ret); > + } > + > + /* Disconnects already connected socket, or set peer. */ > + ret = connect_variant(connect_fd, &self->unspec_any); > + if (self->srv0.protocol.domain == AF_UNIX && > + self->srv0.protocol.type == SOCK_STREAM) { > + EXPECT_EQ(-EINVAL, ret); > + } else { > + EXPECT_EQ(0, ret); > + } > + > + /* Tries to reconnect, or set peer. */ > + ret = connect_variant(connect_fd, &self->srv0); > + if (self->srv0.protocol.domain == AF_UNIX && > + self->srv0.protocol.type == SOCK_STREAM) { > + EXPECT_EQ(-EISCONN, ret); > + } else { > + EXPECT_EQ(0, ret); > + } > + > + EXPECT_EQ(0, close(connect_fd)); > + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); > + return; > + } > + > + client_fd = bind_fd; > + if (self->srv0.protocol.type == SOCK_STREAM) { > + client_fd = accept(bind_fd, NULL, 0); > + ASSERT_LE(0, client_fd); > + } > + > + EXPECT_EQ(child, waitpid(child, &status, 0)); > + EXPECT_EQ(1, WIFEXITED(status)); > + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); > + > + /* Closes connection, if any. */ > + if (client_fd != bind_fd) > + EXPECT_LE(0, close(client_fd)); > + > + /* Closes listening socket. */ > + EXPECT_EQ(0, close(bind_fd)); > +} > + > +FIXTURE(ipv4) > +{ > + struct service_fixture srv0, srv1; > +}; > + > +FIXTURE_VARIANT(ipv4) > +{ > + const enum sandbox_type sandbox; > + const int type; > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(ipv4, no_sandbox_with_tcp) { > + /* clang-format on */ > + .sandbox = NO_SANDBOX, > + .type = SOCK_STREAM, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_tcp) { > + /* clang-format on */ > + .sandbox = TCP_SANDBOX, > + .type = SOCK_STREAM, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(ipv4, no_sandbox_with_udp) { > + /* clang-format on */ > + .sandbox = NO_SANDBOX, > + .type = SOCK_DGRAM, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_udp) { > + /* clang-format on */ > + .sandbox = TCP_SANDBOX, > + .type = SOCK_DGRAM, > +}; > + > +FIXTURE_SETUP(ipv4) > +{ > + const struct protocol_variant prot = { > + .domain = AF_INET, > + .type = variant->type, > + }; > + > + disable_caps(_metadata); > + > + set_service(&self->srv0, prot, 0); > + set_service(&self->srv1, prot, 1); > + > + setup_loopback(_metadata); > +}; > + > +FIXTURE_TEARDOWN(ipv4) > +{ > +} > + > +// Kernel FIXME: tcp_sandbox_with_tcp and tcp_sandbox_with_udp I debugged the code in qemu and came to a conclusion that we don't check if socket's family equals to address's one in check_socket_access(...) function in net.c So I added the next lines (marked with !!!): static int check_socket_access(struct socket *const sock, struct sockaddr *const address, const int addrlen, const access_mask_t access_request) { __be16 port; layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {}; const struct landlock_rule *rule; access_mask_t handled_access; struct landlock_id id = { .type = LANDLOCK_KEY_NET_PORT, }; const struct landlock_ruleset *const domain = get_current_net_domain(); if (!domain) return 0; if (WARN_ON_ONCE(domain->num_layers < 1)) return -EACCES; /* FIXES network tests */ !!! if (sock->sk->__sk_common.skc_family != address->sa_family) !!! return 0; !!! /* Checks if it's a TCP socket. */ if (sock->type != SOCK_STREAM) return 0; ...... So now all network tests pass. What do you think? > +TEST_F(ipv4, from_unix_to_inet) > +{ > + int unix_stream_fd, unix_dgram_fd; > + > + if (variant->sandbox == TCP_SANDBOX) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr tcp_bind_connect_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = self->srv0.port, > + }; > + int ruleset_fd; > + > + /* Denies connect and bind to check errno value. */ > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Allows connect and bind for srv0. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_connect_p0, 0)); > + > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + unix_stream_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); > + ASSERT_LE(0, unix_stream_fd); > + > + unix_dgram_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); Minor mistyping SOCK_STREAM -> SOCK_DGRAM. > + ASSERT_LE(0, unix_dgram_fd); > + > + /* Checks unix stream bind and connect for srv0. */ > + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv0)); > + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv0)); > + > + /* Checks unix stream bind and connect for srv1. */ > + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv1)) > + { > + TH_LOG("Wrong bind error: %s", strerror(errno)); > + } > + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv1)); > + > + /* Checks unix datagram bind and connect for srv0. */ > + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv0)); > + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv0)); > + > + /* Checks unix datagram bind and connect for srv0. */ Should be "Checks... for srv1." > + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv1)); > + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv1)); > +} > + > +FIXTURE(tcp_layers) > +{ > + struct service_fixture srv0, srv1; > +}; > + > +FIXTURE_VARIANT(tcp_layers) > +{ > + const size_t num_layers; > + const int domain; > +}; > + > +FIXTURE_SETUP(tcp_layers) > +{ > + const struct protocol_variant prot = { > + .domain = variant->domain, > + .type = SOCK_STREAM, > + }; > + > + disable_caps(_metadata); > + > + ASSERT_EQ(0, set_service(&self->srv0, prot, 0)); > + ASSERT_EQ(0, set_service(&self->srv1, prot, 1)); > + > + setup_loopback(_metadata); > +}; > + > +FIXTURE_TEARDOWN(tcp_layers) > +{ > +} > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(tcp_layers, no_sandbox_with_ipv4) { > + /* clang-format on */ > + .domain = AF_INET, > + .num_layers = 0, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(tcp_layers, one_sandbox_with_ipv4) { > + /* clang-format on */ > + .domain = AF_INET, > + .num_layers = 1, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(tcp_layers, two_sandboxes_with_ipv4) { > + /* clang-format on */ > + .domain = AF_INET, > + .num_layers = 2, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(tcp_layers, three_sandboxes_with_ipv4) { > + /* clang-format on */ > + .domain = AF_INET, > + .num_layers = 3, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(tcp_layers, no_sandbox_with_ipv6) { > + /* clang-format on */ > + .domain = AF_INET6, > + .num_layers = 0, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(tcp_layers, one_sandbox_with_ipv6) { > + /* clang-format on */ > + .domain = AF_INET6, > + .num_layers = 1, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(tcp_layers, two_sandboxes_with_ipv6) { > + /* clang-format on */ > + .domain = AF_INET6, > + .num_layers = 2, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(tcp_layers, three_sandboxes_with_ipv6) { > + /* clang-format on */ > + .domain = AF_INET6, > + .num_layers = 3, > +}; > + > +TEST_F(tcp_layers, ruleset_overlap) > +{ > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr tcp_bind = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = self->srv0.port, > + }; > + const struct landlock_net_service_attr tcp_bind_connect = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = self->srv0.port, > + }; > + > + if (variant->num_layers >= 1) { > + int ruleset_fd; > + > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Allows bind. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind, 0)); > + /* Also allows bind, but allows connect too. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_connect, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + if (variant->num_layers >= 2) { > + int ruleset_fd; > + > + /* Creates another ruleset layer. */ > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Only allows bind. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + if (variant->num_layers >= 3) { > + int ruleset_fd; > + > + /* Creates another ruleset layer. */ > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Try to allow bind and connect. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_connect, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + /* > + * Forbids to connect to the socket because only one ruleset layer > + * allows connect. > + */ > + test_bind_and_connect(_metadata, &self->srv0, false, > + variant->num_layers >= 2); > +} > + > +TEST_F(tcp_layers, ruleset_expand) > +{ > + if (variant->num_layers >= 1) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, > + }; > + /* Allows bind for srv0. */ > + const struct landlock_net_service_attr bind_srv0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = self->srv0.port, > + }; > + int ruleset_fd; > + > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &bind_srv0, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + if (variant->num_layers >= 2) { > + /* Expands network mask with connect action. */ > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + /* Allows bind for srv0 and connect to srv0. */ > + const struct landlock_net_service_attr tcp_bind_connect_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = self->srv0.port, > + }; > + /* Try to allow bind for srv1. */ > + const struct landlock_net_service_attr tcp_bind_p1 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = self->srv1.port, > + }; > + int ruleset_fd; > + > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_connect_p0, 0)); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_p1, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + if (variant->num_layers >= 3) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + /* Allows connect to srv0, without bind rule. */ > + const struct landlock_net_service_attr tcp_bind_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = self->srv0.port, > + }; > + int ruleset_fd; > + > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_p0, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + EXPECT_EQ(0, close(ruleset_fd)); > + } > + > + test_bind_and_connect(_metadata, &self->srv0, false, > + variant->num_layers >= 3); > + > + test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1, > + variant->num_layers >= 2); > +} > + > +/* clang-format off */ > +FIXTURE(mini) {}; > +/* clang-format on */ > + > +FIXTURE_SETUP(mini) > +{ > + disable_caps(_metadata); > + > + setup_loopback(_metadata); > +}; > + > +FIXTURE_TEARDOWN(mini) > +{ > +} > + > +/* clang-format off */ > + > +#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP > + > +#define ACCESS_ALL ( \ > + LANDLOCK_ACCESS_NET_BIND_TCP | \ > + LANDLOCK_ACCESS_NET_CONNECT_TCP) > + > +/* clang-format on */ > + > +TEST_F(mini, network_access_rights) > +{ > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = ACCESS_ALL, > + }; > + struct landlock_net_service_attr net_service = { > + .port = sock_port_start, > + }; > + int ruleset_fd; > + __u64 access; > + > + ruleset_fd = > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + for (access = 1; access <= ACCESS_LAST; access <<= 1) { > + net_service.allowed_access = access; > + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, > + LANDLOCK_RULE_NET_SERVICE, > + &net_service, 0)) > + { > + TH_LOG("Failed to add rule with access 0x%llx: %s", > + access, strerror(errno)); > + } > + } > + EXPECT_EQ(0, close(ruleset_fd)); > +} > + > +/* Checks invalid attribute, out of landlock network access range. */ > +TEST_F(mini, unknown_access_rights) > +{ > + __u64 access_mask; > + > + for (access_mask = 1ULL << 63; access_mask != ACCESS_LAST; > + access_mask >>= 1) { > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = access_mask, > + }; > + > + EXPECT_EQ(-1, landlock_create_ruleset(&ruleset_attr, > + sizeof(ruleset_attr), 0)); > + EXPECT_EQ(EINVAL, errno); > + } > +} > + > +TEST_F(mini, inval) > +{ > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP > + }; > + const struct landlock_net_service_attr tcp_bind_connect = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = sock_port_start, > + }; > + const struct landlock_net_service_attr tcp_bind_port_zero = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = 0, > + }; > + const struct landlock_net_service_attr tcp_denied = { > + .allowed_access = 0, > + .port = sock_port_start, > + }; > + const struct landlock_net_service_attr tcp_bind = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = sock_port_start, > + }; > + int ruleset_fd; > + > + ruleset_fd = > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + /* Checks unhandled allowed_access. */ > + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_connect, 0)); > + EXPECT_EQ(EINVAL, errno); > + > + /* Checks zero port value. */ > + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind_port_zero, 0)); > + EXPECT_EQ(EINVAL, errno); > + > + /* Checks zero access value. */ > + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &tcp_denied, 0)); > + EXPECT_EQ(ENOMSG, errno); > + > + /* Adds with legitimate values. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &tcp_bind, 0)); > +} > + > +TEST_F(mini, tcp_port_overflow) > +{ > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr port_max_bind = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = UINT16_MAX, > + }; > + const struct landlock_net_service_attr port_max_connect = { > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, > + .port = UINT16_MAX, > + }; > + const struct landlock_net_service_attr port_overflow1 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = UINT16_MAX + 1, > + }; > + const struct landlock_net_service_attr port_overflow2 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = UINT16_MAX + 2, > + }; > + const struct landlock_net_service_attr port_overflow3 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = UINT32_MAX + 1UL, > + }; > + const struct landlock_net_service_attr port_overflow4 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + .port = UINT32_MAX + 2UL, > + }; > + const struct protocol_variant ipv4_tcp = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }; > + struct service_fixture srv_denied, srv_max_allowed; > + int ruleset_fd; > + > + ASSERT_EQ(0, set_service(&srv_denied, ipv4_tcp, 0)); > + > + /* Be careful to avoid port inconsistencies. */ > + srv_max_allowed = srv_denied; > + srv_max_allowed.port = port_max_bind.port; > + srv_max_allowed.ipv4_addr.sin_port = htons(port_max_bind.port); > + > + ruleset_fd = > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &port_max_bind, 0)); > + > + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &port_overflow1, 0)); > + EXPECT_EQ(EINVAL, errno); > + > + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &port_overflow2, 0)); > + EXPECT_EQ(EINVAL, errno); > + > + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &port_overflow3, 0)); > + EXPECT_EQ(EINVAL, errno); > + > + /* Interleaves with invalid rule additions. */ > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &port_max_connect, 0)); > + > + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &port_overflow4, 0)); > + EXPECT_EQ(EINVAL, errno); > + > + enforce_ruleset(_metadata, ruleset_fd); > + > + test_bind_and_connect(_metadata, &srv_denied, true, true); > + test_bind_and_connect(_metadata, &srv_max_allowed, false, false); > +} > + > +FIXTURE(inet) > +{ > + struct service_fixture srv0, srv1; > +}; > + > +FIXTURE_VARIANT(inet) > +{ > + const bool is_sandboxed; > + const struct protocol_variant prot; > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { > + /* clang-format on */ > + .is_sandboxed = false, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { > + /* clang-format on */ > + .is_sandboxed = true, > + .prot = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { > + /* clang-format on */ > + .is_sandboxed = false, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_STREAM, > + }, > +}; > + > +/* clang-format off */ > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { > + /* clang-format on */ > + .is_sandboxed = true, > + .prot = { > + .domain = AF_INET6, > + .type = SOCK_STREAM, > + }, > +}; > + > +FIXTURE_SETUP(inet) > +{ > + const struct protocol_variant ipv4_tcp = { > + .domain = AF_INET, > + .type = SOCK_STREAM, > + }; > + > + disable_caps(_metadata); > + > + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); > + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); > + > + setup_loopback(_metadata); > +}; > + > +FIXTURE_TEARDOWN(inet) > +{ > +} > + > +TEST_F(inet, port_endianness) > +{ > + const struct landlock_ruleset_attr ruleset_attr = { > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + }; > + const struct landlock_net_service_attr bind_host_endian_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > + /* Host port format. */ > + .port = self->srv0.port, > + }; > + const struct landlock_net_service_attr connect_big_endian_p0 = { > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, > + /* Big endian port format. */ > + .port = htons(self->srv0.port), > + }; > + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > + /* Host port format. */ > + .port = self->srv1.port, > + }; > + const unsigned int one = 1; > + const char little_endian = *(const char *)&one; > + int ruleset_fd; > + > + ruleset_fd = > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); > + ASSERT_LE(0, ruleset_fd); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &bind_host_endian_p0, 0)); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &connect_big_endian_p0, 0)); > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > + &bind_connect_host_endian_p1, 0)); > + enforce_ruleset(_metadata, ruleset_fd); > + > + /* No restriction for big endinan CPU. */ > + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); > + > + /* No restriction for any CPU. */ > + test_bind_and_connect(_metadata, &self->srv1, false, false); > +} > + > +TEST_HARNESS_MAIN
7/12/2023 10:02 AM, Mickaël Salaün пишет: > > On 06/07/2023 16:55, Mickaël Salaün wrote: >> From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> >> This patch is a revamp of the v11 tests [1] with new tests (see the >> "Changes since v11" description). I (Mickaël) only added the following >> todo list and the "Changes since v11" sections in this commit message. >> I think this patch is good but it would appreciate reviews. >> You can find the diff of my changes here but it is not really readable: >> https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> TODO: >> - Rename all "net_service" to "net_port". >> - Fix the two kernel bugs found with the new tests. >> - Update this commit message with a small description of all tests. > > [...] > >> +FIXTURE_SETUP(ipv4) >> +{ >> + const struct protocol_variant prot = { >> + .domain = AF_INET, >> + .type = variant->type, >> + }; >> + >> + disable_caps(_metadata); >> + >> + set_service(&self->srv0, prot, 0); >> + set_service(&self->srv1, prot, 1); >> + >> + setup_loopback(_metadata); >> +}; >> + >> +FIXTURE_TEARDOWN(ipv4) >> +{ >> +} >> + >> +// Kernel FIXME: tcp_sandbox_with_tcp and tcp_sandbox_with_udp >> +TEST_F(ipv4, from_unix_to_inet) >> +{ >> + int unix_stream_fd, unix_dgram_fd; >> + >> + if (variant->sandbox == TCP_SANDBOX) { >> + const struct landlock_ruleset_attr ruleset_attr = { >> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + }; >> + const struct landlock_net_service_attr tcp_bind_connect_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + .port = self->srv0.port, >> + }; >> + int ruleset_fd; >> + >> + /* Denies connect and bind to check errno value. */ >> + ruleset_fd = landlock_create_ruleset(&ruleset_attr, >> + sizeof(ruleset_attr), 0); >> + ASSERT_LE(0, ruleset_fd); >> + >> + /* Allows connect and bind for srv0. */ >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, >> + LANDLOCK_RULE_NET_SERVICE, >> + &tcp_bind_connect_p0, 0)); >> + >> + enforce_ruleset(_metadata, ruleset_fd); >> + EXPECT_EQ(0, close(ruleset_fd)); >> + } >> + >> + unix_stream_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); >> + ASSERT_LE(0, unix_stream_fd); >> + >> + unix_dgram_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); >> + ASSERT_LE(0, unix_dgram_fd); >> + >> + /* Checks unix stream bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv0)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv0)); >> + >> + /* Checks unix stream bind and connect for srv1. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv1)) >> + { >> + TH_LOG("Wrong bind error: %s", strerror(errno)); >> + } >> + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv1)); >> + >> + /* Checks unix datagram bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv0)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv0)); >> + >> + /* Checks unix datagram bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv1)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv1)); >> +} > > We should also add a test to make sure errno is the same with and > without sandboxing when using port 0 for connect and consistent with > bind (using an available port). The test fixture and variants should be > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, > which will result in 8 "ip" variants: > > TEST_F(ip, port_zero) > { > if (variant->sandbox == TCP_SANDBOX) { > /* Denies any connect and bind. */ > } > /* Checks errno for port 0. */ > } As I understand the would be the next test cases: 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by landlock). 2. ip4, non-sandboxed, bind port 0 -> should return 0 (should be bounded to random port). 3. ip6, sandboxed, bind port 0 -> should return EACCES (denied by landlock). 4. ip6, non-sandboxed, bind port 0 -> should return 0 (should be bounded to random port). 5. ip4, sandboxed, bind some available port, connect port 0 -> should return -EACCES (denied by landlock). 6. ip4, non-sandboxed, bind some available port, connect port 0 -> should return ECONNREFUSED. 7. ip6, sandboxed, bind some available port, connect port 0 -> should return -EACCES (denied by landlock) 8. ip6, non-sandboxed, some bind available port, connect port 0 -> should return ECONNREFUSED. Correct? > > [...] > >> +FIXTURE(inet) >> +{ >> + struct service_fixture srv0, srv1; >> +}; > > The "inet" variants are useless and should be removed. The "inet" > fixture can then be renamed to "ipv4_tcp". > So inet should be changed to ipv4_tcp and ipv6_tcp with next variants: - ipv4_tcp.no_sandbox_with_ipv4.port_endianness - ipv4_tcp.sandbox_with_ipv4.port_endianness - ipv6_tcp.no_sandbox_with_ipv6.port_endianness - ipv6_tcp.sandbox_with_ipv6.port_endianness ???? in this case we need double copy of TEST_F(inet, port_endianness) : TEST_F(ipv4_tcp, port_endianness) TEST_F(ipv6_tcp, port_endianness) > >> + >> +FIXTURE_VARIANT(inet) >> +{ >> + const bool is_sandboxed; >> + const struct protocol_variant prot; >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { >> + /* clang-format on */ >> + .is_sandboxed = false, >> + .prot = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { >> + /* clang-format on */ >> + .is_sandboxed = true, >> + .prot = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { >> + /* clang-format on */ >> + .is_sandboxed = false, >> + .prot = { >> + .domain = AF_INET6, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { >> + /* clang-format on */ >> + .is_sandboxed = true, >> + .prot = { >> + .domain = AF_INET6, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +FIXTURE_SETUP(inet) >> +{ >> + const struct protocol_variant ipv4_tcp = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }; >> + >> + disable_caps(_metadata); >> + >> + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); >> + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); >> + >> + setup_loopback(_metadata); >> +}; >> + >> +FIXTURE_TEARDOWN(inet) >> +{ >> +} >> + >> +TEST_F(inet, port_endianness) >> +{ >> + const struct landlock_ruleset_attr ruleset_attr = { >> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + }; >> + const struct landlock_net_service_attr bind_host_endian_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, >> + /* Host port format. */ >> + .port = self->srv0.port, >> + }; >> + const struct landlock_net_service_attr connect_big_endian_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + /* Big endian port format. */ >> + .port = htons(self->srv0.port), >> + }; >> + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + /* Host port format. */ >> + .port = self->srv1.port, >> + }; >> + const unsigned int one = 1; >> + const char little_endian = *(const char *)&one; >> + int ruleset_fd; >> + >> + ruleset_fd = >> + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); >> + ASSERT_LE(0, ruleset_fd); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &bind_host_endian_p0, 0)); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &connect_big_endian_p0, 0)); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &bind_connect_host_endian_p1, 0)); >> + enforce_ruleset(_metadata, ruleset_fd); >> + >> + /* No restriction for big endinan CPU. */ >> + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); >> + >> + /* No restriction for any CPU. */ >> + test_bind_and_connect(_metadata, &self->srv1, false, false); >> +} >> + >> +TEST_HARNESS_MAIN > .
7/12/2023 10:02 AM, Mickaël Salaün пишет: > > On 06/07/2023 16:55, Mickaël Salaün wrote: >> From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> >> This patch is a revamp of the v11 tests [1] with new tests (see the >> "Changes since v11" description). I (Mickaël) only added the following >> todo list and the "Changes since v11" sections in this commit message. >> I think this patch is good but it would appreciate reviews. >> You can find the diff of my changes here but it is not really readable: >> https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> TODO: >> - Rename all "net_service" to "net_port". >> - Fix the two kernel bugs found with the new tests. >> - Update this commit message with a small description of all tests. > > [...] > >> +FIXTURE_SETUP(ipv4) >> +{ >> + const struct protocol_variant prot = { >> + .domain = AF_INET, >> + .type = variant->type, >> + }; >> + >> + disable_caps(_metadata); >> + >> + set_service(&self->srv0, prot, 0); >> + set_service(&self->srv1, prot, 1); >> + >> + setup_loopback(_metadata); >> +}; >> + >> +FIXTURE_TEARDOWN(ipv4) >> +{ >> +} >> + >> +// Kernel FIXME: tcp_sandbox_with_tcp and tcp_sandbox_with_udp >> +TEST_F(ipv4, from_unix_to_inet) >> +{ >> + int unix_stream_fd, unix_dgram_fd; >> + >> + if (variant->sandbox == TCP_SANDBOX) { >> + const struct landlock_ruleset_attr ruleset_attr = { >> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + }; >> + const struct landlock_net_service_attr tcp_bind_connect_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + .port = self->srv0.port, >> + }; >> + int ruleset_fd; >> + >> + /* Denies connect and bind to check errno value. */ >> + ruleset_fd = landlock_create_ruleset(&ruleset_attr, >> + sizeof(ruleset_attr), 0); >> + ASSERT_LE(0, ruleset_fd); >> + >> + /* Allows connect and bind for srv0. */ >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, >> + LANDLOCK_RULE_NET_SERVICE, >> + &tcp_bind_connect_p0, 0)); >> + >> + enforce_ruleset(_metadata, ruleset_fd); >> + EXPECT_EQ(0, close(ruleset_fd)); >> + } >> + >> + unix_stream_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); >> + ASSERT_LE(0, unix_stream_fd); >> + >> + unix_dgram_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); >> + ASSERT_LE(0, unix_dgram_fd); >> + >> + /* Checks unix stream bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv0)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv0)); >> + >> + /* Checks unix stream bind and connect for srv1. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv1)) >> + { >> + TH_LOG("Wrong bind error: %s", strerror(errno)); >> + } >> + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv1)); >> + >> + /* Checks unix datagram bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv0)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv0)); >> + >> + /* Checks unix datagram bind and connect for srv0. */ >> + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv1)); >> + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv1)); >> +} > > We should also add a test to make sure errno is the same with and > without sandboxing when using port 0 for connect and consistent with > bind (using an available port). The test fixture and variants should be > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, > which will result in 8 "ip" variants: > > TEST_F(ip, port_zero) > { > if (variant->sandbox == TCP_SANDBOX) { > /* Denies any connect and bind. */ > } > /* Checks errno for port 0. */ > } > > [...] > >> +FIXTURE(inet) >> +{ >> + struct service_fixture srv0, srv1; >> +}; > > The "inet" variants are useless and should be removed. The "inet" > fixture can then be renamed to "ipv4_tcp". > Maybe its better to name it "tcp". So we dont need to copy TEST_F(tcp, port_endianness) for ipv6 and ipv4. What do you think? > >> + >> +FIXTURE_VARIANT(inet) >> +{ >> + const bool is_sandboxed; >> + const struct protocol_variant prot; >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { >> + /* clang-format on */ >> + .is_sandboxed = false, >> + .prot = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { >> + /* clang-format on */ >> + .is_sandboxed = true, >> + .prot = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { >> + /* clang-format on */ >> + .is_sandboxed = false, >> + .prot = { >> + .domain = AF_INET6, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +/* clang-format off */ >> +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { >> + /* clang-format on */ >> + .is_sandboxed = true, >> + .prot = { >> + .domain = AF_INET6, >> + .type = SOCK_STREAM, >> + }, >> +}; >> + >> +FIXTURE_SETUP(inet) >> +{ >> + const struct protocol_variant ipv4_tcp = { >> + .domain = AF_INET, >> + .type = SOCK_STREAM, >> + }; >> + >> + disable_caps(_metadata); >> + >> + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); >> + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); >> + >> + setup_loopback(_metadata); >> +}; >> + >> +FIXTURE_TEARDOWN(inet) >> +{ >> +} >> + >> +TEST_F(inet, port_endianness) >> +{ >> + const struct landlock_ruleset_attr ruleset_attr = { >> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + }; >> + const struct landlock_net_service_attr bind_host_endian_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, >> + /* Host port format. */ >> + .port = self->srv0.port, >> + }; >> + const struct landlock_net_service_attr connect_big_endian_p0 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + /* Big endian port format. */ >> + .port = htons(self->srv0.port), >> + }; >> + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { >> + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | >> + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> + /* Host port format. */ >> + .port = self->srv1.port, >> + }; >> + const unsigned int one = 1; >> + const char little_endian = *(const char *)&one; >> + int ruleset_fd; >> + >> + ruleset_fd = >> + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); >> + ASSERT_LE(0, ruleset_fd); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &bind_host_endian_p0, 0)); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &connect_big_endian_p0, 0)); >> + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> + &bind_connect_host_endian_p1, 0)); >> + enforce_ruleset(_metadata, ruleset_fd); >> + >> + /* No restriction for big endinan CPU. */ >> + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); >> + >> + /* No restriction for any CPU. */ >> + test_bind_and_connect(_metadata, &self->srv1, false, false); >> +} >> + >> +TEST_HARNESS_MAIN > .
On Sat, Aug 12, 2023 at 12:03:02AM +0300, Konstantin Meskhidze (A) wrote: > > > 7/6/2023 5:55 PM, Mickaël Salaün пишет: > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > > > This patch is a revamp of the v11 tests [1] with new tests (see the > > "Changes since v11" description). I (Mickaël) only added the following > > todo list and the "Changes since v11" sections in this commit message. > > I think this patch is good but it would appreciate reviews. > > You can find the diff of my changes here but it is not really readable: > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > > TODO: > > - Rename all "net_service" to "net_port". > > - Fix the two kernel bugs found with the new tests. > > - Update this commit message with a small description of all tests. > > > > These test suites try to check edge cases for TCP sockets > > bind() and connect() actions. > > > > inet: > > * bind: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets. > > * connect: Tests with non-landlocked/landlocked ipv4 and ipv6 sockets. > > * bind_afunspec: Tests with non-landlocked/landlocked restrictions > > for bind action with AF_UNSPEC socket family. > > * connect_afunspec: Tests with non-landlocked/landlocked restrictions > > for connect action with AF_UNSPEC socket family. > > * ruleset_overlap: Tests with overlapping rules for one port. > > * ruleset_expanding: Tests with expanding rulesets in which rules are > > gradually added one by one, restricting sockets' connections. > > * inval_port_format: Tests with wrong port format for ipv4/ipv6 sockets > > and with port values more than U16_MAX. > > > > port: > > * inval: Tests with invalid user space supplied data: > > - out of range ruleset attribute; > > - unhandled allowed access; > > - zero port value; > > - zero access value; > > - legitimate access values; > > * bind_connect_inval_addrlen: Tests with invalid address length. > > * bind_connect_unix_*_socket: Tests to make sure unix sockets' actions > > are not restricted by Landlock rules applied to TCP ones. > > > > layout1: > > * with_net: Tests with network bind() socket action within > > filesystem directory access test. > > > > Test coverage for security/landlock is 94.8% of 934 lines according > > to gcc/gcov-11. > > > > Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > Co-developed-by: Mickaël Salaün <mic@digikod.net> > > Signed-off-by: Mickaël Salaün <mic@digikod.net> [...] > > +// Kernel FIXME: tcp_sandbox_with_tcp and tcp_sandbox_with_udp > > I debugged the code in qemu and came to a conclusion that we don't > check if socket's family equals to address's one in check_socket_access(...) > function in net.c > So I added the next lines (marked with !!!): > > static int check_socket_access(struct socket *const sock, > struct sockaddr *const address, > const int addrlen, > const access_mask_t access_request) > { > __be16 port; > layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {}; > const struct landlock_rule *rule; > access_mask_t handled_access; > struct landlock_id id = { > .type = LANDLOCK_KEY_NET_PORT, > }; > const struct landlock_ruleset *const domain = > get_current_net_domain(); > > if (!domain) > return 0; > if (WARN_ON_ONCE(domain->num_layers < 1)) > return -EACCES; > > /* FIXES network tests */ !!! > if (sock->sk->__sk_common.skc_family != address->sa_family) !!! > return 0; !!! > /* Checks if it's a TCP socket. */ > if (sock->type != SOCK_STREAM) > return 0; > ...... > > So now all network tests pass. > What do you think? Good catch, we should indeed check this inconsistency, but this fix also adds two issues: - sa_family is read before checking if it is out of bound (see offsetofend() check bellow). A simple fix is to move down the new check. - access request with AF_UNSPEC on a bind operation doesn't go through the specific AF_UNSPEC handling branch. There are two things to fix here: handle AF_UNSPEC specifically in this new af_family check, and add a new test to make sure bind with AF_UNSPEC and INADDR_ANY is properly restricted. I'll reply to this message with a patch extending your fix. > > > +TEST_F(ipv4, from_unix_to_inet) > > +{ > > + int unix_stream_fd, unix_dgram_fd; > > + > > + if (variant->sandbox == TCP_SANDBOX) { > > + const struct landlock_ruleset_attr ruleset_attr = { > > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > > + }; > > + const struct landlock_net_service_attr tcp_bind_connect_p0 = { > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > > + .port = self->srv0.port, > > + }; > > + int ruleset_fd; > > + > > + /* Denies connect and bind to check errno value. */ > > + ruleset_fd = landlock_create_ruleset(&ruleset_attr, > > + sizeof(ruleset_attr), 0); > > + ASSERT_LE(0, ruleset_fd); > > + > > + /* Allows connect and bind for srv0. */ > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, > > + LANDLOCK_RULE_NET_SERVICE, > > + &tcp_bind_connect_p0, 0)); > > + > > + enforce_ruleset(_metadata, ruleset_fd); > > + EXPECT_EQ(0, close(ruleset_fd)); > > + } > > + > > + unix_stream_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); > > + ASSERT_LE(0, unix_stream_fd); > > + > > + unix_dgram_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); > > Minor mistyping SOCK_STREAM -> SOCK_DGRAM. Good catch! > > > + ASSERT_LE(0, unix_dgram_fd); > > + > > + /* Checks unix stream bind and connect for srv0. */ > > + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv0)); > > + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv0)); > > + > > + /* Checks unix stream bind and connect for srv1. */ > > + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv1)) > > + { > > + TH_LOG("Wrong bind error: %s", strerror(errno)); > > + } > > + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv1)); > > + > > + /* Checks unix datagram bind and connect for srv0. */ > > + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv0)); > > + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv0)); > > + > > + /* Checks unix datagram bind and connect for srv0. */ > > Should be "Checks... for srv1." indeed > > > + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv1)); > > + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv1)); > > +}
On Sun, Aug 13, 2023 at 11:09:59PM +0300, Konstantin Meskhidze (A) wrote: > > > 7/12/2023 10:02 AM, Mickaël Salaün пишет: > > > > On 06/07/2023 16:55, Mickaël Salaün wrote: > > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > > > > > This patch is a revamp of the v11 tests [1] with new tests (see the > > > "Changes since v11" description). I (Mickaël) only added the following > > > todo list and the "Changes since v11" sections in this commit message. > > > I think this patch is good but it would appreciate reviews. > > > You can find the diff of my changes here but it is not really readable: > > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > > > TODO: > > > - Rename all "net_service" to "net_port". > > > - Fix the two kernel bugs found with the new tests. > > > - Update this commit message with a small description of all tests. > > > > [...] > > > +FIXTURE(inet) > > > +{ > > > + struct service_fixture srv0, srv1; > > > +}; > > > > The "inet" variants are useless and should be removed. The "inet" > > fixture can then be renamed to "ipv4_tcp". > > > Maybe its better to name it "tcp". So we dont need to copy TEST_F(tcp, > port_endianness) for ipv6 and ipv4. > What do you think? I don't see any need to test with IPv4 and IPv6, hence the "inet" name (and without variants). You can rename it to "inet_tcp" to highlight the specificities of this fixture. > > > > > > + > > > +FIXTURE_VARIANT(inet) > > > +{ > > > + const bool is_sandboxed; > > > + const struct protocol_variant prot; > > > +}; > > > + > > > +/* clang-format off */ > > > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { > > > + /* clang-format on */ > > > + .is_sandboxed = false, > > > + .prot = { > > > + .domain = AF_INET, > > > + .type = SOCK_STREAM, > > > + }, > > > +}; > > > + > > > +/* clang-format off */ > > > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { > > > + /* clang-format on */ > > > + .is_sandboxed = true, > > > + .prot = { > > > + .domain = AF_INET, > > > + .type = SOCK_STREAM, > > > + }, > > > +}; > > > + > > > +/* clang-format off */ > > > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { > > > + /* clang-format on */ > > > + .is_sandboxed = false, > > > + .prot = { > > > + .domain = AF_INET6, > > > + .type = SOCK_STREAM, > > > + }, > > > +}; > > > + > > > +/* clang-format off */ > > > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { > > > + /* clang-format on */ > > > + .is_sandboxed = true, > > > + .prot = { > > > + .domain = AF_INET6, > > > + .type = SOCK_STREAM, > > > + }, > > > +}; > > > + > > > +FIXTURE_SETUP(inet) > > > +{ > > > + const struct protocol_variant ipv4_tcp = { > > > + .domain = AF_INET, > > > + .type = SOCK_STREAM, > > > + }; > > > + > > > + disable_caps(_metadata); > > > + > > > + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); > > > + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); > > > + > > > + setup_loopback(_metadata); > > > +}; > > > + > > > +FIXTURE_TEARDOWN(inet) > > > +{ > > > +} > > > + > > > +TEST_F(inet, port_endianness) > > > +{ > > > + const struct landlock_ruleset_attr ruleset_attr = { > > > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > > > + }; > > > + const struct landlock_net_service_attr bind_host_endian_p0 = { > > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > > > + /* Host port format. */ > > > + .port = self->srv0.port, > > > + }; > > > + const struct landlock_net_service_attr connect_big_endian_p0 = { > > > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, > > > + /* Big endian port format. */ > > > + .port = htons(self->srv0.port), > > > + }; > > > + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { > > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > > > + /* Host port format. */ > > > + .port = self->srv1.port, > > > + }; > > > + const unsigned int one = 1; > > > + const char little_endian = *(const char *)&one; > > > + int ruleset_fd; > > > + > > > + ruleset_fd = > > > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); > > > + ASSERT_LE(0, ruleset_fd); > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > > > + &bind_host_endian_p0, 0)); > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > > > + &connect_big_endian_p0, 0)); > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > > > + &bind_connect_host_endian_p1, 0)); > > > + enforce_ruleset(_metadata, ruleset_fd); > > > + > > > + /* No restriction for big endinan CPU. */ > > > + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); > > > + > > > + /* No restriction for any CPU. */ > > > + test_bind_and_connect(_metadata, &self->srv1, false, false); > > > +} > > > + > > > +TEST_HARNESS_MAIN > > .
8/17/2023 4:19 PM, Mickaël Salaün пишет: > On Sun, Aug 13, 2023 at 11:09:59PM +0300, Konstantin Meskhidze (A) wrote: >> >> >> 7/12/2023 10:02 AM, Mickaël Salaün пишет: >> > >> > On 06/07/2023 16:55, Mickaël Salaün wrote: >> > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> > > >> > > This patch is a revamp of the v11 tests [1] with new tests (see the >> > > "Changes since v11" description). I (Mickaël) only added the following >> > > todo list and the "Changes since v11" sections in this commit message. >> > > I think this patch is good but it would appreciate reviews. >> > > You can find the diff of my changes here but it is not really readable: >> > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> > > TODO: >> > > - Rename all "net_service" to "net_port". >> > > - Fix the two kernel bugs found with the new tests. >> > > - Update this commit message with a small description of all tests. >> > >> > [...] > >> > > +FIXTURE(inet) >> > > +{ >> > > + struct service_fixture srv0, srv1; >> > > +}; >> > >> > The "inet" variants are useless and should be removed. The "inet" >> > fixture can then be renamed to "ipv4_tcp". >> > >> Maybe its better to name it "tcp". So we dont need to copy TEST_F(tcp, >> port_endianness) for ipv6 and ipv4. >> What do you think? > > I don't see any need to test with IPv4 and IPv6, hence the "inet" name > (and without variants). You can rename it to "inet_tcp" to highlight the > specificities of this fixture. > I think there was some misunderstanding from my side. So I will rename inet to inet_tcp and keep all fixture variants: - no_sandbox_with_ipv4. - sandbox_with_ipv4. - no_sandbox_with_ipv6. - sandbox_with_ipv6. Correct? >> >> > >> > > + >> > > +FIXTURE_VARIANT(inet) >> > > +{ >> > > + const bool is_sandboxed; >> > > + const struct protocol_variant prot; >> > > +}; >> > > + >> > > +/* clang-format off */ >> > > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { >> > > + /* clang-format on */ >> > > + .is_sandboxed = false, >> > > + .prot = { >> > > + .domain = AF_INET, >> > > + .type = SOCK_STREAM, >> > > + }, >> > > +}; >> > > + >> > > +/* clang-format off */ >> > > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { >> > > + /* clang-format on */ >> > > + .is_sandboxed = true, >> > > + .prot = { >> > > + .domain = AF_INET, >> > > + .type = SOCK_STREAM, >> > > + }, >> > > +}; >> > > + >> > > +/* clang-format off */ >> > > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { >> > > + /* clang-format on */ >> > > + .is_sandboxed = false, >> > > + .prot = { >> > > + .domain = AF_INET6, >> > > + .type = SOCK_STREAM, >> > > + }, >> > > +}; >> > > + >> > > +/* clang-format off */ >> > > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { >> > > + /* clang-format on */ >> > > + .is_sandboxed = true, >> > > + .prot = { >> > > + .domain = AF_INET6, >> > > + .type = SOCK_STREAM, >> > > + }, >> > > +}; >> > > + >> > > +FIXTURE_SETUP(inet) >> > > +{ >> > > + const struct protocol_variant ipv4_tcp = { >> > > + .domain = AF_INET, >> > > + .type = SOCK_STREAM, >> > > + }; >> > > + >> > > + disable_caps(_metadata); >> > > + >> > > + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); >> > > + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); >> > > + >> > > + setup_loopback(_metadata); >> > > +}; >> > > + >> > > +FIXTURE_TEARDOWN(inet) >> > > +{ >> > > +} >> > > + >> > > +TEST_F(inet, port_endianness) >> > > +{ >> > > + const struct landlock_ruleset_attr ruleset_attr = { >> > > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | >> > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> > > + }; >> > > + const struct landlock_net_service_attr bind_host_endian_p0 = { >> > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, >> > > + /* Host port format. */ >> > > + .port = self->srv0.port, >> > > + }; >> > > + const struct landlock_net_service_attr connect_big_endian_p0 = { >> > > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, >> > > + /* Big endian port format. */ >> > > + .port = htons(self->srv0.port), >> > > + }; >> > > + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { >> > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | >> > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> > > + /* Host port format. */ >> > > + .port = self->srv1.port, >> > > + }; >> > > + const unsigned int one = 1; >> > > + const char little_endian = *(const char *)&one; >> > > + int ruleset_fd; >> > > + >> > > + ruleset_fd = >> > > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); >> > > + ASSERT_LE(0, ruleset_fd); >> > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> > > + &bind_host_endian_p0, 0)); >> > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> > > + &connect_big_endian_p0, 0)); >> > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> > > + &bind_connect_host_endian_p1, 0)); >> > > + enforce_ruleset(_metadata, ruleset_fd); >> > > + >> > > + /* No restriction for big endinan CPU. */ >> > > + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); >> > > + >> > > + /* No restriction for any CPU. */ >> > > + test_bind_and_connect(_metadata, &self->srv1, false, false); >> > > +} >> > > + >> > > +TEST_HARNESS_MAIN >> > . > .
On Sat, Aug 12, 2023 at 05:37:00PM +0300, Konstantin Meskhidze (A) wrote: > > > 7/12/2023 10:02 AM, Mickaël Salaün пишет: > > > > On 06/07/2023 16:55, Mickaël Salaün wrote: > > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > > > > > This patch is a revamp of the v11 tests [1] with new tests (see the > > > "Changes since v11" description). I (Mickaël) only added the following > > > todo list and the "Changes since v11" sections in this commit message. > > > I think this patch is good but it would appreciate reviews. > > > You can find the diff of my changes here but it is not really readable: > > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > > > TODO: > > > - Rename all "net_service" to "net_port". > > > - Fix the two kernel bugs found with the new tests. > > > - Update this commit message with a small description of all tests. > > > > [...] > > We should also add a test to make sure errno is the same with and > > without sandboxing when using port 0 for connect and consistent with > > bind (using an available port). The test fixture and variants should be > > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, > > which will result in 8 "ip" variants: > > > > TEST_F(ip, port_zero) > > { > > if (variant->sandbox == TCP_SANDBOX) { > > /* Denies any connect and bind. */ > > } > > /* Checks errno for port 0. */ > > } > As I understand the would be the next test cases: > > 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by > landlock). Without any allowed port, yes. This test case is useful. By tuning /proc/sys/net/ipv4/ip_local_port_range (see inet_csk_find_open_port call) we should be able to pick a specific allowed port and test it. We can also test for the EADDRINUSE error to make sure error ordering is correct (compared with -EACCES). However, I think the current LSM API don't enable to infer this random port because the LSM hook is called before a port is picked. If this is correct, the best way to control port binding would be to always deny binding on port zero/random (when restricting port binding, whatever exception rules are in place). This explanation should be part of a comment for this specific exception. Cc Paul > 2. ip4, non-sandboxed, bind port 0 -> should return 0 (should be bounded to > random port). I think so but we need to make sure the random port cannot be < 1024, I guess with /proc/sys/net/ipv4/ip_local_port_range but I don't know for IPv6. > 3. ip6, sandboxed, bind port 0 -> should return EACCES (denied by > landlock). > 4. ip6, non-sandboxed, bind port 0 -> should return 0 (should be bounded to > random port). > 5. ip4, sandboxed, bind some available port, connect port 0 -> should > return -EACCES (denied by landlock). Yes, but don't need to bind to anything (same for the next ones). > 6. ip4, non-sandboxed, bind some available port, connect port 0 -> should > return ECONNREFUSED. Yes, but without any binding. > 7. ip6, sandboxed, bind some available port, connect port 0 -> should > return -EACCES (denied by landlock) > 8. ip6, non-sandboxed, some bind available port, connect port 0 -> should > return ECONNREFUSED. > > Correct? Thinking more about this case, being able to add a rule with port zero *for a connect action* looks legitimate. A rule with both connect and bind actions on port zero should then be denied. We should fix add_rule_net_service() and test that (with a first layer allowing port zero, and a second without rule, for connect). > > > > > [...] > > > > > +FIXTURE(inet) > > > +{ > > > + struct service_fixture srv0, srv1; > > > +}; > > > > The "inet" variants are useless and should be removed. The "inet" > > fixture can then be renamed to "ipv4_tcp". > > > So inet should be changed to ipv4_tcp and ipv6_tcp with next variants: > > - ipv4_tcp.no_sandbox_with_ipv4.port_endianness > - ipv4_tcp.sandbox_with_ipv4.port_endianness > - ipv6_tcp.no_sandbox_with_ipv6.port_endianness > - ipv6_tcp.sandbox_with_ipv6.port_endianness > ???? > > in this case we need double copy of TEST_F(inet, port_endianness) : > TEST_F(ipv4_tcp, port_endianness) > TEST_F(ipv6_tcp, port_endianness) There is no need for any variant for the port_endianness test. You can rename "inet" to "ipv4_tcp" (and not "inet_tcp" like I said before).
On Thu, Aug 17, 2023 at 05:04:00PM +0300, Konstantin Meskhidze (A) wrote: > > > 8/17/2023 4:19 PM, Mickaël Salaün пишет: > > On Sun, Aug 13, 2023 at 11:09:59PM +0300, Konstantin Meskhidze (A) wrote: > > > > > > > > > 7/12/2023 10:02 AM, Mickaël Salaün пишет: > > > > > On 06/07/2023 16:55, Mickaël Salaün wrote: > > > > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > > > > > > This patch is a revamp of the v11 tests [1] with new tests > > > (see the > > > > > "Changes since v11" description). I (Mickaël) only added the following > > > > > todo list and the "Changes since v11" sections in this commit message. > > > > > I think this patch is good but it would appreciate reviews. > > > > > You can find the diff of my changes here but it is not really readable: > > > > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > > > > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > > > > > TODO: > > > > > - Rename all "net_service" to "net_port". > > > > > - Fix the two kernel bugs found with the new tests. > > > > > - Update this commit message with a small description of all tests. > > > > > [...] > > > > > > > +FIXTURE(inet) > > > > > +{ > > > > > + struct service_fixture srv0, srv1; > > > > > +}; > > > > > The "inet" variants are useless and should be removed. The > > > "inet" > > > > fixture can then be renamed to "ipv4_tcp". > > > > Maybe its better to name it "tcp". So we dont need to copy > > > TEST_F(tcp, > > > port_endianness) for ipv6 and ipv4. > > > What do you think? > > > > I don't see any need to test with IPv4 and IPv6, hence the "inet" name > > (and without variants). You can rename it to "inet_tcp" to highlight the > > specificities of this fixture. > > > > I think there was some misunderstanding from my side. So I will rename > inet to inet_tcp and keep all fixture variants: > - no_sandbox_with_ipv4. > - sandbox_with_ipv4. > - no_sandbox_with_ipv6. > - sandbox_with_ipv6. > Correct? No, you just need to remove the FIXTURE_VARIANT and the four FIXTURE_VARIANT_ADD blocks bellow. And according to another reply, "ipv4_tcp" seems more appropriate. > > > > > > + > > > > > +FIXTURE_VARIANT(inet) > > > > > +{ > > > > > + const bool is_sandboxed; > > > > > + const struct protocol_variant prot; > > > > > +}; > > > > > + > > > > > +/* clang-format off */ > > > > > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { > > > > > + /* clang-format on */ > > > > > + .is_sandboxed = false, > > > > > + .prot = { > > > > > + .domain = AF_INET, > > > > > + .type = SOCK_STREAM, > > > > > + }, > > > > > +}; > > > > > + > > > > > +/* clang-format off */ > > > > > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { > > > > > + /* clang-format on */ > > > > > + .is_sandboxed = true, > > > > > + .prot = { > > > > > + .domain = AF_INET, > > > > > + .type = SOCK_STREAM, > > > > > + }, > > > > > +}; > > > > > + > > > > > +/* clang-format off */ > > > > > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { > > > > > + /* clang-format on */ > > > > > + .is_sandboxed = false, > > > > > + .prot = { > > > > > + .domain = AF_INET6, > > > > > + .type = SOCK_STREAM, > > > > > + }, > > > > > +}; > > > > > + > > > > > +/* clang-format off */ > > > > > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { > > > > > + /* clang-format on */ > > > > > + .is_sandboxed = true, > > > > > + .prot = { > > > > > + .domain = AF_INET6, > > > > > + .type = SOCK_STREAM, > > > > > + }, > > > > > +}; > > > > > + > > > > > +FIXTURE_SETUP(inet) > > > > > +{ > > > > > + const struct protocol_variant ipv4_tcp = { > > > > > + .domain = AF_INET, > > > > > + .type = SOCK_STREAM, > > > > > + }; > > > > > + > > > > > + disable_caps(_metadata); > > > > > + > > > > > + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); > > > > > + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); > > > > > + > > > > > + setup_loopback(_metadata); > > > > > +}; > > > > > + > > > > > +FIXTURE_TEARDOWN(inet) > > > > > +{ > > > > > +} > > > > > + > > > > > +TEST_F(inet, port_endianness) > > > > > +{ > > > > > + const struct landlock_ruleset_attr ruleset_attr = { > > > > > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | > > > > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > > > > > + }; > > > > > + const struct landlock_net_service_attr bind_host_endian_p0 = { > > > > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, > > > > > + /* Host port format. */ > > > > > + .port = self->srv0.port, > > > > > + }; > > > > > + const struct landlock_net_service_attr connect_big_endian_p0 = { > > > > > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, > > > > > + /* Big endian port format. */ > > > > > + .port = htons(self->srv0.port), > > > > > + }; > > > > > + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { > > > > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | > > > > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, > > > > > + /* Host port format. */ > > > > > + .port = self->srv1.port, > > > > > + }; > > > > > + const unsigned int one = 1; > > > > > + const char little_endian = *(const char *)&one; > > > > > + int ruleset_fd; > > > > > + > > > > > + ruleset_fd = > > > > > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); > > > > > + ASSERT_LE(0, ruleset_fd); > > > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > > > > > + &bind_host_endian_p0, 0)); > > > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > > > > > + &connect_big_endian_p0, 0)); > > > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, > > > > > + &bind_connect_host_endian_p1, 0)); > > > > > + enforce_ruleset(_metadata, ruleset_fd); > > > > > + > > > > > + /* No restriction for big endinan CPU. */ > > > > > + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); > > > > > + > > > > > + /* No restriction for any CPU. */ > > > > > + test_bind_and_connect(_metadata, &self->srv1, false, false); > > > > > +} > > > > > + > > > > > +TEST_HARNESS_MAIN > > > > . > > .
8/17/2023 6:34 PM, Mickaël Salaün пишет: > On Thu, Aug 17, 2023 at 05:04:00PM +0300, Konstantin Meskhidze (A) wrote: >> >> >> 8/17/2023 4:19 PM, Mickaël Salaün пишет: >> > On Sun, Aug 13, 2023 at 11:09:59PM +0300, Konstantin Meskhidze (A) wrote: >> > > >> > > >> > > 7/12/2023 10:02 AM, Mickaël Salaün пишет: >> > > > > On 06/07/2023 16:55, Mickaël Salaün wrote: >> > > > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> > > > > > > This patch is a revamp of the v11 tests [1] with new tests >> > > (see the >> > > > > "Changes since v11" description). I (Mickaël) only added the following >> > > > > todo list and the "Changes since v11" sections in this commit message. >> > > > > I think this patch is good but it would appreciate reviews. >> > > > > You can find the diff of my changes here but it is not really readable: >> > > > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> > > > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> > > > > TODO: >> > > > > - Rename all "net_service" to "net_port". >> > > > > - Fix the two kernel bugs found with the new tests. >> > > > > - Update this commit message with a small description of all tests. >> > > > > [...] >> > >> > > > > +FIXTURE(inet) >> > > > > +{ >> > > > > + struct service_fixture srv0, srv1; >> > > > > +}; >> > > > > The "inet" variants are useless and should be removed. The >> > > "inet" >> > > > fixture can then be renamed to "ipv4_tcp". >> > > > Maybe its better to name it "tcp". So we dont need to copy >> > > TEST_F(tcp, >> > > port_endianness) for ipv6 and ipv4. >> > > What do you think? >> > >> > I don't see any need to test with IPv4 and IPv6, hence the "inet" name >> > (and without variants). You can rename it to "inet_tcp" to highlight the >> > specificities of this fixture. >> > >> >> I think there was some misunderstanding from my side. So I will rename >> inet to inet_tcp and keep all fixture variants: >> - no_sandbox_with_ipv4. >> - sandbox_with_ipv4. >> - no_sandbox_with_ipv6. >> - sandbox_with_ipv6. >> Correct? > > No, you just need to remove the FIXTURE_VARIANT and the four > FIXTURE_VARIANT_ADD blocks bellow. And according to another reply, > "ipv4_tcp" seems more appropriate. > Ok. Got it. Thank you. > >> > > > > > + >> > > > > +FIXTURE_VARIANT(inet) >> > > > > +{ >> > > > > + const bool is_sandboxed; >> > > > > + const struct protocol_variant prot; >> > > > > +}; >> > > > > + >> > > > > +/* clang-format off */ >> > > > > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { >> > > > > + /* clang-format on */ >> > > > > + .is_sandboxed = false, >> > > > > + .prot = { >> > > > > + .domain = AF_INET, >> > > > > + .type = SOCK_STREAM, >> > > > > + }, >> > > > > +}; >> > > > > + >> > > > > +/* clang-format off */ >> > > > > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { >> > > > > + /* clang-format on */ >> > > > > + .is_sandboxed = true, >> > > > > + .prot = { >> > > > > + .domain = AF_INET, >> > > > > + .type = SOCK_STREAM, >> > > > > + }, >> > > > > +}; >> > > > > + >> > > > > +/* clang-format off */ >> > > > > +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { >> > > > > + /* clang-format on */ >> > > > > + .is_sandboxed = false, >> > > > > + .prot = { >> > > > > + .domain = AF_INET6, >> > > > > + .type = SOCK_STREAM, >> > > > > + }, >> > > > > +}; >> > > > > + >> > > > > +/* clang-format off */ >> > > > > +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { >> > > > > + /* clang-format on */ >> > > > > + .is_sandboxed = true, >> > > > > + .prot = { >> > > > > + .domain = AF_INET6, >> > > > > + .type = SOCK_STREAM, >> > > > > + }, >> > > > > +}; >> > > > > + >> > > > > +FIXTURE_SETUP(inet) >> > > > > +{ >> > > > > + const struct protocol_variant ipv4_tcp = { >> > > > > + .domain = AF_INET, >> > > > > + .type = SOCK_STREAM, >> > > > > + }; >> > > > > + >> > > > > + disable_caps(_metadata); >> > > > > + >> > > > > + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); >> > > > > + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); >> > > > > + >> > > > > + setup_loopback(_metadata); >> > > > > +}; >> > > > > + >> > > > > +FIXTURE_TEARDOWN(inet) >> > > > > +{ >> > > > > +} >> > > > > + >> > > > > +TEST_F(inet, port_endianness) >> > > > > +{ >> > > > > + const struct landlock_ruleset_attr ruleset_attr = { >> > > > > + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | >> > > > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> > > > > + }; >> > > > > + const struct landlock_net_service_attr bind_host_endian_p0 = { >> > > > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, >> > > > > + /* Host port format. */ >> > > > > + .port = self->srv0.port, >> > > > > + }; >> > > > > + const struct landlock_net_service_attr connect_big_endian_p0 = { >> > > > > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, >> > > > > + /* Big endian port format. */ >> > > > > + .port = htons(self->srv0.port), >> > > > > + }; >> > > > > + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { >> > > > > + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | >> > > > > + LANDLOCK_ACCESS_NET_CONNECT_TCP, >> > > > > + /* Host port format. */ >> > > > > + .port = self->srv1.port, >> > > > > + }; >> > > > > + const unsigned int one = 1; >> > > > > + const char little_endian = *(const char *)&one; >> > > > > + int ruleset_fd; >> > > > > + >> > > > > + ruleset_fd = >> > > > > + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); >> > > > > + ASSERT_LE(0, ruleset_fd); >> > > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> > > > > + &bind_host_endian_p0, 0)); >> > > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> > > > > + &connect_big_endian_p0, 0)); >> > > > > + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, >> > > > > + &bind_connect_host_endian_p1, 0)); >> > > > > + enforce_ruleset(_metadata, ruleset_fd); >> > > > > + >> > > > > + /* No restriction for big endinan CPU. */ >> > > > > + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); >> > > > > + >> > > > > + /* No restriction for any CPU. */ >> > > > > + test_bind_and_connect(_metadata, &self->srv1, false, false); >> > > > > +} >> > > > > + >> > > > > +TEST_HARNESS_MAIN >> > > > . >> > . > .
8/17/2023 6:08 PM, Mickaël Salaün пишет: > On Sat, Aug 12, 2023 at 05:37:00PM +0300, Konstantin Meskhidze (A) wrote: >> >> >> 7/12/2023 10:02 AM, Mickaël Salaün пишет: >> > >> > On 06/07/2023 16:55, Mickaël Salaün wrote: >> > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> > > >> > > This patch is a revamp of the v11 tests [1] with new tests (see the >> > > "Changes since v11" description). I (Mickaël) only added the following >> > > todo list and the "Changes since v11" sections in this commit message. >> > > I think this patch is good but it would appreciate reviews. >> > > You can find the diff of my changes here but it is not really readable: >> > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> > > TODO: >> > > - Rename all "net_service" to "net_port". >> > > - Fix the two kernel bugs found with the new tests. >> > > - Update this commit message with a small description of all tests. >> > >> > [...] > >> > We should also add a test to make sure errno is the same with and >> > without sandboxing when using port 0 for connect and consistent with >> > bind (using an available port). The test fixture and variants should be >> > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, >> > which will result in 8 "ip" variants: >> > >> > TEST_F(ip, port_zero) >> > { >> > if (variant->sandbox == TCP_SANDBOX) { >> > /* Denies any connect and bind. */ >> > } >> > /* Checks errno for port 0. */ >> > } >> As I understand the would be the next test cases: >> >> 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by >> landlock). > > Without any allowed port, yes. This test case is useful. > > By tuning /proc/sys/net/ipv4/ip_local_port_range (see > inet_csk_find_open_port call) we should be able to pick a specific > allowed port and test it. We can also test for the EADDRINUSE error to > make sure error ordering is correct (compared with -EACCES). Sorry, did not get this case. Could please explain it with more details? > > However, I think the current LSM API don't enable to infer this random > port because the LSM hook is called before a port is picked. If this is > correct, the best way to control port binding would be to always deny > binding on port zero/random (when restricting port binding, whatever > exception rules are in place). This explanation should be part of a > comment for this specific exception. Yep, if some LSM rule (for bind) has been applied a with specific port, other attemps to bind with zero/random ports would be refused by LSM security checks. > > Cc Paul > >> 2. ip4, non-sandboxed, bind port 0 -> should return 0 (should be bounded to >> random port). > > I think so but we need to make sure the random port cannot be < 1024, I > guess with /proc/sys/net/ipv4/ip_local_port_range but I don't know for > IPv6. For ipv4 when connecting to a server a client binds to a random port within /proc/sys/net/ipv4/ip_local_port_range, by default one my machine this range is: cat /proc/sys/net/ipv4/ip_local_port_range 32768 60999. But for ipv6 there is no such tuning range. > >> 3. ip6, sandboxed, bind port 0 -> should return EACCES (denied by >> landlock). >> 4. ip6, non-sandboxed, bind port 0 -> should return 0 (should be bounded to >> random port). >> 5. ip4, sandboxed, bind some available port, connect port 0 -> should >> return -EACCES (denied by landlock). > > Yes, but don't need to bind to anything (same for the next ones). > >> 6. ip4, non-sandboxed, bind some available port, connect port 0 -> should >> return ECONNREFUSED. > > Yes, but without any binding. > >> 7. ip6, sandboxed, bind some available port, connect port 0 -> should >> return -EACCES (denied by landlock) >> 8. ip6, non-sandboxed, some bind available port, connect port 0 -> should >> return ECONNREFUSED. >> >> Correct? > > Thinking more about this case, being able to add a rule with port zero > *for a connect action* looks legitimate. A rule with both connect and > bind actions on port zero should then be denied. We should fix > add_rule_net_service() and test that (with a first layer allowing port > zero, and a second without rule, for connect). So with first rule allowing port 0 connect action, the second rule with some another port and connect action, as a result test should allow that. Correct? > > >> >> > >> > [...] >> > >> > > +FIXTURE(inet) >> > > +{ >> > > + struct service_fixture srv0, srv1; >> > > +}; >> > >> > The "inet" variants are useless and should be removed. The "inet" >> > fixture can then be renamed to "ipv4_tcp". >> > >> So inet should be changed to ipv4_tcp and ipv6_tcp with next variants: >> >> - ipv4_tcp.no_sandbox_with_ipv4.port_endianness >> - ipv4_tcp.sandbox_with_ipv4.port_endianness >> - ipv6_tcp.no_sandbox_with_ipv6.port_endianness >> - ipv6_tcp.sandbox_with_ipv6.port_endianness >> ???? >> >> in this case we need double copy of TEST_F(inet, port_endianness) : >> TEST_F(ipv4_tcp, port_endianness) >> TEST_F(ipv6_tcp, port_endianness) > > There is no need for any variant for the port_endianness test. You can > rename "inet" to "ipv4_tcp" (and not "inet_tcp" like I said before). > .
On Mon, Sep 11, 2023 at 01:13:24PM +0300, Konstantin Meskhidze (A) wrote: > > > 8/17/2023 6:08 PM, Mickaël Salaün пишет: > > On Sat, Aug 12, 2023 at 05:37:00PM +0300, Konstantin Meskhidze (A) wrote: > > > > > > > > > 7/12/2023 10:02 AM, Mickaël Salaün пишет: > > > > > On 06/07/2023 16:55, Mickaël Salaün wrote: > > > > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > > > > > > This patch is a revamp of the v11 tests [1] with new tests > > > (see the > > > > > "Changes since v11" description). I (Mickaël) only added the following > > > > > todo list and the "Changes since v11" sections in this commit message. > > > > > I think this patch is good but it would appreciate reviews. > > > > > You can find the diff of my changes here but it is not really readable: > > > > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > > > > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > > > > > TODO: > > > > > - Rename all "net_service" to "net_port". > > > > > - Fix the two kernel bugs found with the new tests. > > > > > - Update this commit message with a small description of all tests. > > > > > [...] > > > > > > We should also add a test to make sure errno is the same with and > > > > without sandboxing when using port 0 for connect and consistent with > > > > bind (using an available port). The test fixture and variants should be > > > > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, > > > > which will result in 8 "ip" variants: > > > > > TEST_F(ip, port_zero) > > > > { > > > > if (variant->sandbox == TCP_SANDBOX) { > > > > /* Denies any connect and bind. */ > > > > } > > > > /* Checks errno for port 0. */ > > > > } > > > As I understand the would be the next test cases: > > > > > > 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by > > > landlock). > > > > Without any allowed port, yes. This test case is useful. > > > > By tuning /proc/sys/net/ipv4/ip_local_port_range (see > > inet_csk_find_open_port call) we should be able to pick a specific > > allowed port and test it. We can also test for the EADDRINUSE error to > > make sure error ordering is correct (compared with -EACCES). > Sorry, did not get this case. Could please explain it with more details? According to bind(2), if no port are available, the syscall should return EADDRINUSE. And this returned value should be the same whatever the process is sandbox or not (and never EACCES). But as I explained just below, we cannot know this random port from the LSM hook, so no need to tweak /proc/sys/net/ipv4/ip_local_port_range, and your this is correct: 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by landlock). > > > > However, I think the current LSM API don't enable to infer this random > > port because the LSM hook is called before a port is picked. If this is > > correct, the best way to control port binding would be to always deny > > binding on port zero/random (when restricting port binding, whatever > > exception rules are in place). This explanation should be part of a > > comment for this specific exception. > > Yep, if some LSM rule (for bind) has been applied a with specific port, > other attemps to bind with zero/random ports would be refused by LSM > security checks. To say it another way, we should not allow to add a rule with port 0 for LANDLOCK_ACCESS_NET_BIND_TCP, but return -EINVAL in this case. This limitation should be explained, documented and tested. With (only) LANDLOCK_ACCESS_NET_CONNECT_TCP it should be allowed though (except if there is also LANDLOCK_ACCESS_NET_BIND_TCP) of course. Another test should cover the case with a new rule with these two access rights and port 0. > > > > Cc Paul > > > > > 2. ip4, non-sandboxed, bind port 0 -> should return 0 (should be bounded to > > > random port). > > > > I think so but we need to make sure the random port cannot be < 1024, I > > guess with /proc/sys/net/ipv4/ip_local_port_range but I don't know for > > IPv6. > > For ipv4 when connecting to a server a client binds to a random port > within /proc/sys/net/ipv4/ip_local_port_range, by default one my machine > this range is: cat /proc/sys/net/ipv4/ip_local_port_range > 32768 60999. > But for ipv6 there is no such tuning range. Ok, let's just assume that the test system doesn't have ip_local_port_range < 1024, put this assumption in a comment, and don't touch ip_local_port_range at all. > > > > > > 3. ip6, sandboxed, bind port 0 -> should return EACCES (denied by > > > landlock). > > > 4. ip6, non-sandboxed, bind port 0 -> should return 0 (should be bounded to > > > random port). > > > 5. ip4, sandboxed, bind some available port, connect port 0 -> should > > > return -EACCES (denied by landlock). If a rule allows connecting to port 0, then it should be ECONNREFUSED, otherwise EACCES indeed. Both cases should be tested. > > > > Yes, but don't need to bind to anything (same for the next ones). > > > > > 6. ip4, non-sandboxed, bind some available port, connect port 0 -> should > > > return ECONNREFUSED. > > > > Yes, but without any binding. > > > > > 7. ip6, sandboxed, bind some available port, connect port 0 -> should > > > return -EACCES (denied by landlock) > > > 8. ip6, non-sandboxed, some bind available port, connect port 0 -> should > > > return ECONNREFUSED. > > > > > > Correct? > > > > Thinking more about this case, being able to add a rule with port zero > > *for a connect action* looks legitimate. A rule with both connect and > > bind actions on port zero should then be denied. We should fix > > add_rule_net_service() and test that (with a first layer allowing port > > zero, and a second without rule, for connect). > > So with first rule allowing port 0 connect action, the second rule with > some another port and connect action, Yes, but the first rule being part of a first layer/restriction, and the second rule part of a second layer. > as a result test should allow that. > Correct? The first layer should return ECONNREFUSED when connecting on port 0 (allowed but nothing listening), and once the second layer is enforced, EACCES should be returned on port 0. > > > > > > > > > > > > [...] > > > > > > +FIXTURE(inet) > > > > > +{ > > > > > + struct service_fixture srv0, srv1; > > > > > +}; > > > > > The "inet" variants are useless and should be removed. The > > > "inet" > > > > fixture can then be renamed to "ipv4_tcp". > > > > So inet should be changed to ipv4_tcp and ipv6_tcp with next > > > variants: > > > > > > - ipv4_tcp.no_sandbox_with_ipv4.port_endianness > > > - ipv4_tcp.sandbox_with_ipv4.port_endianness > > > - ipv6_tcp.no_sandbox_with_ipv6.port_endianness > > > - ipv6_tcp.sandbox_with_ipv6.port_endianness > > > ???? > > > > > > in this case we need double copy of TEST_F(inet, port_endianness) : > > > TEST_F(ipv4_tcp, port_endianness) > > > TEST_F(ipv6_tcp, port_endianness) > > > > There is no need for any variant for the port_endianness test. You can > > rename "inet" to "ipv4_tcp" (and not "inet_tcp" like I said before). > > .
9/14/2023 11:08 AM, Mickaël Salaün пишет: > On Mon, Sep 11, 2023 at 01:13:24PM +0300, Konstantin Meskhidze (A) wrote: >> >> >> 8/17/2023 6:08 PM, Mickaël Salaün пишет: >> > On Sat, Aug 12, 2023 at 05:37:00PM +0300, Konstantin Meskhidze (A) wrote: >> > > >> > > >> > > 7/12/2023 10:02 AM, Mickaël Salaün пишет: >> > > > > On 06/07/2023 16:55, Mickaël Salaün wrote: >> > > > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> > > > > > > This patch is a revamp of the v11 tests [1] with new tests >> > > (see the >> > > > > "Changes since v11" description). I (Mickaël) only added the following >> > > > > todo list and the "Changes since v11" sections in this commit message. >> > > > > I think this patch is good but it would appreciate reviews. >> > > > > You can find the diff of my changes here but it is not really readable: >> > > > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> > > > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> > > > > TODO: >> > > > > - Rename all "net_service" to "net_port". >> > > > > - Fix the two kernel bugs found with the new tests. >> > > > > - Update this commit message with a small description of all tests. >> > > > > [...] >> > >> > > > We should also add a test to make sure errno is the same with and >> > > > without sandboxing when using port 0 for connect and consistent with >> > > > bind (using an available port). The test fixture and variants should be >> > > > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, >> > > > which will result in 8 "ip" variants: >> > > > > TEST_F(ip, port_zero) >> > > > { >> > > > if (variant->sandbox == TCP_SANDBOX) { >> > > > /* Denies any connect and bind. */ >> > > > } >> > > > /* Checks errno for port 0. */ >> > > > } >> > > As I understand the would be the next test cases: >> > > >> > > 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by >> > > landlock). >> > >> > Without any allowed port, yes. This test case is useful. >> > >> > By tuning /proc/sys/net/ipv4/ip_local_port_range (see >> > inet_csk_find_open_port call) we should be able to pick a specific >> > allowed port and test it. We can also test for the EADDRINUSE error to >> > make sure error ordering is correct (compared with -EACCES). >> Sorry, did not get this case. Could please explain it with more details? > > According to bind(2), if no port are available, the syscall should > return EADDRINUSE. And this returned value should be the same whatever > the process is sandbox or not (and never EACCES). But as I explained > just below, we cannot know this random port from the LSM hook, so no > need to tweak /proc/sys/net/ipv4/ip_local_port_range, and your this is > correct: > > 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by > landlock). yep, adding rule with port 0 (for bind) returns EINVAL then calling bind port 0 returns EACCES cause there is no rule with port 0. > >> > >> > However, I think the current LSM API don't enable to infer this random >> > port because the LSM hook is called before a port is picked. If this is >> > correct, the best way to control port binding would be to always deny >> > binding on port zero/random (when restricting port binding, whatever >> > exception rules are in place). This explanation should be part of a >> > comment for this specific exception. >> >> Yep, if some LSM rule (for bind) has been applied a with specific port, >> other attemps to bind with zero/random ports would be refused by LSM >> security checks. > > To say it another way, we should not allow to add a rule with port 0 for > LANDLOCK_ACCESS_NET_BIND_TCP, but return -EINVAL in this case. This > limitation should be explained, documented and tested. > > With (only) LANDLOCK_ACCESS_NET_CONNECT_TCP it should be allowed though > (except if there is also LANDLOCK_ACCESS_NET_BIND_TCP) of course. > Another test should cover the case with a new rule with these two access > rights and port 0. I think it's possible to have LANDLOCK_ACCESS_NET_CONNECT_TCP with port 0 with LANDLOCK_ACCESS_NET_BIND_TCP at the same time, cause LANDLOCK_ACCESS_NET_BIND_TCP rule is allowed (by Landlock) with any other port but 0. > >> > >> > Cc Paul >> > >> > > 2. ip4, non-sandboxed, bind port 0 -> should return 0 (should be bounded to >> > > random port). >> > >> > I think so but we need to make sure the random port cannot be < 1024, I >> > guess with /proc/sys/net/ipv4/ip_local_port_range but I don't know for >> > IPv6. >> >> For ipv4 when connecting to a server a client binds to a random port >> within /proc/sys/net/ipv4/ip_local_port_range, by default one my machine >> this range is: cat /proc/sys/net/ipv4/ip_local_port_range >> 32768 60999. >> But for ipv6 there is no such tuning range. > > Ok, let's just assume that the test system doesn't have > ip_local_port_range < 1024, put this assumption in a comment, and don't > touch ip_local_port_range at all. > >> >> > >> > > 3. ip6, sandboxed, bind port 0 -> should return EACCES (denied by >> > > landlock). >> > > 4. ip6, non-sandboxed, bind port 0 -> should return 0 (should be bounded to >> > > random port). >> > > 5. ip4, sandboxed, bind some available port, connect port 0 -> should >> > > return -EACCES (denied by landlock). > > If a rule allows connecting to port 0, then it should be ECONNREFUSED, > otherwise EACCES indeed. Both cases should be tested. > >> > >> > Yes, but don't need to bind to anything (same for the next ones). >> > >> > > 6. ip4, non-sandboxed, bind some available port, connect port 0 -> should >> > > return ECONNREFUSED. >> > >> > Yes, but without any binding. >> > >> > > 7. ip6, sandboxed, bind some available port, connect port 0 -> should >> > > return -EACCES (denied by landlock) >> > > 8. ip6, non-sandboxed, some bind available port, connect port 0 -> should >> > > return ECONNREFUSED. >> > > >> > > Correct? >> > >> > Thinking more about this case, being able to add a rule with port zero >> > *for a connect action* looks legitimate. A rule with both connect and >> > bind actions on port zero should then be denied. We should fix >> > add_rule_net_service() and test that (with a first layer allowing port >> > zero, and a second without rule, for connect). >> >> So with first rule allowing port 0 connect action, the second rule with >> some another port and connect action, > > Yes, but the first rule being part of a first layer/restriction, and the > second rule part of a second layer. > >> as a result test should allow that. >> Correct? > > The first layer should return ECONNREFUSED when connecting on port 0 > (allowed but nothing listening), and once the second layer is enforced, > EACCES should be returned on port 0. > >> > >> > >> > > >> > > > > [...] >> > > > > > +FIXTURE(inet) >> > > > > +{ >> > > > > + struct service_fixture srv0, srv1; >> > > > > +}; >> > > > > The "inet" variants are useless and should be removed. The >> > > "inet" >> > > > fixture can then be renamed to "ipv4_tcp". >> > > > So inet should be changed to ipv4_tcp and ipv6_tcp with next >> > > variants: >> > > >> > > - ipv4_tcp.no_sandbox_with_ipv4.port_endianness >> > > - ipv4_tcp.sandbox_with_ipv4.port_endianness >> > > - ipv6_tcp.no_sandbox_with_ipv6.port_endianness >> > > - ipv6_tcp.sandbox_with_ipv6.port_endianness >> > > ???? >> > > >> > > in this case we need double copy of TEST_F(inet, port_endianness) : >> > > TEST_F(ipv4_tcp, port_endianness) >> > > TEST_F(ipv6_tcp, port_endianness) >> > >> > There is no need for any variant for the port_endianness test. You can >> > rename "inet" to "ipv4_tcp" (and not "inet_tcp" like I said before). >> > . > .
On Fri, Sep 15, 2023 at 11:54:46AM +0300, Konstantin Meskhidze (A) wrote: > > > 9/14/2023 11:08 AM, Mickaël Salaün пишет: > > On Mon, Sep 11, 2023 at 01:13:24PM +0300, Konstantin Meskhidze (A) wrote: > > > > > > > > > 8/17/2023 6:08 PM, Mickaël Salaün пишет: > > > > On Sat, Aug 12, 2023 at 05:37:00PM +0300, Konstantin Meskhidze (A) wrote: > > > > > > > > > 7/12/2023 10:02 AM, Mickaël Salaün пишет: > > > > > > > On 06/07/2023 16:55, Mickaël Salaün wrote: > > > > > > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> > > > > > > > > > This patch is a revamp of the v11 tests [1] with new tests > > > > > (see the > > > > > > > "Changes since v11" description). I (Mickaël) only added the following > > > > > > > todo list and the "Changes since v11" sections in this commit message. > > > > > > > I think this patch is good but it would appreciate reviews. > > > > > > > You can find the diff of my changes here but it is not really readable: > > > > > > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) > > > > > > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ > > > > > > > TODO: > > > > > > > - Rename all "net_service" to "net_port". > > > > > > > - Fix the two kernel bugs found with the new tests. > > > > > > > - Update this commit message with a small description of all tests. > > > > > > > [...] > > > > > > > We should also add a test to make sure errno is the same > > > with and > > > > > > without sandboxing when using port 0 for connect and consistent with > > > > > > bind (using an available port). The test fixture and variants should be > > > > > > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, > > > > > > which will result in 8 "ip" variants: > > > > > > > TEST_F(ip, port_zero) > > > > > > { > > > > > > if (variant->sandbox == TCP_SANDBOX) { > > > > > > /* Denies any connect and bind. */ > > > > > > } > > > > > > /* Checks errno for port 0. */ > > > > > > } > > > > > As I understand the would be the next test cases: > > > > > > > 1. ip4, sandboxed, bind port 0 -> should return EACCES > > > (denied by > > > > > landlock). > > > > > Without any allowed port, yes. This test case is useful. > > > > > By tuning /proc/sys/net/ipv4/ip_local_port_range (see > > > > inet_csk_find_open_port call) we should be able to pick a specific > > > > allowed port and test it. We can also test for the EADDRINUSE error to > > > > make sure error ordering is correct (compared with -EACCES). > > > Sorry, did not get this case. Could please explain it with more details? > > > > According to bind(2), if no port are available, the syscall should > > return EADDRINUSE. And this returned value should be the same whatever > > the process is sandbox or not (and never EACCES). But as I explained > > just below, we cannot know this random port from the LSM hook, so no > > need to tweak /proc/sys/net/ipv4/ip_local_port_range, and your this is > > correct: > > > > 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by > > landlock). > > yep, adding rule with port 0 (for bind) returns EINVAL then > calling bind port 0 returns EACCES cause there is no rule with port 0. > > > > > > > However, I think the current LSM API don't enable to infer this > > > random > > > > port because the LSM hook is called before a port is picked. If this is > > > > correct, the best way to control port binding would be to always deny > > > > binding on port zero/random (when restricting port binding, whatever > > > > exception rules are in place). This explanation should be part of a > > > > comment for this specific exception. > > > > > > Yep, if some LSM rule (for bind) has been applied a with specific port, > > > other attemps to bind with zero/random ports would be refused by LSM > > > security checks. > > > > To say it another way, we should not allow to add a rule with port 0 for > > LANDLOCK_ACCESS_NET_BIND_TCP, but return -EINVAL in this case. This > > limitation should be explained, documented and tested. > > > > With (only) LANDLOCK_ACCESS_NET_CONNECT_TCP it should be allowed though > > (except if there is also LANDLOCK_ACCESS_NET_BIND_TCP) of course. > > Another test should cover the case with a new rule with these two access > > rights and port 0. > > I think it's possible to have LANDLOCK_ACCESS_NET_CONNECT_TCP with port 0 > with LANDLOCK_ACCESS_NET_BIND_TCP at the same time, cause > LANDLOCK_ACCESS_NET_BIND_TCP rule is allowed (by Landlock) with any other > port but 0. It would mask the fact that port zero cannot be allowed, which could be possible one day. So for now we need to return EINVAL in this case.
9/18/2023 9:56 AM, Mickaël Salaün пишет: > On Fri, Sep 15, 2023 at 11:54:46AM +0300, Konstantin Meskhidze (A) wrote: >> >> >> 9/14/2023 11:08 AM, Mickaël Salaün пишет: >> > On Mon, Sep 11, 2023 at 01:13:24PM +0300, Konstantin Meskhidze (A) wrote: >> > > >> > > >> > > 8/17/2023 6:08 PM, Mickaël Salaün пишет: >> > > > On Sat, Aug 12, 2023 at 05:37:00PM +0300, Konstantin Meskhidze (A) wrote: >> > > > > > > > > 7/12/2023 10:02 AM, Mickaël Salaün пишет: >> > > > > > > On 06/07/2023 16:55, Mickaël Salaün wrote: >> > > > > > > From: Konstantin Meskhidze <konstantin.meskhidze@huawei.com> >> > > > > > > > > This patch is a revamp of the v11 tests [1] with new tests >> > > > > (see the >> > > > > > > "Changes since v11" description). I (Mickaël) only added the following >> > > > > > > todo list and the "Changes since v11" sections in this commit message. >> > > > > > > I think this patch is good but it would appreciate reviews. >> > > > > > > You can find the diff of my changes here but it is not really readable: >> > > > > > > https://git.kernel.org/mic/c/78edf722fba5 (landlock-net-v11 branch) >> > > > > > > [1] https://lore.kernel.org/all/20230515161339.631577-11-konstantin.meskhidze@huawei.com/ >> > > > > > > TODO: >> > > > > > > - Rename all "net_service" to "net_port". >> > > > > > > - Fix the two kernel bugs found with the new tests. >> > > > > > > - Update this commit message with a small description of all tests. >> > > > > > > [...] >> > > > > > > We should also add a test to make sure errno is the same >> > > with and >> > > > > > without sandboxing when using port 0 for connect and consistent with >> > > > > > bind (using an available port). The test fixture and variants should be >> > > > > > quite similar to the "ipv4" ones, but we can also add AF_INET6 variants, >> > > > > > which will result in 8 "ip" variants: >> > > > > > > TEST_F(ip, port_zero) >> > > > > > { >> > > > > > if (variant->sandbox == TCP_SANDBOX) { >> > > > > > /* Denies any connect and bind. */ >> > > > > > } >> > > > > > /* Checks errno for port 0. */ >> > > > > > } >> > > > > As I understand the would be the next test cases: >> > > > > > > 1. ip4, sandboxed, bind port 0 -> should return EACCES >> > > (denied by >> > > > > landlock). >> > > > > Without any allowed port, yes. This test case is useful. >> > > > > By tuning /proc/sys/net/ipv4/ip_local_port_range (see >> > > > inet_csk_find_open_port call) we should be able to pick a specific >> > > > allowed port and test it. We can also test for the EADDRINUSE error to >> > > > make sure error ordering is correct (compared with -EACCES). >> > > Sorry, did not get this case. Could please explain it with more details? >> > >> > According to bind(2), if no port are available, the syscall should >> > return EADDRINUSE. And this returned value should be the same whatever >> > the process is sandbox or not (and never EACCES). But as I explained >> > just below, we cannot know this random port from the LSM hook, so no >> > need to tweak /proc/sys/net/ipv4/ip_local_port_range, and your this is >> > correct: >> > >> > 1. ip4, sandboxed, bind port 0 -> should return EACCES (denied by >> > landlock). >> >> yep, adding rule with port 0 (for bind) returns EINVAL then >> calling bind port 0 returns EACCES cause there is no rule with port 0. >> > >> > > > > However, I think the current LSM API don't enable to infer this >> > > random >> > > > port because the LSM hook is called before a port is picked. If this is >> > > > correct, the best way to control port binding would be to always deny >> > > > binding on port zero/random (when restricting port binding, whatever >> > > > exception rules are in place). This explanation should be part of a >> > > > comment for this specific exception. >> > > >> > > Yep, if some LSM rule (for bind) has been applied a with specific port, >> > > other attemps to bind with zero/random ports would be refused by LSM >> > > security checks. >> > >> > To say it another way, we should not allow to add a rule with port 0 for >> > LANDLOCK_ACCESS_NET_BIND_TCP, but return -EINVAL in this case. This >> > limitation should be explained, documented and tested. >> > >> > With (only) LANDLOCK_ACCESS_NET_CONNECT_TCP it should be allowed though >> > (except if there is also LANDLOCK_ACCESS_NET_BIND_TCP) of course. >> > Another test should cover the case with a new rule with these two access >> > rights and port 0. >> >> I think it's possible to have LANDLOCK_ACCESS_NET_CONNECT_TCP with port 0 >> with LANDLOCK_ACCESS_NET_BIND_TCP at the same time, cause >> LANDLOCK_ACCESS_NET_BIND_TCP rule is allowed (by Landlock) with any other >> port but 0. > > It would mask the fact that port zero cannot be allowed, which could be > possible one day. So for now we need to return EINVAL in this case. Got it. I added bind mask in add_rule_net_service() to check that zero port is not allowed with bind action. I sent all changes in the latest V12 patch. > .
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config index 0f0a65287bac..71f7e9a8a64c 100644 --- a/tools/testing/selftests/landlock/config +++ b/tools/testing/selftests/landlock/config @@ -1,3 +1,7 @@ +CONFIG_INET=y +CONFIG_IPV6=y +CONFIG_NET=y +CONFIG_NET_NS=y CONFIG_OVERLAY_FS=y CONFIG_SECURITY_LANDLOCK=y CONFIG_SECURITY_PATH=y diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index b762b5419a89..9175ee8adf51 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -8,8 +8,10 @@ */ #define _GNU_SOURCE +#include <arpa/inet.h> #include <fcntl.h> #include <linux/landlock.h> +#include <netinet/in.h> #include <sched.h> #include <stdio.h> #include <string.h> @@ -17,6 +19,7 @@ #include <sys/mount.h> #include <sys/prctl.h> #include <sys/sendfile.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/sysmacros.h> #include <unistd.h> @@ -4413,4 +4416,65 @@ TEST_F_FORK(layout2_overlay, same_content_different_file) } } +static const char loopback_ipv4[] = "127.0.0.1"; +const unsigned short sock_port = 15000; + +TEST_F_FORK(layout1, with_net) +{ + const struct rule rules[] = { + { + .path = dir_s1d2, + .access = ACCESS_RO, + }, + {}, + }; + struct landlock_ruleset_attr ruleset_attr_net = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + struct landlock_net_service_attr tcp_bind = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + + .port = sock_port, + }; + int sockfd, ruleset_fd, ruleset_fd_net; + struct sockaddr_in addr4; + + addr4.sin_family = AF_INET; + addr4.sin_port = htons(sock_port); + addr4.sin_addr.s_addr = inet_addr(loopback_ipv4); + memset(&addr4.sin_zero, '\0', 8); + + /* Creates ruleset for network access. */ + ruleset_fd_net = landlock_create_ruleset(&ruleset_attr_net, + sizeof(ruleset_attr_net), 0); + ASSERT_LE(0, ruleset_fd_net); + + /* Adds a network rule. */ + ASSERT_EQ(0, + landlock_add_rule(ruleset_fd_net, LANDLOCK_RULE_NET_SERVICE, + &tcp_bind, 0)); + + enforce_ruleset(_metadata, ruleset_fd_net); + ASSERT_EQ(0, close(ruleset_fd_net)); + + ruleset_fd = create_ruleset(_metadata, ACCESS_RW, rules); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Tests on a directory with the network rule loaded. */ + ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); + ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); + + sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_LE(0, sockfd); + /* Binds a socket to port 15000. */ + ASSERT_EQ(0, bind(sockfd, &addr4, sizeof(addr4))); + + /* Closes bounded socket. */ + ASSERT_EQ(0, close(sockfd)); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c new file mode 100644 index 000000000000..12dc127ea7d1 --- /dev/null +++ b/tools/testing/selftests/landlock/net_test.c @@ -0,0 +1,1439 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock tests - Network + * + * Copyright © 2022-2023 Huawei Tech. Co., Ltd. + * Copyright © 2023 Microsoft Corporation + */ + +#define _GNU_SOURCE +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/landlock.h> +#include <linux/in.h> +#include <sched.h> +#include <stdint.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include "common.h" + +const short sock_port_start = (1 << 10); + +static const char loopback_ipv4[] = "127.0.0.1"; +static const char loopback_ipv6[] = "::1"; + +/* Number pending connections queue to be hold. */ +const short backlog = 10; + +enum sandbox_type { + NO_SANDBOX, + /* This may be used to test rules that allow *and* deny accesses. */ + TCP_SANDBOX, +}; + +struct protocol_variant { + int domain; + int type; +}; + +struct service_fixture { + struct protocol_variant protocol; + /* port is also stored in ipv4_addr.sin_port or ipv6_addr.sin6_port */ + unsigned short port; + union { + struct sockaddr_in ipv4_addr; + struct sockaddr_in6 ipv6_addr; + struct { + struct sockaddr_un unix_addr; + socklen_t unix_addr_len; + }; + }; +}; + +static int set_service(struct service_fixture *const srv, + const struct protocol_variant prot, + const unsigned short index) +{ + memset(srv, 0, sizeof(*srv)); + + /* + * Copies all protocol properties in case of the variant only contains + * a subset of them. + */ + srv->protocol = prot; + + /* Checks for port overflow. */ + if (index > 2) + return 1; + srv->port = sock_port_start << (2 * index); + + switch (prot.domain) { + case AF_UNSPEC: + case AF_INET: + srv->ipv4_addr.sin_family = prot.domain; + srv->ipv4_addr.sin_port = htons(srv->port); + srv->ipv4_addr.sin_addr.s_addr = inet_addr(loopback_ipv4); + return 0; + + case AF_INET6: + srv->ipv6_addr.sin6_family = prot.domain; + srv->ipv6_addr.sin6_port = htons(srv->port); + inet_pton(AF_INET6, loopback_ipv6, &srv->ipv6_addr.sin6_addr); + return 0; + + case AF_UNIX: + srv->unix_addr.sun_family = prot.domain; + sprintf(srv->unix_addr.sun_path, + "_selftests-landlock-net-tid%d-index%d", gettid(), + index); + srv->unix_addr_len = SUN_LEN(&srv->unix_addr); + srv->unix_addr.sun_path[0] = '\0'; + return 0; + } + return 1; +} + +static void setup_loopback(struct __test_metadata *const _metadata) +{ + set_cap(_metadata, CAP_SYS_ADMIN); + ASSERT_EQ(0, unshare(CLONE_NEWNET)); + ASSERT_EQ(0, system("ip link set dev lo up")); + clear_cap(_metadata, CAP_SYS_ADMIN); +} + +static bool is_restricted(const struct protocol_variant *const prot, + const enum sandbox_type sandbox) +{ + switch (prot->domain) { + case AF_INET: + case AF_INET6: + switch (prot->type) { + case SOCK_STREAM: + return sandbox == TCP_SANDBOX; + } + break; + } + return false; +} + +static int socket_variant(const struct service_fixture *const srv) +{ + int ret; + + ret = socket(srv->protocol.domain, srv->protocol.type | SOCK_CLOEXEC, + 0); + if (ret < 0) + return -errno; + return ret; +} + +#ifndef SIN6_LEN_RFC2133 +#define SIN6_LEN_RFC2133 24 +#endif + +static socklen_t get_addrlen(const struct service_fixture *const srv, + const bool minimal) +{ + switch (srv->protocol.domain) { + case AF_UNSPEC: + case AF_INET: + return sizeof(srv->ipv4_addr); + + case AF_INET6: + if (minimal) + return SIN6_LEN_RFC2133; + return sizeof(srv->ipv6_addr); + + case AF_UNIX: + if (minimal) + return sizeof(srv->unix_addr) - + sizeof(srv->unix_addr.sun_path); + return srv->unix_addr_len; + + default: + return 0; + } +} + +static int bind_variant_addrlen(const int sock_fd, + const struct service_fixture *const srv, + const socklen_t addrlen) +{ + int ret; + + switch (srv->protocol.domain) { + case AF_UNSPEC: + case AF_INET: + ret = bind(sock_fd, &srv->ipv4_addr, addrlen); + break; + + case AF_INET6: + ret = bind(sock_fd, &srv->ipv6_addr, addrlen); + break; + + case AF_UNIX: + ret = bind(sock_fd, &srv->unix_addr, addrlen); + break; + + default: + errno = -EAFNOSUPPORT; + return -errno; + } + + if (ret < 0) + return -errno; + return ret; +} + +static int bind_variant(const int sock_fd, + const struct service_fixture *const srv) +{ + return bind_variant_addrlen(sock_fd, srv, get_addrlen(srv, false)); +} + +static int connect_variant_addrlen(const int sock_fd, + const struct service_fixture *const srv, + const socklen_t addrlen) +{ + int ret; + + switch (srv->protocol.domain) { + case AF_UNSPEC: + case AF_INET: + ret = connect(sock_fd, &srv->ipv4_addr, addrlen); + break; + + case AF_INET6: + ret = connect(sock_fd, &srv->ipv6_addr, addrlen); + break; + + case AF_UNIX: + ret = connect(sock_fd, &srv->unix_addr, addrlen); + break; + + default: + errno = -EAFNOSUPPORT; + return -errno; + } + + if (ret < 0) + return -errno; + return ret; +} + +static int connect_variant(const int sock_fd, + const struct service_fixture *const srv) +{ + return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false)); +} + +FIXTURE(protocol) +{ + struct service_fixture srv0, srv1, srv2, unspec_any, unspec_srv0; +}; + +FIXTURE_VARIANT(protocol) +{ + const enum sandbox_type sandbox; + const struct protocol_variant prot; +}; + +FIXTURE_SETUP(protocol) +{ + const struct protocol_variant prot_unspec = { + .domain = AF_UNSPEC, + .type = SOCK_STREAM, + }; + + disable_caps(_metadata); + + ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0)); + ASSERT_EQ(0, set_service(&self->srv1, variant->prot, 1)); + ASSERT_EQ(0, set_service(&self->srv2, variant->prot, 2)); + + ASSERT_EQ(0, set_service(&self->unspec_srv0, prot_unspec, 0)); + + ASSERT_EQ(0, set_service(&self->unspec_any, prot_unspec, 0)); + self->unspec_any.ipv4_addr.sin_addr.s_addr = htonl(INADDR_ANY); + + setup_loopback(_metadata); +}; + +FIXTURE_TEARDOWN(protocol) +{ +} + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_tcp) { + /* clang-format on */ + .sandbox = NO_SANDBOX, + .prot = { + .domain = AF_INET, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_tcp) { + /* clang-format on */ + .sandbox = NO_SANDBOX, + .prot = { + .domain = AF_INET6, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_udp) { + /* clang-format on */ + .sandbox = NO_SANDBOX, + .prot = { + .domain = AF_INET, + .type = SOCK_DGRAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_udp) { + /* clang-format on */ + .sandbox = NO_SANDBOX, + .prot = { + .domain = AF_INET6, + .type = SOCK_DGRAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_unix_stream) { + /* clang-format on */ + .sandbox = NO_SANDBOX, + .prot = { + .domain = AF_UNIX, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_unix_datagram) { + /* clang-format on */ + .sandbox = NO_SANDBOX, + .prot = { + .domain = AF_UNIX, + .type = SOCK_DGRAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_tcp) { + /* clang-format on */ + .sandbox = TCP_SANDBOX, + .prot = { + .domain = AF_INET, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_tcp) { + /* clang-format on */ + .sandbox = TCP_SANDBOX, + .prot = { + .domain = AF_INET6, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_udp) { + /* clang-format on */ + .sandbox = TCP_SANDBOX, + .prot = { + .domain = AF_INET, + .type = SOCK_DGRAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_udp) { + /* clang-format on */ + .sandbox = TCP_SANDBOX, + .prot = { + .domain = AF_INET6, + .type = SOCK_DGRAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_stream) { + /* clang-format on */ + .sandbox = TCP_SANDBOX, + .prot = { + .domain = AF_UNIX, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) { + /* clang-format on */ + .sandbox = TCP_SANDBOX, + .prot = { + .domain = AF_UNIX, + .type = SOCK_DGRAM, + }, +}; + +static void test_bind_and_connect(struct __test_metadata *const _metadata, + const struct service_fixture *const srv, + const bool deny_bind, const bool deny_connect) +{ + char buf = '\0'; + int inval_fd, bind_fd, client_fd, status, ret; + pid_t child; + + /* Starts invalid addrlen tests with bind. */ + inval_fd = socket_variant(srv); + ASSERT_LE(0, inval_fd) + { + TH_LOG("Failed to create socket: %s", strerror(errno)); + } + + /* Tries to bind with zero as addrlen. */ + EXPECT_EQ(-EINVAL, bind_variant_addrlen(inval_fd, srv, 0)); + + /* Tries to bind with too small addrlen. */ + EXPECT_EQ(-EINVAL, bind_variant_addrlen(inval_fd, srv, + get_addrlen(srv, true) - 1)); + + /* Tries to bind with minimal addrlen. */ + ret = bind_variant_addrlen(inval_fd, srv, get_addrlen(srv, true)); + if (deny_bind) { + EXPECT_EQ(-EACCES, ret); + } else { + EXPECT_EQ(0, ret) + { + TH_LOG("Failed to bind to socket: %s", strerror(errno)); + } + } + EXPECT_EQ(0, close(inval_fd)); + + /* Starts invalid addrlen tests with connect. */ + inval_fd = socket_variant(srv); + ASSERT_LE(0, inval_fd); + + /* Tries to connect with zero as addrlen. */ + EXPECT_EQ(-EINVAL, connect_variant_addrlen(inval_fd, srv, 0)); + + /* Tries to connect with too small addrlen. */ + EXPECT_EQ(-EINVAL, connect_variant_addrlen(inval_fd, srv, + get_addrlen(srv, true) - 1)); + + /* Tries to connect with minimal addrlen. */ + ret = connect_variant_addrlen(inval_fd, srv, get_addrlen(srv, true)); + if (srv->protocol.domain == AF_UNIX) { + EXPECT_EQ(-EINVAL, ret); + } else if (deny_connect) { + EXPECT_EQ(-EACCES, ret); + } else if (srv->protocol.type == SOCK_STREAM) { + /* No listening server, whatever the value of deny_bind. */ + EXPECT_EQ(-ECONNREFUSED, ret); + } else { + EXPECT_EQ(0, ret) + { + TH_LOG("Failed to connect to socket: %s", + strerror(errno)); + } + } + EXPECT_EQ(0, close(inval_fd)); + + /* Starts connection tests. */ + bind_fd = socket_variant(srv); + ASSERT_LE(0, bind_fd); + + ret = bind_variant(bind_fd, srv); + if (deny_bind) { + EXPECT_EQ(-EACCES, ret); + } else { + EXPECT_EQ(0, ret); + + /* Creates a listening socket. */ + if (srv->protocol.type == SOCK_STREAM) + EXPECT_EQ(0, listen(bind_fd, backlog)); + } + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int connect_fd, ret; + + /* Closes listening socket for the child. */ + EXPECT_EQ(0, close(bind_fd)); + + /* Starts connection tests. */ + connect_fd = socket_variant(srv); + ASSERT_LE(0, connect_fd); + ret = connect_variant(connect_fd, srv); + if (deny_connect) { + EXPECT_EQ(-EACCES, ret); + } else if (deny_bind) { + /* No listening server. */ + EXPECT_EQ(-ECONNREFUSED, ret); + } else { + EXPECT_EQ(0, ret); + EXPECT_EQ(1, write(connect_fd, ".", 1)); + } + + EXPECT_EQ(0, close(connect_fd)); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + + /* Accepts connection from the child. */ + client_fd = bind_fd; + if (!deny_bind && !deny_connect) { + if (srv->protocol.type == SOCK_STREAM) { + client_fd = accept(bind_fd, NULL, 0); + ASSERT_LE(0, client_fd); + } + + EXPECT_EQ(1, read(client_fd, &buf, 1)); + EXPECT_EQ('.', buf); + } + + EXPECT_EQ(child, waitpid(child, &status, 0)); + EXPECT_EQ(1, WIFEXITED(status)); + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + /* Closes connection, if any. */ + if (client_fd != bind_fd) + EXPECT_LE(0, close(client_fd)); + + /* Closes listening socket. */ + EXPECT_EQ(0, close(bind_fd)); +} + +TEST_F(protocol, bind) +{ + if (variant->sandbox == TCP_SANDBOX) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + const struct landlock_net_service_attr tcp_bind_connect_p0 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->srv0.port, + }; + const struct landlock_net_service_attr tcp_connect_p1 = { + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->srv1.port, + }; + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind for the first port. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_connect_p0, 0)); + + /* Allows connect and denies bind for the second port. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_connect_p1, 0)); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + /* Binds a socket to the first port. */ + test_bind_and_connect(_metadata, &self->srv0, false, false); + + /* Binds a socket to the second port. */ + test_bind_and_connect(_metadata, &self->srv1, + is_restricted(&variant->prot, variant->sandbox), + false); + + /* Binds a socket to the third port. */ + test_bind_and_connect(_metadata, &self->srv2, + is_restricted(&variant->prot, variant->sandbox), + is_restricted(&variant->prot, variant->sandbox)); +} + +TEST_F(protocol, connect) +{ + if (variant->sandbox == TCP_SANDBOX) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + const struct landlock_net_service_attr tcp_bind_connect_p0 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->srv0.port, + }; + const struct landlock_net_service_attr tcp_bind_p1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->srv1.port, + }; + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind for the first port. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_connect_p0, 0)); + + /* Allows bind and denies connect for the second port. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_p1, 0)); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + test_bind_and_connect(_metadata, &self->srv0, false, false); + + test_bind_and_connect(_metadata, &self->srv1, false, + is_restricted(&variant->prot, variant->sandbox)); + + test_bind_and_connect(_metadata, &self->srv2, + is_restricted(&variant->prot, variant->sandbox), + is_restricted(&variant->prot, variant->sandbox)); +} + +// Kernel FIXME: tcp_sandbox_with_ipv6_tcp and tcp_sandbox_with_unix_stream +TEST_F(protocol, bind_unspec) +{ + int bind_fd, ret; + + if (variant->sandbox == TCP_SANDBOX) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, + }; + const struct landlock_net_service_attr tcp_bind = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->srv0.port, + }; + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows bind. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + bind_fd = socket_variant(&self->srv0); + ASSERT_LE(0, bind_fd); + + /* Binds on AF_UNSPEC/INADDR_ANY. */ + ret = bind_variant(bind_fd, &self->unspec_any); + if (variant->prot.domain == AF_INET) { + EXPECT_EQ(0, ret) + { + TH_LOG("Failed to bind to unspec/any socket: %s", + strerror(errno)); + } + } else { + EXPECT_EQ(-EINVAL, ret); + } + EXPECT_EQ(0, close(bind_fd)); + + bind_fd = socket_variant(&self->srv0); + ASSERT_LE(0, bind_fd); + ret = bind_variant(bind_fd, &self->unspec_srv0); + if (variant->prot.domain == AF_INET) { + EXPECT_EQ(-EAFNOSUPPORT, ret); + } else { + EXPECT_EQ(-EINVAL, ret) + { + TH_LOG("Wrong bind error: %s", strerror(errno)); + } + } + EXPECT_EQ(0, close(bind_fd)); +} + +TEST_F(protocol, connect_unspec) +{ + int bind_fd, client_fd, status; + pid_t child; + + if (variant->sandbox == TCP_SANDBOX) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + const struct landlock_net_service_attr tcp_connect = { + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->srv0.port, + }; + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_connect, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + /* Generic connection tests. */ + test_bind_and_connect(_metadata, &self->srv0, false, false); + + /* Specific connection tests. */ + bind_fd = socket_variant(&self->srv0); + ASSERT_LE(0, bind_fd); + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0)); + if (self->srv0.protocol.type == SOCK_STREAM) + EXPECT_EQ(0, listen(bind_fd, backlog)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int connect_fd, ret; + + /* Closes listening socket for the child. */ + EXPECT_EQ(0, close(bind_fd)); + + connect_fd = socket_variant(&self->srv0); + ASSERT_LE(0, connect_fd); + EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0)); + + /* Tries to connect again, or set peer. */ + ret = connect_variant(connect_fd, &self->srv0); + if (self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-EISCONN, ret); + } else { + EXPECT_EQ(0, ret); + } + + /* Disconnects already connected socket, or set peer. */ + ret = connect_variant(connect_fd, &self->unspec_any); + if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-EINVAL, ret); + } else { + EXPECT_EQ(0, ret); + } + + /* Tries to reconnect, or set peer. */ + ret = connect_variant(connect_fd, &self->srv0); + if (self->srv0.protocol.domain == AF_UNIX && + self->srv0.protocol.type == SOCK_STREAM) { + EXPECT_EQ(-EISCONN, ret); + } else { + EXPECT_EQ(0, ret); + } + + EXPECT_EQ(0, close(connect_fd)); + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + return; + } + + client_fd = bind_fd; + if (self->srv0.protocol.type == SOCK_STREAM) { + client_fd = accept(bind_fd, NULL, 0); + ASSERT_LE(0, client_fd); + } + + EXPECT_EQ(child, waitpid(child, &status, 0)); + EXPECT_EQ(1, WIFEXITED(status)); + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + /* Closes connection, if any. */ + if (client_fd != bind_fd) + EXPECT_LE(0, close(client_fd)); + + /* Closes listening socket. */ + EXPECT_EQ(0, close(bind_fd)); +} + +FIXTURE(ipv4) +{ + struct service_fixture srv0, srv1; +}; + +FIXTURE_VARIANT(ipv4) +{ + const enum sandbox_type sandbox; + const int type; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ipv4, no_sandbox_with_tcp) { + /* clang-format on */ + .sandbox = NO_SANDBOX, + .type = SOCK_STREAM, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_tcp) { + /* clang-format on */ + .sandbox = TCP_SANDBOX, + .type = SOCK_STREAM, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ipv4, no_sandbox_with_udp) { + /* clang-format on */ + .sandbox = NO_SANDBOX, + .type = SOCK_DGRAM, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ipv4, tcp_sandbox_with_udp) { + /* clang-format on */ + .sandbox = TCP_SANDBOX, + .type = SOCK_DGRAM, +}; + +FIXTURE_SETUP(ipv4) +{ + const struct protocol_variant prot = { + .domain = AF_INET, + .type = variant->type, + }; + + disable_caps(_metadata); + + set_service(&self->srv0, prot, 0); + set_service(&self->srv1, prot, 1); + + setup_loopback(_metadata); +}; + +FIXTURE_TEARDOWN(ipv4) +{ +} + +// Kernel FIXME: tcp_sandbox_with_tcp and tcp_sandbox_with_udp +TEST_F(ipv4, from_unix_to_inet) +{ + int unix_stream_fd, unix_dgram_fd; + + if (variant->sandbox == TCP_SANDBOX) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + const struct landlock_net_service_attr tcp_bind_connect_p0 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->srv0.port, + }; + int ruleset_fd; + + /* Denies connect and bind to check errno value. */ + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows connect and bind for srv0. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_connect_p0, 0)); + + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + unix_stream_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_LE(0, unix_stream_fd); + + unix_dgram_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_LE(0, unix_dgram_fd); + + /* Checks unix stream bind and connect for srv0. */ + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv0)); + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv0)); + + /* Checks unix stream bind and connect for srv1. */ + EXPECT_EQ(-EINVAL, bind_variant(unix_stream_fd, &self->srv1)) + { + TH_LOG("Wrong bind error: %s", strerror(errno)); + } + EXPECT_EQ(-EINVAL, connect_variant(unix_stream_fd, &self->srv1)); + + /* Checks unix datagram bind and connect for srv0. */ + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv0)); + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv0)); + + /* Checks unix datagram bind and connect for srv0. */ + EXPECT_EQ(-EINVAL, bind_variant(unix_dgram_fd, &self->srv1)); + EXPECT_EQ(-EINVAL, connect_variant(unix_dgram_fd, &self->srv1)); +} + +FIXTURE(tcp_layers) +{ + struct service_fixture srv0, srv1; +}; + +FIXTURE_VARIANT(tcp_layers) +{ + const size_t num_layers; + const int domain; +}; + +FIXTURE_SETUP(tcp_layers) +{ + const struct protocol_variant prot = { + .domain = variant->domain, + .type = SOCK_STREAM, + }; + + disable_caps(_metadata); + + ASSERT_EQ(0, set_service(&self->srv0, prot, 0)); + ASSERT_EQ(0, set_service(&self->srv1, prot, 1)); + + setup_loopback(_metadata); +}; + +FIXTURE_TEARDOWN(tcp_layers) +{ +} + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_layers, no_sandbox_with_ipv4) { + /* clang-format on */ + .domain = AF_INET, + .num_layers = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_layers, one_sandbox_with_ipv4) { + /* clang-format on */ + .domain = AF_INET, + .num_layers = 1, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_layers, two_sandboxes_with_ipv4) { + /* clang-format on */ + .domain = AF_INET, + .num_layers = 2, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_layers, three_sandboxes_with_ipv4) { + /* clang-format on */ + .domain = AF_INET, + .num_layers = 3, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_layers, no_sandbox_with_ipv6) { + /* clang-format on */ + .domain = AF_INET6, + .num_layers = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_layers, one_sandbox_with_ipv6) { + /* clang-format on */ + .domain = AF_INET6, + .num_layers = 1, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_layers, two_sandboxes_with_ipv6) { + /* clang-format on */ + .domain = AF_INET6, + .num_layers = 2, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_layers, three_sandboxes_with_ipv6) { + /* clang-format on */ + .domain = AF_INET6, + .num_layers = 3, +}; + +TEST_F(tcp_layers, ruleset_overlap) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + const struct landlock_net_service_attr tcp_bind = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->srv0.port, + }; + const struct landlock_net_service_attr tcp_bind_connect = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->srv0.port, + }; + + if (variant->num_layers >= 1) { + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Allows bind. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind, 0)); + /* Also allows bind, but allows connect too. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_connect, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + if (variant->num_layers >= 2) { + int ruleset_fd; + + /* Creates another ruleset layer. */ + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Only allows bind. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + if (variant->num_layers >= 3) { + int ruleset_fd; + + /* Creates another ruleset layer. */ + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Try to allow bind and connect. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_connect, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + /* + * Forbids to connect to the socket because only one ruleset layer + * allows connect. + */ + test_bind_and_connect(_metadata, &self->srv0, false, + variant->num_layers >= 2); +} + +TEST_F(tcp_layers, ruleset_expand) +{ + if (variant->num_layers >= 1) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, + }; + /* Allows bind for srv0. */ + const struct landlock_net_service_attr bind_srv0 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->srv0.port, + }; + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &bind_srv0, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + if (variant->num_layers >= 2) { + /* Expands network mask with connect action. */ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + /* Allows bind for srv0 and connect to srv0. */ + const struct landlock_net_service_attr tcp_bind_connect_p0 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = self->srv0.port, + }; + /* Try to allow bind for srv1. */ + const struct landlock_net_service_attr tcp_bind_p1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->srv1.port, + }; + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_connect_p0, 0)); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_p1, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + if (variant->num_layers >= 3) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + /* Allows connect to srv0, without bind rule. */ + const struct landlock_net_service_attr tcp_bind_p0 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = self->srv0.port, + }; + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_p0, 0)); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + } + + test_bind_and_connect(_metadata, &self->srv0, false, + variant->num_layers >= 3); + + test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1, + variant->num_layers >= 2); +} + +/* clang-format off */ +FIXTURE(mini) {}; +/* clang-format on */ + +FIXTURE_SETUP(mini) +{ + disable_caps(_metadata); + + setup_loopback(_metadata); +}; + +FIXTURE_TEARDOWN(mini) +{ +} + +/* clang-format off */ + +#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP + +#define ACCESS_ALL ( \ + LANDLOCK_ACCESS_NET_BIND_TCP | \ + LANDLOCK_ACCESS_NET_CONNECT_TCP) + +/* clang-format on */ + +TEST_F(mini, network_access_rights) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = ACCESS_ALL, + }; + struct landlock_net_service_attr net_service = { + .port = sock_port_start, + }; + int ruleset_fd; + __u64 access; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + for (access = 1; access <= ACCESS_LAST; access <<= 1) { + net_service.allowed_access = access; + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, + LANDLOCK_RULE_NET_SERVICE, + &net_service, 0)) + { + TH_LOG("Failed to add rule with access 0x%llx: %s", + access, strerror(errno)); + } + } + EXPECT_EQ(0, close(ruleset_fd)); +} + +/* Checks invalid attribute, out of landlock network access range. */ +TEST_F(mini, unknown_access_rights) +{ + __u64 access_mask; + + for (access_mask = 1ULL << 63; access_mask != ACCESS_LAST; + access_mask >>= 1) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = access_mask, + }; + + EXPECT_EQ(-1, landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0)); + EXPECT_EQ(EINVAL, errno); + } +} + +TEST_F(mini, inval) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP + }; + const struct landlock_net_service_attr tcp_bind_connect = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = sock_port_start, + }; + const struct landlock_net_service_attr tcp_bind_port_zero = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = 0, + }; + const struct landlock_net_service_attr tcp_denied = { + .allowed_access = 0, + .port = sock_port_start, + }; + const struct landlock_net_service_attr tcp_bind = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = sock_port_start, + }; + int ruleset_fd; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Checks unhandled allowed_access. */ + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_connect, 0)); + EXPECT_EQ(EINVAL, errno); + + /* Checks zero port value. */ + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &tcp_bind_port_zero, 0)); + EXPECT_EQ(EINVAL, errno); + + /* Checks zero access value. */ + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &tcp_denied, 0)); + EXPECT_EQ(ENOMSG, errno); + + /* Adds with legitimate values. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &tcp_bind, 0)); +} + +TEST_F(mini, tcp_port_overflow) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + const struct landlock_net_service_attr port_max_bind = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = UINT16_MAX, + }; + const struct landlock_net_service_attr port_max_connect = { + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, + .port = UINT16_MAX, + }; + const struct landlock_net_service_attr port_overflow1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = UINT16_MAX + 1, + }; + const struct landlock_net_service_attr port_overflow2 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = UINT16_MAX + 2, + }; + const struct landlock_net_service_attr port_overflow3 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = UINT32_MAX + 1UL, + }; + const struct landlock_net_service_attr port_overflow4 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + .port = UINT32_MAX + 2UL, + }; + const struct protocol_variant ipv4_tcp = { + .domain = AF_INET, + .type = SOCK_STREAM, + }; + struct service_fixture srv_denied, srv_max_allowed; + int ruleset_fd; + + ASSERT_EQ(0, set_service(&srv_denied, ipv4_tcp, 0)); + + /* Be careful to avoid port inconsistencies. */ + srv_max_allowed = srv_denied; + srv_max_allowed.port = port_max_bind.port; + srv_max_allowed.ipv4_addr.sin_port = htons(port_max_bind.port); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &port_max_bind, 0)); + + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &port_overflow1, 0)); + EXPECT_EQ(EINVAL, errno); + + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &port_overflow2, 0)); + EXPECT_EQ(EINVAL, errno); + + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &port_overflow3, 0)); + EXPECT_EQ(EINVAL, errno); + + /* Interleaves with invalid rule additions. */ + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &port_max_connect, 0)); + + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &port_overflow4, 0)); + EXPECT_EQ(EINVAL, errno); + + enforce_ruleset(_metadata, ruleset_fd); + + test_bind_and_connect(_metadata, &srv_denied, true, true); + test_bind_and_connect(_metadata, &srv_max_allowed, false, false); +} + +FIXTURE(inet) +{ + struct service_fixture srv0, srv1; +}; + +FIXTURE_VARIANT(inet) +{ + const bool is_sandboxed; + const struct protocol_variant prot; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv4) { + /* clang-format on */ + .is_sandboxed = false, + .prot = { + .domain = AF_INET, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv4) { + /* clang-format on */ + .is_sandboxed = true, + .prot = { + .domain = AF_INET, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(inet, no_sandbox_with_ipv6) { + /* clang-format on */ + .is_sandboxed = false, + .prot = { + .domain = AF_INET6, + .type = SOCK_STREAM, + }, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(inet, sandbox_with_ipv6) { + /* clang-format on */ + .is_sandboxed = true, + .prot = { + .domain = AF_INET6, + .type = SOCK_STREAM, + }, +}; + +FIXTURE_SETUP(inet) +{ + const struct protocol_variant ipv4_tcp = { + .domain = AF_INET, + .type = SOCK_STREAM, + }; + + disable_caps(_metadata); + + ASSERT_EQ(0, set_service(&self->srv0, ipv4_tcp, 0)); + ASSERT_EQ(0, set_service(&self->srv1, ipv4_tcp, 1)); + + setup_loopback(_metadata); +}; + +FIXTURE_TEARDOWN(inet) +{ +} + +TEST_F(inet, port_endianness) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + }; + const struct landlock_net_service_attr bind_host_endian_p0 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, + /* Host port format. */ + .port = self->srv0.port, + }; + const struct landlock_net_service_attr connect_big_endian_p0 = { + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP, + /* Big endian port format. */ + .port = htons(self->srv0.port), + }; + const struct landlock_net_service_attr bind_connect_host_endian_p1 = { + .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP | + LANDLOCK_ACCESS_NET_CONNECT_TCP, + /* Host port format. */ + .port = self->srv1.port, + }; + const unsigned int one = 1; + const char little_endian = *(const char *)&one; + int ruleset_fd; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &bind_host_endian_p0, 0)); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &connect_big_endian_p0, 0)); + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_SERVICE, + &bind_connect_host_endian_p1, 0)); + enforce_ruleset(_metadata, ruleset_fd); + + /* No restriction for big endinan CPU. */ + test_bind_and_connect(_metadata, &self->srv0, false, little_endian); + + /* No restriction for any CPU. */ + test_bind_and_connect(_metadata, &self->srv1, false, false); +} + +TEST_HARNESS_MAIN