Siyao Meng created HADOOP-20040:
-----------------------------------
Summary: SecurityUtil host resolver cache shares one mutable
InetAddress per IP literal, leaking reverse lookups to other callers
Key: HADOOP-20040
URL: https://issues.apache.org/jira/browse/HADOOP-20040
Project: Hadoop Common
Issue Type: Bug
Components: security, net
Affects Versions: 3.5.0
Reporter: Siyao Meng
Assignee: Siyao Meng
h3. Problem
HADOOP-19447 added a cache to {{SecurityUtil.CacheableHostResolver}}, enabled
when {{hadoop.security.hostname.cache.expire-interval.seconds}} > 0. The cache
returns the same {{InetAddress}} instance to every caller resolving the same
host string, and {{NetUtils.createSocketAddrForHost}} wraps that instance
directly in a new {{InetSocketAddress}}.
{{InetAddress}} is not immutable. For an IP literal resolved by
{{StandardHostResolver}} (the default, since
{{hadoop.security.token.service.use_ip}} defaults to true), the hostname is
unset, and the first {{getHostName()}} call does a reverse lookup and stores
the PTR name in the object. With the cache on, that one call changes the
address seen by every later caller in the JVM:
{code:java}
InetSocketAddress a = NetUtils.createSocketAddr("10.0.0.5:8020");
InetSocketAddress b = NetUtils.createSocketAddr("10.0.0.5:9870");
b.getHostString(); // "10.0.0.5"
a.getHostName(); // reverse lookup, e.g. "nn1.example.com"
b.getHostString(); // now "nn1.example.com"
NetUtils.createSocketAddr("10.0.0.5:50070").getHostString(); //
"nn1.example.com"
{code}
Without the cache, each resolution returns a fresh object and
{{getHostString()}} keeps returning the literal. So enabling the cache makes
the host string of a configured IP address depend on unrelated earlier calls.
The cache key is the host only, so addresses on different ports share the
object too.
Callers that trigger the reverse lookup include {{NetUtils.canonicalizeHost}}
(via {{FileSystem}} URI canonicalization of {{hdfs://<ip>:<port>}}),
{{NetUtils.getHostPortString}} and {{NameNode#loginAsNameNodeUser}}. Callers
that read the host string include
{{DatanodeHttpServer#getHostnameForSpnegoPrincipal}} (SPNEGO {{_HOST}}
substitution), {{RBFMetrics}} router web URI, and
{{InetSocketAddress#toString}} in logs. HDFS-18004 makes the NameNode RPC
server bind to {{getHostString()}} so an IP literal is bound as given; with the
cache on it silently falls back to binding the reverse lookup name.
{{QualifiedHostResolver}} ({{use_ip=false}}) is not affected for literals,
since it builds the address with {{InetAddress.getByAddress(host, bytes)}} and
the hostname is already set.
h3. Proposed fix
Bypass the cache for IP literals in {{CacheableHostResolver#getByName}}.
Resolving a literal needs no DNS lookup, so the cache saves nothing for it, and
bypassing restores the exact pre-HADOOP-19447 behavior for literals. Hostnames
stay cached; {{InetAddress.getByName(hostname)}} already sets the hostname, so
{{getHostName()}} does not mutate those objects.
Returning a copy via {{InetAddress.getByAddress(host, bytes)}} was considered
and rejected: it would make {{getHostName()}} return the literal without the
reverse lookup, which is a behavior change of its own.
The existing {{TestSecurityUtil#testCacheableResolve}} used {{127.0.0.1}} to
check cache hits, so it switches to {{localhost}} and checks expiry on the
cache entry (the JDK returns the same object for a hostname from its own lookup
cache). A new test checks that literals are not shared and keep their host
string after {{getHostName()}}.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]