git: 97edd37e6279 - main - cap_net: add tests for limits drop

From: Mariusz Zaborski <oshogbo_at_FreeBSD.org>
Date: Tue, 09 Jun 2026 11:38:19 UTC
The branch main has been updated by oshogbo:

URL: https://cgit.FreeBSD.org/src/commit/?id=97edd37e6279d76efee89d466550587246161dc9

commit 97edd37e6279d76efee89d466550587246161dc9
Author:     Mariusz Zaborski <oshogbo@FreeBSD.org>
AuthorDate: 2026-06-09 11:34:13 +0000
Commit:     Mariusz Zaborski <oshogbo@FreeBSD.org>
CommitDate: 2026-06-09 11:34:13 +0000

    cap_net: add tests for limits drop
    
    Reviewed by:    markj
    Differential Revision:  https://reviews.freebsd.org/D56992
---
 lib/libcasper/services/cap_net/tests/net_test.c | 235 ++++++++++++++++++++++++
 1 file changed, 235 insertions(+)

diff --git a/lib/libcasper/services/cap_net/tests/net_test.c b/lib/libcasper/services/cap_net/tests/net_test.c
index 21d620e0f8d8..0fd20d9deae8 100644
--- a/lib/libcasper/services/cap_net/tests/net_test.c
+++ b/lib/libcasper/services/cap_net/tests/net_test.c
@@ -24,6 +24,7 @@
  */
 
 #include <sys/param.h>
+#include <sys/nv.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -1443,6 +1444,233 @@ ATF_TC_BODY(capnet__limits_deprecated_connecttodns, tc)
 	cap_close(capnet);
 }
 
