Loading Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -292,6 +292,9 @@ cc_library { "libssl", "libstatssocket", ], runtime_libs: [ "libcom.android.tethering.dns_helper", ], header_libs: [ "libnetdbinder_utils_headers", ], Loading DnsProxyListener.cpp +69 −4 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <arpa/inet.h> #include <dirent.h> #include <dlfcn.h> #include <linux/if.h> #include <math.h> #include <net/if.h> Loading Loading @@ -661,6 +662,54 @@ std::string makeThreadName(unsigned netId, uint32_t uid) { return fmt::format("Dns_{}_{}", netId, multiuser_get_app_id(uid)); } typedef int (*InitFn)(); typedef int (*IsUidBlockedFn)(uid_t, bool); IsUidBlockedFn ADnsHelper_isUidNetworkingBlocked; IsUidBlockedFn resolveIsUidNetworkingBlockedFn() { // Related BPF maps were mainlined from T. if (!isAtLeastT()) return nullptr; // TODO: Check whether it is safe to shared link the .so without using dlopen when the carrier // APEX module (tethering) is fully released. void* handle = dlopen("libcom.android.tethering.dns_helper.so", RTLD_NOW | RTLD_LOCAL); if (!handle) { LOG(WARNING) << __func__ << ": " << dlerror(); return nullptr; } InitFn ADnsHelper_init = reinterpret_cast<InitFn>(dlsym(handle, "ADnsHelper_init")); if (!ADnsHelper_init) { LOG(ERROR) << __func__ << ": " << dlerror(); abort(); } const int ret = (*ADnsHelper_init)(); if (ret) { LOG(ERROR) << __func__ << ": ADnsHelper_init failed " << strerror(-ret); abort(); } IsUidBlockedFn f = reinterpret_cast<IsUidBlockedFn>(dlsym(handle, "ADnsHelper_isUidNetworkingBlocked")); if (!f) { LOG(ERROR) << __func__ << ": " << dlerror(); abort(); } return f; } bool isUidNetworkingBlocked(uid_t uid, unsigned netId) { if (!ADnsHelper_isUidNetworkingBlocked) return false; // The enforceDnsUid is an OEM feature that sets DNS packet with AID_DNS instead of the // application's UID. Its DNS packets are not subject to certain network restriction features. if (resolv_is_enforceDnsUid_enabled_network(netId)) return false; // TODO: Pass metered information from CS to DNS resolver and replace the hardcode value. return (*ADnsHelper_isUidNetworkingBlocked)(uid, /*metered=*/false) == 1; } } // namespace DnsProxyListener::DnsProxyListener() : FrameworkListener(SOCKET_NAME) { Loading @@ -678,6 +727,8 @@ DnsProxyListener::DnsProxyListener() : FrameworkListener(SOCKET_NAME) { mGetDnsNetIdCommand = std::make_unique<GetDnsNetIdCommand>(); registerCmd(mGetDnsNetIdCommand.get()); ADnsHelper_isUidNetworkingBlocked = resolveIsUidNetworkingBlockedFn(); } void DnsProxyListener::Handler::spawn() { Loading Loading @@ -853,7 +904,10 @@ void DnsProxyListener::GetAddrInfoHandler::run() { int32_t rv = 0; NetworkDnsEventReported event; initDnsEvent(&event, mNetContext); if (startQueryLimiter(uid)) { if (isUidNetworkingBlocked(mNetContext.uid, mNetContext.dns_netid)) { LOG(INFO) << "GetAddrInfoHandler::run: network access blocked"; rv = EAI_FAIL; } else if (startQueryLimiter(uid)) { const char* host = mHost.starts_with('^') ? nullptr : mHost.c_str(); const char* service = mService.starts_with('^') ? nullptr : mService.c_str(); if (evaluate_domain_name(mNetContext, host)) { Loading Loading @@ -1062,11 +1116,15 @@ void DnsProxyListener::ResNSendHandler::run() { int ansLen = -1; NetworkDnsEventReported event; initDnsEvent(&event, mNetContext); if (startQueryLimiter(uid)) { if (isUidNetworkingBlocked(mNetContext.uid, mNetContext.dns_netid)) { LOG(INFO) << "ResNSendHandler::run: network access blocked"; ansLen = -ECONNREFUSED; } else if (startQueryLimiter(uid)) { if (evaluate_domain_name(mNetContext, rr_name.c_str())) { ansLen = resolv_res_nsend(&mNetContext, std::span(msg.data(), msgLen), ansBuf, &rcode, static_cast<ResNsendFlags>(mFlags), &event); } else { // TODO(b/307048182): It should return -errno. ansLen = -EAI_SYSTEM; } endQueryLimiter(uid); Loading Loading @@ -1262,7 +1320,10 @@ void DnsProxyListener::GetHostByNameHandler::run() { int32_t rv = 0; NetworkDnsEventReported event; initDnsEvent(&event, mNetContext); if (startQueryLimiter(uid)) { if (isUidNetworkingBlocked(mNetContext.uid, mNetContext.dns_netid)) { LOG(INFO) << "GetHostByNameHandler::run: network access blocked"; rv = EAI_FAIL; } else if (startQueryLimiter(uid)) { const char* name = mName.starts_with('^') ? nullptr : mName.c_str(); if (evaluate_domain_name(mNetContext, name)) { rv = resolv_gethostbyname(name, mAf, &hbuf, tmpbuf, sizeof tmpbuf, &mNetContext, &hp, Loading Loading @@ -1421,7 +1482,11 @@ void DnsProxyListener::GetHostByAddrHandler::run() { int32_t rv = 0; NetworkDnsEventReported event; initDnsEvent(&event, mNetContext); if (startQueryLimiter(uid)) { if (isUidNetworkingBlocked(mNetContext.uid, mNetContext.dns_netid)) { LOG(INFO) << "GetHostByAddrHandler::run: network access blocked"; rv = EAI_FAIL; } else if (startQueryLimiter(uid)) { // From Android U, evaluate_domain_name() is not only for OEM customization, but also tells // DNS resolver whether the UID can send DNS on the specified network. The function needs // to be called even when there is no domain name to evaluate (GetHostByAddr). This is Loading tests/resolv_integration_test.cpp +2 −2 Original line number Diff line number Diff line Loading @@ -4505,9 +4505,9 @@ TEST_F(ResolverTest, GetAddrinfo_BlockDnsQueryWithUidRule) { const char* hname; const int expectedErrorCode; } kTestData[] = { {host_name, EAI_NODATA}, {host_name, isAtLeastT() ? EAI_FAIL : EAI_NODATA}, // To test the query with search domain. {"howdy", EAI_AGAIN}, {"howdy", isAtLeastT() ? EAI_FAIL : EAI_AGAIN}, }; INetd* netdService = mDnsClient.netdService(); Loading tests/resolv_private_dns_test.cpp +57 −0 Original line number Diff line number Diff line Loading @@ -528,6 +528,63 @@ TEST_P(TransportParameterizedTest, MdnsGetAddrInfo_fallback) { } } TEST_P(TransportParameterizedTest, BlockDnsQueryWithUidRule) { SKIP_IF_BEFORE_T; constexpr char ptr_name[] = "v4v6.example.com."; // PTR record for IPv6 address 2001:db8::102:304 constexpr char ptr_addr_v6[] = "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."; const DnsRecord r = {ptr_addr_v6, ns_type::ns_t_ptr, ptr_name}; dns.addMapping(r.host_name, r.type, r.addr); dot_backend.addMapping(r.host_name, r.type, r.addr); doh_backend.addMapping(r.host_name, r.type, r.addr); const auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel(); ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel)); if (testParamHasDoh()) EXPECT_TRUE(WaitForDohValidationSuccess(test::kDefaultListenAddr)); if (testParamHasDot()) EXPECT_TRUE(WaitForDotValidationSuccess(test::kDefaultListenAddr)); // This waiting time is expected to avoid that the DoH validation event interferes other tests. if (!testParamHasDoh()) waitForDohValidationFailed(); // Have the test independent of the number of sent queries in private DNS validation, because // the DnsResolver can send either 1 or 2 queries in DoT validation. if (testParamHasDoh()) { doh.clearQueries(); } if (testParamHasDot()) { EXPECT_TRUE(dot.waitForQueries(1)); dot.clearQueries(); } dns.clearQueries(); // Block TEST_UID's network access ScopeBlockedUIDRule scopeBlockUidRule(mDnsClient.netdService(), TEST_UID); // getaddrinfo should fail const addrinfo hints = {.ai_socktype = SOCK_DGRAM}; EXPECT_FALSE(safe_getaddrinfo(kQueryHostname, nullptr, &hints)); // gethostbyname should fail EXPECT_FALSE(gethostbyname(kQueryHostname)); // gethostbyaddr should fail in6_addr v6addr; inet_pton(AF_INET6, "2001:db8::102:304", &v6addr); EXPECT_FALSE(gethostbyaddr(&v6addr, sizeof(v6addr), AF_INET6)); // resNetworkQuery should fail int fd = resNetworkQuery(TEST_NETID, kQueryHostname, ns_c_in, ns_t_aaaa, 0); EXPECT_TRUE(fd != -1); uint8_t buf[MAXPACKET] = {}; int rcode; EXPECT_EQ(-ECONNREFUSED, getAsyncResponse(fd, &rcode, buf, MAXPACKET)); expectQueries(0 /* dns */, 0 /* dot */, 0 /* doh */); } class PrivateDnsDohTest : public BasePrivateDnsTest { protected: void SetUp() override { Loading tests/resolv_test_utils.h +8 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ #include <netdutils/InternetAddresses.h> #include "dns_responder/dns_responder.h" #include "util.h" class ScopeBlockedUIDRule { using INetd = aidl::android::net::INetd; Loading Loading @@ -402,3 +403,10 @@ android::netdutils::ScopedAddrinfo safe_getaddrinfo(const char* node, const char void SetMdnsRoute(); void RemoveMdnsRoute(); #define SKIP_IF_BEFORE_T \ do { \ if (!isAtLeastT()) { \ GTEST_SKIP() << "Skipping test because SDK version is less than T."; \ } \ } while (0) Loading
Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -292,6 +292,9 @@ cc_library { "libssl", "libstatssocket", ], runtime_libs: [ "libcom.android.tethering.dns_helper", ], header_libs: [ "libnetdbinder_utils_headers", ], Loading
DnsProxyListener.cpp +69 −4 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <arpa/inet.h> #include <dirent.h> #include <dlfcn.h> #include <linux/if.h> #include <math.h> #include <net/if.h> Loading Loading @@ -661,6 +662,54 @@ std::string makeThreadName(unsigned netId, uint32_t uid) { return fmt::format("Dns_{}_{}", netId, multiuser_get_app_id(uid)); } typedef int (*InitFn)(); typedef int (*IsUidBlockedFn)(uid_t, bool); IsUidBlockedFn ADnsHelper_isUidNetworkingBlocked; IsUidBlockedFn resolveIsUidNetworkingBlockedFn() { // Related BPF maps were mainlined from T. if (!isAtLeastT()) return nullptr; // TODO: Check whether it is safe to shared link the .so without using dlopen when the carrier // APEX module (tethering) is fully released. void* handle = dlopen("libcom.android.tethering.dns_helper.so", RTLD_NOW | RTLD_LOCAL); if (!handle) { LOG(WARNING) << __func__ << ": " << dlerror(); return nullptr; } InitFn ADnsHelper_init = reinterpret_cast<InitFn>(dlsym(handle, "ADnsHelper_init")); if (!ADnsHelper_init) { LOG(ERROR) << __func__ << ": " << dlerror(); abort(); } const int ret = (*ADnsHelper_init)(); if (ret) { LOG(ERROR) << __func__ << ": ADnsHelper_init failed " << strerror(-ret); abort(); } IsUidBlockedFn f = reinterpret_cast<IsUidBlockedFn>(dlsym(handle, "ADnsHelper_isUidNetworkingBlocked")); if (!f) { LOG(ERROR) << __func__ << ": " << dlerror(); abort(); } return f; } bool isUidNetworkingBlocked(uid_t uid, unsigned netId) { if (!ADnsHelper_isUidNetworkingBlocked) return false; // The enforceDnsUid is an OEM feature that sets DNS packet with AID_DNS instead of the // application's UID. Its DNS packets are not subject to certain network restriction features. if (resolv_is_enforceDnsUid_enabled_network(netId)) return false; // TODO: Pass metered information from CS to DNS resolver and replace the hardcode value. return (*ADnsHelper_isUidNetworkingBlocked)(uid, /*metered=*/false) == 1; } } // namespace DnsProxyListener::DnsProxyListener() : FrameworkListener(SOCKET_NAME) { Loading @@ -678,6 +727,8 @@ DnsProxyListener::DnsProxyListener() : FrameworkListener(SOCKET_NAME) { mGetDnsNetIdCommand = std::make_unique<GetDnsNetIdCommand>(); registerCmd(mGetDnsNetIdCommand.get()); ADnsHelper_isUidNetworkingBlocked = resolveIsUidNetworkingBlockedFn(); } void DnsProxyListener::Handler::spawn() { Loading Loading @@ -853,7 +904,10 @@ void DnsProxyListener::GetAddrInfoHandler::run() { int32_t rv = 0; NetworkDnsEventReported event; initDnsEvent(&event, mNetContext); if (startQueryLimiter(uid)) { if (isUidNetworkingBlocked(mNetContext.uid, mNetContext.dns_netid)) { LOG(INFO) << "GetAddrInfoHandler::run: network access blocked"; rv = EAI_FAIL; } else if (startQueryLimiter(uid)) { const char* host = mHost.starts_with('^') ? nullptr : mHost.c_str(); const char* service = mService.starts_with('^') ? nullptr : mService.c_str(); if (evaluate_domain_name(mNetContext, host)) { Loading Loading @@ -1062,11 +1116,15 @@ void DnsProxyListener::ResNSendHandler::run() { int ansLen = -1; NetworkDnsEventReported event; initDnsEvent(&event, mNetContext); if (startQueryLimiter(uid)) { if (isUidNetworkingBlocked(mNetContext.uid, mNetContext.dns_netid)) { LOG(INFO) << "ResNSendHandler::run: network access blocked"; ansLen = -ECONNREFUSED; } else if (startQueryLimiter(uid)) { if (evaluate_domain_name(mNetContext, rr_name.c_str())) { ansLen = resolv_res_nsend(&mNetContext, std::span(msg.data(), msgLen), ansBuf, &rcode, static_cast<ResNsendFlags>(mFlags), &event); } else { // TODO(b/307048182): It should return -errno. ansLen = -EAI_SYSTEM; } endQueryLimiter(uid); Loading Loading @@ -1262,7 +1320,10 @@ void DnsProxyListener::GetHostByNameHandler::run() { int32_t rv = 0; NetworkDnsEventReported event; initDnsEvent(&event, mNetContext); if (startQueryLimiter(uid)) { if (isUidNetworkingBlocked(mNetContext.uid, mNetContext.dns_netid)) { LOG(INFO) << "GetHostByNameHandler::run: network access blocked"; rv = EAI_FAIL; } else if (startQueryLimiter(uid)) { const char* name = mName.starts_with('^') ? nullptr : mName.c_str(); if (evaluate_domain_name(mNetContext, name)) { rv = resolv_gethostbyname(name, mAf, &hbuf, tmpbuf, sizeof tmpbuf, &mNetContext, &hp, Loading Loading @@ -1421,7 +1482,11 @@ void DnsProxyListener::GetHostByAddrHandler::run() { int32_t rv = 0; NetworkDnsEventReported event; initDnsEvent(&event, mNetContext); if (startQueryLimiter(uid)) { if (isUidNetworkingBlocked(mNetContext.uid, mNetContext.dns_netid)) { LOG(INFO) << "GetHostByAddrHandler::run: network access blocked"; rv = EAI_FAIL; } else if (startQueryLimiter(uid)) { // From Android U, evaluate_domain_name() is not only for OEM customization, but also tells // DNS resolver whether the UID can send DNS on the specified network. The function needs // to be called even when there is no domain name to evaluate (GetHostByAddr). This is Loading
tests/resolv_integration_test.cpp +2 −2 Original line number Diff line number Diff line Loading @@ -4505,9 +4505,9 @@ TEST_F(ResolverTest, GetAddrinfo_BlockDnsQueryWithUidRule) { const char* hname; const int expectedErrorCode; } kTestData[] = { {host_name, EAI_NODATA}, {host_name, isAtLeastT() ? EAI_FAIL : EAI_NODATA}, // To test the query with search domain. {"howdy", EAI_AGAIN}, {"howdy", isAtLeastT() ? EAI_FAIL : EAI_AGAIN}, }; INetd* netdService = mDnsClient.netdService(); Loading
tests/resolv_private_dns_test.cpp +57 −0 Original line number Diff line number Diff line Loading @@ -528,6 +528,63 @@ TEST_P(TransportParameterizedTest, MdnsGetAddrInfo_fallback) { } } TEST_P(TransportParameterizedTest, BlockDnsQueryWithUidRule) { SKIP_IF_BEFORE_T; constexpr char ptr_name[] = "v4v6.example.com."; // PTR record for IPv6 address 2001:db8::102:304 constexpr char ptr_addr_v6[] = "4.0.3.0.2.0.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."; const DnsRecord r = {ptr_addr_v6, ns_type::ns_t_ptr, ptr_name}; dns.addMapping(r.host_name, r.type, r.addr); dot_backend.addMapping(r.host_name, r.type, r.addr); doh_backend.addMapping(r.host_name, r.type, r.addr); const auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel(); ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel)); if (testParamHasDoh()) EXPECT_TRUE(WaitForDohValidationSuccess(test::kDefaultListenAddr)); if (testParamHasDot()) EXPECT_TRUE(WaitForDotValidationSuccess(test::kDefaultListenAddr)); // This waiting time is expected to avoid that the DoH validation event interferes other tests. if (!testParamHasDoh()) waitForDohValidationFailed(); // Have the test independent of the number of sent queries in private DNS validation, because // the DnsResolver can send either 1 or 2 queries in DoT validation. if (testParamHasDoh()) { doh.clearQueries(); } if (testParamHasDot()) { EXPECT_TRUE(dot.waitForQueries(1)); dot.clearQueries(); } dns.clearQueries(); // Block TEST_UID's network access ScopeBlockedUIDRule scopeBlockUidRule(mDnsClient.netdService(), TEST_UID); // getaddrinfo should fail const addrinfo hints = {.ai_socktype = SOCK_DGRAM}; EXPECT_FALSE(safe_getaddrinfo(kQueryHostname, nullptr, &hints)); // gethostbyname should fail EXPECT_FALSE(gethostbyname(kQueryHostname)); // gethostbyaddr should fail in6_addr v6addr; inet_pton(AF_INET6, "2001:db8::102:304", &v6addr); EXPECT_FALSE(gethostbyaddr(&v6addr, sizeof(v6addr), AF_INET6)); // resNetworkQuery should fail int fd = resNetworkQuery(TEST_NETID, kQueryHostname, ns_c_in, ns_t_aaaa, 0); EXPECT_TRUE(fd != -1); uint8_t buf[MAXPACKET] = {}; int rcode; EXPECT_EQ(-ECONNREFUSED, getAsyncResponse(fd, &rcode, buf, MAXPACKET)); expectQueries(0 /* dns */, 0 /* dot */, 0 /* doh */); } class PrivateDnsDohTest : public BasePrivateDnsTest { protected: void SetUp() override { Loading
tests/resolv_test_utils.h +8 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ #include <netdutils/InternetAddresses.h> #include "dns_responder/dns_responder.h" #include "util.h" class ScopeBlockedUIDRule { using INetd = aidl::android::net::INetd; Loading Loading @@ -402,3 +403,10 @@ android::netdutils::ScopedAddrinfo safe_getaddrinfo(const char* node, const char void SetMdnsRoute(); void RemoveMdnsRoute(); #define SKIP_IF_BEFORE_T \ do { \ if (!isAtLeastT()) { \ GTEST_SKIP() << "Skipping test because SDK version is less than T."; \ } \ } while (0)