Add a resolver option to enforce AID_DNS on query
The default behavior is that plaintext DNS queries are sent by the
application's UID using fchown(). DoT are sent with an UID of AID_DNS
This option control the plaintext uid of DNS query.
Bug: 154910763
Test: atest
Change-Id: Iada5d850d8bb9d7b0ad46f5c28a1fff22c7d11a6
Merged-In: Iada5d850d8bb9d7b0ad46f5c28a1fff22c7d11a6
diff --git a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
index a13216a..d55ae46 100644
--- a/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
+++ b/aidl_api/dnsresolver_aidl_interface/current/android/net/ResolverOptionsParcel.aidl
@@ -20,4 +20,5 @@
parcelable ResolverOptionsParcel {
android.net.ResolverHostsParcel[] hosts = {};
int tcMode = 0;
+ boolean enforceDnsUid = false;
}
diff --git a/binder/android/net/ResolverOptionsParcel.aidl b/binder/android/net/ResolverOptionsParcel.aidl
index 8e435de..bca9fb6 100644
--- a/binder/android/net/ResolverOptionsParcel.aidl
+++ b/binder/android/net/ResolverOptionsParcel.aidl
@@ -43,4 +43,18 @@
* Other values are invalid.
*/
int tcMode = 0;
+
+ /**
+ * The default behavior is that plaintext DNS queries are sent by the application's UID using
+ * fchown(). DoT are sent with an UID of AID_DNS. This option control the plaintext uid of DNS
+ * query.
+ * Setting this option to true decreases battery life because it results in the device sending
+ * UDP DNS queries even if the app that made the DNS lookup does not have network access.
+ * Anecdotal data from the field suggests that about 15% of DNS lookups are in this category.
+ * This option also results in data usage for UDP DNS queries being attributed to the OS instead
+ * of to the requesting app.
+ * false: set application uid on DNS sockets (default)
+ * true: set AID_DNS on DNS sockets
+ */
+ boolean enforceDnsUid = false;
}
diff --git a/res_cache.cpp b/res_cache.cpp
index 07985d1..560de93 100644
--- a/res_cache.cpp
+++ b/res_cache.cpp
@@ -1003,6 +1003,7 @@
// If resolverParams.hosts is empty, the existing customized table will be erased.
HostMapping customizedTable = {};
int tc_mode = aidl::android::net::IDnsResolver::TC_MODE_DEFAULT;
+ bool enforceDnsUid = false;
std::vector<int32_t> transportTypes;
};
@@ -1645,6 +1646,7 @@
return -EINVAL;
}
netconfig->tc_mode = resolverOptions.tcMode;
+ netconfig->enforceDnsUid = resolverOptions.enforceDnsUid;
netconfig->transportTypes = transportTypes;
@@ -1683,6 +1685,7 @@
statp->nsaddrs = info->nameserverSockAddrs;
statp->search_domains = info->search_domains;
statp->tc_mode = info->tc_mode;
+ statp->enforce_dns_uid = info->enforceDnsUid;
}
/* Resolver reachability statistics. */
diff --git a/res_send.cpp b/res_send.cpp
index 6646310..2db8220 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -707,7 +707,8 @@
return -1;
}
}
- resolv_tag_socket(statp->tcp_nssock, statp->uid, statp->pid);
+ const uid_t uid = statp->enforce_dns_uid ? AID_DNS : statp->uid;
+ resolv_tag_socket(statp->tcp_nssock, uid, statp->pid);
if (statp->_mark != MARK_UNSET) {
if (setsockopt(statp->tcp_nssock, SOL_SOCKET, SO_MARK, &statp->_mark,
sizeof(statp->_mark)) < 0) {
@@ -1018,7 +1019,8 @@
}
}
- resolv_tag_socket(statp->nssocks[*ns], statp->uid, statp->pid);
+ const uid_t uid = statp->enforce_dns_uid ? AID_DNS : statp->uid;
+ resolv_tag_socket(statp->nssocks[*ns], uid, statp->pid);
if (statp->_mark != MARK_UNSET) {
if (setsockopt(statp->nssocks[*ns], SOL_SOCKET, SO_MARK, &(statp->_mark),
sizeof(statp->_mark)) < 0) {
diff --git a/resolv_cache.h b/resolv_cache.h
index 49c7f56..d8a3afc 100644
--- a/resolv_cache.h
+++ b/resolv_cache.h
@@ -84,7 +84,8 @@
const std::vector<std::string>& domains, const res_params& params,
const aidl::android::net::ResolverOptionsParcel& resolverOptions =
{{} /* hosts */,
- aidl::android::net::IDnsResolver::TC_MODE_DEFAULT},
+ aidl::android::net::IDnsResolver::TC_MODE_DEFAULT,
+ false /* enforceDnsUid */},
const std::vector<int32_t>& transportTypes = {});
// Creates the cache associated with the given network.
diff --git a/resolv_private.h b/resolv_private.h
index 61d87f2..f1b667e 100644
--- a/resolv_private.h
+++ b/resolv_private.h
@@ -112,6 +112,7 @@
android::net::NetworkDnsEventReported* event;
uint32_t netcontext_flags;
int tc_mode = 0;
+ bool enforce_dns_uid = false;
// clang-format on
};
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index 236b031..95b5a5c 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -4018,6 +4018,71 @@
EXPECT_EQ(-ECONNREFUSED, res);
}
+TEST_F(ResolverTest, EnforceDnsUid) {
+ SKIP_IF_BPF_NOT_SUPPORTED;
+
+ constexpr char listen_addr1[] = "127.0.0.4";
+ constexpr char listen_addr2[] = "::1";
+ constexpr char host_name[] = "howdy.example.com.";
+ const std::vector<DnsRecord> records = {
+ {host_name, ns_type::ns_t_a, "1.2.3.4"},
+ {host_name, ns_type::ns_t_aaaa, "::1.2.3.4"},
+ };
+ INetd* netdService = mDnsClient.netdService();
+
+ test::DNSResponder dns1(listen_addr1);
+ test::DNSResponder dns2(listen_addr2);
+ StartDns(dns1, records);
+ StartDns(dns2, records);
+
+ // switch uid of DNS queries from applications to AID_DNS
+ ResolverParamsParcel parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+ parcel.servers = {listen_addr1, listen_addr2};
+ ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(parcel).isOk());
+
+ uint8_t buf[MAXPACKET] = {};
+ int rcode;
+ {
+ ScopeBlockedUIDRule scopeBlockUidRule(netdService, TEST_UID);
+ // Dns Queries should be blocked
+ int fd1 = resNetworkQuery(TEST_NETID, host_name, ns_c_in, ns_t_a, 0);
+ int fd2 = resNetworkQuery(TEST_NETID, host_name, ns_c_in, ns_t_aaaa, 0);
+ EXPECT_TRUE(fd1 != -1);
+ EXPECT_TRUE(fd2 != -1);
+
+ int res = getAsyncResponse(fd2, &rcode, buf, MAXPACKET);
+ EXPECT_EQ(-ECONNREFUSED, res);
+
+ memset(buf, 0, MAXPACKET);
+ res = getAsyncResponse(fd1, &rcode, buf, MAXPACKET);
+ EXPECT_EQ(-ECONNREFUSED, res);
+ }
+
+ parcel.resolverOptions.enforceDnsUid = true;
+ ASSERT_TRUE(mDnsClient.resolvService()->setResolverConfiguration(parcel).isOk());
+ {
+ ScopeBlockedUIDRule scopeBlockUidRule(netdService, TEST_UID);
+ // Dns Queries should NOT be blocked
+ int fd1 = resNetworkQuery(TEST_NETID, host_name, ns_c_in, ns_t_a, 0);
+ int fd2 = resNetworkQuery(TEST_NETID, host_name, ns_c_in, ns_t_aaaa, 0);
+ EXPECT_TRUE(fd1 != -1);
+ EXPECT_TRUE(fd2 != -1);
+
+ int res = getAsyncResponse(fd2, &rcode, buf, MAXPACKET);
+ EXPECT_EQ("::1.2.3.4", toString(buf, res, AF_INET6));
+
+ memset(buf, 0, MAXPACKET);
+ res = getAsyncResponse(fd1, &rcode, buf, MAXPACKET);
+ EXPECT_EQ("1.2.3.4", toString(buf, res, AF_INET));
+
+ // @TODO: So far we know that uid of DNS queries are no more set to DNS requester. But we
+ // don't check if they are actually being set to AID_DNS, because system uids are always
+ // allowed in bpf_owner_match(). Audit by firewallSetUidRule(AID_DNS) + sending queries is
+ // infeasible. Fix it if the behavior of bpf_owner_match() is changed in the future, or if
+ // we have better idea to deal with this.
+ }
+}
+
namespace {
const std::string kDotConnectTimeoutMsFlag(