{"sha":"5749d7822f0bf18e2eb0ea1f10d7b7181456ad75","node_id":"C_kwDOABA-c9oAKDU3NDlkNzgyMmYwYmYxOGUyZWIwZWExZjEwZDdiNzE4MTQ1NmFkNzU","commit":{"author":{"name":"Norman Maurer","email":"norman_maurer@apple.com","date":"2026-06-02T10:09:33Z"},"committer":{"name":"GitHub","email":"noreply@github.com","date":"2026-06-02T10:09:33Z"},"message":"DNS: Only cache CNAME if part of the queried domain (#16873)\n\nMotivation:\n\nWe should only cache the CNAME if it is part of the queried domain to\nensure the name server is really authoritive for it and not provide us\nincorrect data.\n\nModifications:\n\n- Only cache if CNAME is part of the queried domain\n- Add unit test\n\nResult:\n\nNo more DNS Cache Poisoning (Bailiwick Bypass) possible","tree":{"sha":"3f3231debe6fd501651e6f39289c1b1255408ef2","url":"https://api.github.com/repos/netty/netty/git/trees/3f3231debe6fd501651e6f39289c1b1255408ef2"},"url":"https://api.github.com/repos/netty/netty/git/commits/5749d7822f0bf18e2eb0ea1f10d7b7181456ad75","comment_count":0,"verification":{"verified":true,"reason":"valid","signature":"-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJqHqvdCRC1aQ7uu5UhlAAA/O8QAF2gfdI1vH6b5CFJqS0wgR4/\nrzo2g8vu9TI+LtKgUCWFAiLGzh9PrfzNSw2aFH93ZTmrkxqjzhljon1ko9sIK2Yn\nUk0dVUMhruHe3/4ZCVv0WpAHMVgPKO8jKH4vnjedXkwJrkjeIfNar2iM6lMJ1ji5\n2DD4XkeQd+WJsrUhOOit4sidray6p3wXl16Z8ZenbREzxZpxlWCXIBlFk8VAUKg8\nKGFpVCyzr8yEILzbMxBP+UmcNUYU1nI9TdTrWFtgZZxMQ2v+fRbG0Q9pMpPnieMT\nCJjpgVMH+RKbG46YRQM9hi1DUP64ZYv05LCKn9KMrSWcUZg3nW/dzHeG/ErzabGy\nk0zjF2EpXuauskPM2JP7jVrnTbmVYmKXOU5FK/RB1O9kZxtZ7bZtY1CuhNlL6EJl\nbEYkv5TaGOOaLAZhNj+H92sZ8X/tSpzRgQa7X+TbGmS5N51Iv+h0ehibhwMkuvUk\njnHB36NbQLu438v8rCQCkIrDiqG57pzbLlX0noUmse1kqgNUg1pRBm4a+OMIK582\nS73aXGejJ/4LNuvTmpTsil2sulcc2YCBiLb0A3lTcTrcQzw/xin3z4rJB0un1BeM\nAeLaRJfMB2upMNhzf+oAnHX0bGXIYMcC82jJ7+NhmjYZAWA1opbR/xdKzYMQbKWW\n+7SGYm1NtKnJxuI2ve0Y\n=E0GE\n-----END PGP SIGNATURE-----\n","payload":"tree 3f3231debe6fd501651e6f39289c1b1255408ef2\nparent 829c885a45fd9f5ba43fe6caf296214b697366fa\nauthor Norman Maurer <norman_maurer@apple.com> 1780394973 +0200\ncommitter GitHub <noreply@github.com> 1780394973 +0200\n\nDNS: Only cache CNAME if part of the queried domain (#16873)\n\nMotivation:\n\nWe should only cache the CNAME if it is part of the queried domain to\nensure the name server is really authoritive for it and not provide us\nincorrect data.\n\nModifications:\n\n- Only cache if CNAME is part of the queried domain\n- Add unit test\n\nResult:\n\nNo more DNS Cache Poisoning (Bailiwick Bypass) possible","verified_at":"2026-06-02T10:09:34Z"}},"url":"https://api.github.com/repos/netty/netty/commits/5749d7822f0bf18e2eb0ea1f10d7b7181456ad75","html_url":"https://github.com/netty/netty/commit/5749d7822f0bf18e2eb0ea1f10d7b7181456ad75","comments_url":"https://api.github.com/repos/netty/netty/commits/5749d7822f0bf18e2eb0ea1f10d7b7181456ad75/comments","author":{"login":"normanmaurer","id":439362,"node_id":"MDQ6VXNlcjQzOTM2Mg==","avatar_url":"https://avatars.githubusercontent.com/u/439362?v=4","gravatar_id":"","url":"https://api.github.com/users/normanmaurer","html_url":"https://github.com/normanmaurer","followers_url":"https://api.github.com/users/normanmaurer/followers","following_url":"https://api.github.com/users/normanmaurer/following{/other_user}","gists_url":"https://api.github.com/users/normanmaurer/gists{/gist_id}","starred_url":"https://api.github.com/users/normanmaurer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/normanmaurer/subscriptions","organizations_url":"https://api.github.com/users/normanmaurer/orgs","repos_url":"https://api.github.com/users/normanmaurer/repos","events_url":"https://api.github.com/users/normanmaurer/events{/privacy}","received_events_url":"https://api.github.com/users/normanmaurer/received_events","type":"User","user_view_type":"public","site_admin":false},"committer":{"login":"web-flow","id":19864447,"node_id":"MDQ6VXNlcjE5ODY0NDQ3","avatar_url":"https://avatars.githubusercontent.com/u/19864447?v=4","gravatar_id":"","url":"https://api.github.com/users/web-flow","html_url":"https://github.com/web-flow","followers_url":"https://api.github.com/users/web-flow/followers","following_url":"https://api.github.com/users/web-flow/following{/other_user}","gists_url":"https://api.github.com/users/web-flow/gists{/gist_id}","starred_url":"https://api.github.com/users/web-flow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/web-flow/subscriptions","organizations_url":"https://api.github.com/users/web-flow/orgs","repos_url":"https://api.github.com/users/web-flow/repos","events_url":"https://api.github.com/users/web-flow/events{/privacy}","received_events_url":"https://api.github.com/users/web-flow/received_events","type":"User","user_view_type":"public","site_admin":false},"parents":[{"sha":"829c885a45fd9f5ba43fe6caf296214b697366fa","url":"https://api.github.com/repos/netty/netty/commits/829c885a45fd9f5ba43fe6caf296214b697366fa","html_url":"https://github.com/netty/netty/commit/829c885a45fd9f5ba43fe6caf296214b697366fa"}],"stats":{"total":92,"additions":87,"deletions":5},"files":[{"sha":"8d22bf786f480bfbc3c47576b83b22b1ca69c53b","filename":"resolver-dns/src/main/java/io/netty/resolver/dns/DnsResolveContext.java","status":"modified","additions":13,"deletions":5,"changes":18,"blob_url":"https://github.com/netty/netty/blob/5749d7822f0bf18e2eb0ea1f10d7b7181456ad75/resolver-dns%2Fsrc%2Fmain%2Fjava%2Fio%2Fnetty%2Fresolver%2Fdns%2FDnsResolveContext.java","raw_url":"https://github.com/netty/netty/raw/5749d7822f0bf18e2eb0ea1f10d7b7181456ad75/resolver-dns%2Fsrc%2Fmain%2Fjava%2Fio%2Fnetty%2Fresolver%2Fdns%2FDnsResolveContext.java","contents_url":"https://api.github.com/repos/netty/netty/contents/resolver-dns%2Fsrc%2Fmain%2Fjava%2Fio%2Fnetty%2Fresolver%2Fdns%2FDnsResolveContext.java?ref=5749d7822f0bf18e2eb0ea1f10d7b7181456ad75","patch":"@@ -641,8 +641,9 @@ private void onResponse(final DnsServerAddressStream nameServerAddrStream, final\n                 final DnsRecordType type = question.type();\n \n                 if (type == DnsRecordType.CNAME) {\n-                    onResponseCNAME(question, buildAliasMap(envelope.content(), cnameCache(), parent.executor()),\n-                                    queryLifecycleObserver, promise);\n+                    onResponseCNAME(question,\n+                            buildAliasMap(question.name(), envelope.content(), cnameCache(), parent.executor()),\n+                            queryLifecycleObserver, promise);\n                     return;\n                 }\n \n@@ -832,7 +833,7 @@ private void onExpectedResponse(\n \n         // We often get a bunch of CNAMES as well when we asked for A/AAAA.\n         final DnsResponse response = envelope.content();\n-        final Map<String, String> cnames = buildAliasMap(response, cnameCache(), parent.executor());\n+        final Map<String, String> cnames = buildAliasMap(question.name(), response, cnameCache(), parent.executor());\n         final int answerCount = response.count(DnsSection.ANSWER);\n \n         boolean found = false;\n@@ -991,7 +992,8 @@ private void onResponseCNAME(\n         }\n     }\n \n-    private static Map<String, String> buildAliasMap(DnsResponse response, DnsCnameCache cache, EventLoop loop) {\n+    private static Map<String, String> buildAliasMap(\n+            String queryName, DnsResponse response, DnsCnameCache cache, EventLoop loop) {\n         final int answerCount = response.count(DnsSection.ANSWER);\n         Map<String, String> cnames = null;\n         for (int i = 0; i < answerCount; i ++) {\n@@ -1022,7 +1024,13 @@ private static Map<String, String> buildAliasMap(DnsResponse response, DnsCnameC\n             String nameWithDot = hostnameWithDot(name);\n             String mappingWithDot = hostnameWithDot(mapping);\n             if (!nameWithDot.equalsIgnoreCase(mappingWithDot)) {\n-                cache.cache(nameWithDot, mappingWithDot, r.timeToLive(), loop);\n+                String queryNameWithDot = hostnameWithDot(queryName.toLowerCase(Locale.US));\n+                // Only cache the CNAME if the owner is in the bailiwick of the original query name.\n+                boolean inBailiwick = nameWithDot.equals(queryNameWithDot) ||\n+                        nameWithDot.endsWith(\".\" + queryNameWithDot);\n+                if (inBailiwick) {\n+                    cache.cache(nameWithDot, mappingWithDot, r.timeToLive(), loop);\n+                }\n                 cnames.put(name, mapping);\n             }\n         }"},{"sha":"25d7e04cb4719160f5b3e5287547f15b42526433","filename":"resolver-dns/src/test/java/io/netty/resolver/dns/DnsNameResolverTest.java","status":"modified","additions":74,"deletions":0,"changes":74,"blob_url":"https://github.com/netty/netty/blob/5749d7822f0bf18e2eb0ea1f10d7b7181456ad75/resolver-dns%2Fsrc%2Ftest%2Fjava%2Fio%2Fnetty%2Fresolver%2Fdns%2FDnsNameResolverTest.java","raw_url":"https://github.com/netty/netty/raw/5749d7822f0bf18e2eb0ea1f10d7b7181456ad75/resolver-dns%2Fsrc%2Ftest%2Fjava%2Fio%2Fnetty%2Fresolver%2Fdns%2FDnsNameResolverTest.java","contents_url":"https://api.github.com/repos/netty/netty/contents/resolver-dns%2Fsrc%2Ftest%2Fjava%2Fio%2Fnetty%2Fresolver%2Fdns%2FDnsNameResolverTest.java?ref=5749d7822f0bf18e2eb0ea1f10d7b7181456ad75","patch":"@@ -3043,6 +3043,80 @@ public boolean clear(String hostname) {\n         }\n     }\n \n+    @ParameterizedTest\n+    @EnumSource(DnsNameResolverChannelStrategy.class)\n+    public void testCnameCacheBailiwick(DnsNameResolverChannelStrategy strategy) throws Exception {\n+        final Map<String, String> cache = new ConcurrentHashMap<String, String>();\n+\n+        TestDnsServer dnsServer = new TestDnsServer(new RecordStore() {\n+            @Override\n+            public Set<ResourceRecord> getRecords(QuestionRecord question) throws DnsException {\n+                if (\"x.netty.io\".equals(question.getDomainName())) {\n+                    Set<ResourceRecord> records = new HashSet<ResourceRecord>();\n+                    // Valid CNAME (in bailiwick of query)\n+                    records.add(new TestDnsServer.TestResourceRecord(\n+                            \"x.netty.io\", RecordType.CNAME,\n+                            Collections.<String, Object>singletonMap(\n+                                    DnsAttribute.DOMAIN_NAME.toLowerCase(), \"cname.netty.io\")));\n+                    // Invalid CNAME (out of bailiwick of query)\n+                    records.add(new TestDnsServer.TestResourceRecord(\n+                            \"cname.netty.io\", RecordType.CNAME,\n+                            Collections.<String, Object>singletonMap(\n+                                    DnsAttribute.DOMAIN_NAME.toLowerCase(), \"evil.com\")));\n+                    // Provide an A record to satisfy the resolution\n+                    records.add(new TestDnsServer.TestResourceRecord(\n+                            \"evil.com\", RecordType.A,\n+                            Collections.<String, Object>singletonMap(\n+                                    DnsAttribute.IP_ADDRESS.toLowerCase(), \"10.0.0.99\")));\n+                    return records;\n+                }\n+                return Collections.emptySet();\n+            }\n+        });\n+        dnsServer.start();\n+        DnsNameResolver resolver = null;\n+        try {\n+            DnsNameResolverBuilder builder = newResolver(strategy)\n+                    .recursionDesired(true)\n+                    .resolvedAddressTypes(ResolvedAddressTypes.IPV4_ONLY)\n+                    .maxQueriesPerResolve(16)\n+                    .nameServerProvider(new SingletonDnsServerAddressStreamProvider(dnsServer.localAddress()))\n+                    .resolveCache(NoopDnsCache.INSTANCE)\n+                    .cnameCache(new DnsCnameCache() {\n+                        @Override\n+                        public String get(String hostname) {\n+                            return cache.get(hostname);\n+                        }\n+\n+                        @Override\n+                        public void cache(String hostname, String cname, long originalTtl, EventLoop loop) {\n+                            cache.put(hostname, cname);\n+                        }\n+\n+                        @Override\n+                        public void clear() {\n+                        }\n+\n+                        @Override\n+                        public boolean clear(String hostname) {\n+                            return false;\n+                        }\n+                    });\n+            resolver = builder.build();\n+            resolver.resolveAll(\"x.netty.io\").syncUninterruptibly();\n+\n+            // The CNAME for x.netty.io should be cached because it was the queried name\n+            assertEquals(\"cname.netty.io.\", cache.get(\"x.netty.io.\"));\n+            // The CNAME for cname.netty.io should NOT be cached because it is out of bailiwick for x.netty.io\n+            assertNull(cache.get(\"cname.netty.io.\"));\n+        } finally {\n+            dnsServer.stop();\n+            if (resolver != null) {\n+                resolver.close();\n+            }\n+        }\n+    }\n+\n     @Test\n     public void testInstanceWithNullPreferredAddressType() {\n         new DnsNameResolver("}]}