+ATF_TC(capnet__limits_name2addr_partial_drops_family);
+ATF_TC_HEAD(capnet__limits_name2addr_partial_drops_family, tc)
+{
+	atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_name2addr_partial_drops_family, tc)
+{
+	cap_channel_t *capnet;
+	cap_net_limit_t *limit;
+	int family = AF_INET6;
+
+	capnet = create_network_service();
+
+	/* Tighten: only AF_INET6 allowed under name2addr. */
+	limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_name2addr_family(limit, &family, 1);
+	ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+	ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+	    ENOTCAPABLE);
+
+	/* Replacement omits "family"; must be rejected. */
+	limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+	ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+	ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) ==
+	    ENOTCAPABLE);
+
+	cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_name2addr_partial_drops_hosts);
+ATF_TC_HEAD(capnet__limits_name2addr_partial_drops_hosts, tc)
+{
+	atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_name2addr_partial_drops_hosts, tc)
+{
+	cap_channel_t *capnet;
+	cap_net_limit_t *limit;
+	int family = AF_INET;
+
+	capnet = create_network_service();
+
+	/* Tighten: only TEST_DOMAIN_0 allowed. */
+	limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL);
+	ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+	ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, NULL) ==
+	    ENOTCAPABLE);
+
+	/* Replacement omits "hosts"; must be rejected. */
+	limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_name2addr_family(limit, &family, 1);
+	ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+	ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, NULL) ==
+	    ENOTCAPABLE);
+
+	cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_addr2name_partial_drops_family);
+ATF_TC_HEAD(capnet__limits_addr2name_partial_drops_family, tc)
+{
+	atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_addr2name_partial_drops_family, tc)
+{
+	cap_channel_t *capnet;
+	cap_net_limit_t *limit;
+	struct sockaddr_in ipaddrv4;
+	int family = AF_INET6;
+
+	capnet = create_network_service();
+
+	memset(&ipaddrv4, 0, sizeof(ipaddrv4));
+	ipaddrv4.sin_family = AF_INET;
+	inet_pton(AF_INET, TEST_IPV4, &ipaddrv4.sin_addr);
+
+	/* Tighten: only AF_INET6 allowed under addr2name. */
+	limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_addr2name_family(limit, &family, 1);
+	ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+	ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+	    ENOTCAPABLE);
+
+	/* Replacement omits "family". Must be rejected. */
+	limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4,
+	    sizeof(ipaddrv4));
+	ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+	ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) ==
+	    ENOTCAPABLE);
+
+	cap_close(capnet);
+}
+
+ATF_TC(capnet__limits_addr2name_partial_drops_sockaddr);
+ATF_TC_HEAD(capnet__limits_addr2name_partial_drops_sockaddr, tc)
+{
+	atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_addr2name_partial_drops_sockaddr, tc)
+{
+	cap_channel_t *capnet;
+	cap_net_limit_t *limit;
+	struct sockaddr_in6 ipaddrv6;
+	int family = AF_INET6;
+
+	capnet = create_network_service();
+
+	memset(&ipaddrv6, 0, sizeof(ipaddrv6));
+	ipaddrv6.sin6_family = AF_INET6;
+	inet_pton(AF_INET6, TEST_IPV6, &ipaddrv6.sin6_addr);
+
+	/* Tighten: only TEST_IPV6 allowed under addr2name. */
+	limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv6,
+	    sizeof(ipaddrv6));
+	ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+	/* Replacement omits "sockaddr". Must be rejected. */
+	limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_addr2name_family(limit, &family, 1);
+	ATF_REQUIRE(cap_net_limit(limit) != 0);
+
+	cap_close(capnet);
+}
+
+/*
+ * The public helpers drop empty sublimits during pack, so the empty-{}
+ * variant is only reachable via libnv + cap_limit_set() directly.
+ */
+ATF_TC(capnet__limits_connect_partial_drops_sockaddr);
+ATF_TC_HEAD(capnet__limits_connect_partial_drops_sockaddr, tc)
+{
+	atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_connect_partial_drops_sockaddr, tc)
+{
+	cap_channel_t *capnet;
+	cap_net_limit_t *limit;
+	struct sockaddr_in ipv4;
+	nvlist_t *lnvl;
+
+	capnet = create_network_service();
+
+	memset(&ipv4, 0, sizeof(ipv4));
+	ipv4.sin_family = AF_INET;
+	ipv4.sin_port = htons(TEST_PORT);
+	inet_pton(AF_INET, TEST_IPV4, &ipv4.sin_addr);
+
+	/* Tighten: only TEST_IPV4:TEST_PORT allowed under connect. */
+	limit = cap_net_limit_init(capnet, CAPNET_CONNECT);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_connect(limit, (struct sockaddr *)&ipv4, sizeof(ipv4));
+	ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+	ATF_REQUIRE(test_connect(capnet, TEST_IPV4, TEST_PORT) == 0);
+	ATF_REQUIRE(test_connect(capnet, "8.8.8.8", TEST_PORT) == ENOTCAPABLE);
+
+	/* Build connect={} (no sockaddr subkey) directly. Must be rejected. */
+	lnvl = nvlist_create(0);
+	nvlist_add_number(lnvl, "mode", CAPNET_CONNECT);
+	nvlist_add_nvlist(lnvl, "connect", nvlist_create(0));
+	ATF_REQUIRE(cap_limit_set(capnet, lnvl) != 0);
+
+	ATF_REQUIRE(test_connect(capnet, "8.8.8.8", TEST_PORT) == ENOTCAPABLE);
+
+	cap_close(capnet);
+}
+
+/*
+ * The public helpers drop empty sublimits during pack, so the empty-{}
+ * variant is only reachable via libnv + cap_limit_set() directly.
+ */
+ATF_TC(capnet__limits_bind_partial_drops_sockaddr);
+ATF_TC_HEAD(capnet__limits_bind_partial_drops_sockaddr, tc)
+{
+	atf_tc_set_md_var(tc, "require.config", "allow_network_access");
+}
+ATF_TC_BODY(capnet__limits_bind_partial_drops_sockaddr, tc)
+{
+	cap_channel_t *capnet;
+	cap_net_limit_t *limit;
+	struct sockaddr_in ipv4;
+	nvlist_t *lnvl;
+
+	capnet = create_network_service();
+
+	memset(&ipv4, 0, sizeof(ipv4));
+	ipv4.sin_family = AF_INET;
+	inet_pton(AF_INET, TEST_BIND_IPV4, &ipv4.sin_addr);
+
+	/* Tighten: only TEST_BIND_IPV4 allowed under bind. */
+	limit = cap_net_limit_init(capnet, CAPNET_BIND);
+	ATF_REQUIRE(limit != NULL);
+	cap_net_limit_bind(limit, (struct sockaddr *)&ipv4, sizeof(ipv4));
+	ATF_REQUIRE(cap_net_limit(limit) == 0);
+
+	ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == 0);
+	ATF_REQUIRE(test_bind(capnet, "127.0.0.2") == ENOTCAPABLE);
+
+	/* Build bind={} (no sockaddr subkey) directly. Must be rejected. */
+	lnvl = nvlist_create(0);
+	nvlist_add_number(lnvl, "mode", CAPNET_BIND);
+	nvlist_add_nvlist(lnvl, "bind", nvlist_create(0));
+	ATF_REQUIRE(cap_limit_set(capnet, lnvl) != 0);
+
+	ATF_REQUIRE(test_bind(capnet, "127.0.0.2") == ENOTCAPABLE);
+
+	cap_close(capnet);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 
@@ -1483,5 +1711,12 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, capnet__limits_connecttodns);
 	ATF_TP_ADD_TC(tp, capnet__limits_deprecated_connecttodns);
 
+	ATF_TP_ADD_TC(tp, capnet__limits_name2addr_partial_drops_family);
+	ATF_TP_ADD_TC(tp, capnet__limits_name2addr_partial_drops_hosts);
+	ATF_TP_ADD_TC(tp, capnet__limits_addr2name_partial_drops_family);
+	ATF_TP_ADD_TC(tp, capnet__limits_addr2name_partial_drops_sockaddr);
+	ATF_TP_ADD_TC(tp, capnet__limits_connect_partial_drops_sockaddr);
+	ATF_TP_ADD_TC(tp, capnet__limits_bind_partial_drops_sockaddr);
+
 	return (atf_no_error());
 }