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(