Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: pure go version of dnsclient does a DNS lookup of "localhost" qualified with the domain name. #32017

Closed
kraney opened this issue May 14, 2019 · 11 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Milestone

Comments

@kraney
Copy link

kraney commented May 14, 2019

This bug is derived from this one I reported in Kubernetes.

kubernetes/kubernetes#77764

What did you do?

On my DNS domain, someone decided it would be cute to create a hostname 'localhost.mydomain.com'.

Go's pure go DNS resolver, when looking up "localhost", tries "localhost.mydomain.com" first and ends up with an IP address from DNS, rather than resolving to 127.0.0.1 from /etc/hosts like you'd expect.

What did you expect to see?

I expected that a) there would be no DNS lookup, and b) to get a result of 127.0.0.1

What did you see instead?

Attempts to contact https://localhost/ end up being directed to the cutely-named system elsewhere in the network.

Looking in https://github.com/golang/go/blob/master/src/net/dnsclient_unix.go, it appears that the function nameList(), given an argument of 'localhost', would return results that first search for the FQDN obtained by appending the default domain, and then would search for the unadorned 'localhost' last. This produces incorrect results.

@kraney
Copy link
Author

kraney commented May 14, 2019

Note that this bug has security implications, since it represents a technique for someone to get in the middle of communications that would ordinarily be unseen on loopback, and might not be well secured as a result of that expectation.

@agnivade agnivade changed the title Pure go version of dnsclient does a DNS lookup of "localhost" qualified with the domain name. net: pure go version of dnsclient does a DNS lookup of "localhost" qualified with the domain name. May 14, 2019
@agnivade
Copy link
Contributor

@mikioh @ianlancetaylor

@agnivade agnivade added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label May 14, 2019
@agnivade agnivade added this to the Go1.14 milestone May 14, 2019
@bradfitz
Copy link
Contributor

Dup of #22826? (net: add support for "let localhost be localhost")

But looks like https://tools.ietf.org/html/draft-ietf-dnsop-let-localhost-be-localhost-02 stalled (and actually expired).

Is our behavior actually wrong from a spec standpoint?

I personally agree with let-localhost-be-localhost for sanity/security reasons, but it looks like (from #22826) that we were waiting for that to become more official best practice.

@kraney
Copy link
Author

kraney commented May 15, 2019

"localhost" is supposed to go through name resolution. That's the mechanism for selecting whether it resolves to an IPv4 loopback address or an IPv6 one for that particular host.

However, "localhost" is not supposed to be qualified with the default domain name before that lookup happens. The implementation here checks "localhost.fulldomain.com" first, before it checks "localhost". If no such host exists, you won't notice an issue except perhaps an unnecessary delay. But if it does exist, you're in for a surprise.

@bradfitz
Copy link
Contributor

Oh, sorry, I missed on my pre-coffee reading that this was about Go appending the default domain first, before the bare "localhost" lookup.

@bradfitz
Copy link
Contributor

I'm not able to reproduce this, though. I tried both the cgo and netgo resolvers, but neither attempts a DNS lookup.

Log of my repro attempt...
bradfitz@go:~$ cat /etc/resolv.conf 
domain c.symbolic-datum-552.internal 
search c.symbolic-datum-552.internal. google.internal. 
nameserver 169.254.169.254 
bradfitz@go:~$ cat /etc/hosts 
127.0.0.1       localhost MyLocalHost 
::1             localhost ip6-localhost ip6-loopback 
fe00::0         ip6-localnet 
ff00::0         ip6-mcastprefix 
ff02::1         ip6-allnodes 
ff02::2         ip6-allrouters 
  
10.240.0.12 dev-bradfitz.c.symbolic-datum-552.internal dev-bradfitz  # Added by Google 
 
bradfitz@go:~$ cat lookup.go 
package main 
 
import ( 
        "flag" 
        "fmt" 
        "log" 
        "net" 
) 
 
func main() { 
        flag.Parse() 
        addr, err := net.LookupHost(flag.Arg(0)) 
        if err != nil { 
                log.Fatal(err) 
        } 
        fmt.Println(addr) 
} 
bradfitz@go:~$ go build lookup.go 
bradfitz@go:~$ GODEBUG=netdns=go+1 strace -e socket,open,connect,read,write -f ./lookup localhost 
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 
open("/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3 
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0Pa\0\0\0\0\0\0"..., 832) = 832 
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\4\2\0\0\0\0\0"..., 832) = 832 
strace: Process 11550 attached 
strace: Process 11551 attached 
strace: Process 11552 attached 
strace: Process 11553 attached 
strace: Process 11554 attached 
[pid 11549] read(3, "# /etc/nsswitch.conf\n#\n# Example"..., 1024) = 497 
[pid 11549] read(3, "", 1024)           = 0 
[pid 11549] read(3, "domain c.symbolic-datum-552.inte"..., 65536) = 119 
[pid 11549] read(3, "", 65417)          = 0 
[pid 11549] read(3, "", 65536)          = 0 
[pid 11549] write(2, "go package net: GODEBUG setting "..., 61go package net: GODEBUG setting forcing use of Go's resolver 
) = 61 
[pid 11549] read(3, "127.0.0.1\tlocalhost MyLocalHost\n"..., 65536) = 330 
[pid 11549] read(3, "", 65206)          = 0 
[pid 11549] read(3, "", 65536)          = 0 
[pid 11549] write(1, "[127.0.0.1 ::1]\n", 16[127.0.0.1 ::1] 
) = 16 
[pid 11553] +++ exited with 0 +++ 
[pid 11552] +++ exited with 0 +++ 
[pid 11551] +++ exited with 0 +++ 
[pid 11550] +++ exited with 0 +++ 
[pid 11554] +++ exited with 0 +++ 
+++ exited with 0 +++ 
bradfitz@go:~$ GODEBUG=netdns=+1 strace -e socket,open,connect,read,write -f ./lookup localhost 
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 
open("/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3 
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0Pa\0\0\0\0\0\0"..., 832) = 832 
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\4\2\0\0\0\0\0"..., 832) = 832 
strace: Process 11558 attached 
strace: Process 11559 attached 
strace: Process 11560 attached 
strace: Process 11561 attached 
strace: Process 11562 attached 
[pid 11557] read(3, "# /etc/nsswitch.conf\n#\n# Example"..., 1024) = 497 
[pid 11557] read(3, "", 1024)           = 0 
[pid 11557] read(3, "domain c.symbolic-datum-552.inte"..., 65536) = 119 
[pid 11557] read(3, "", 65417)          = 0 
[pid 11557] read(3, "", 65536)          = 0 
[pid 11557] write(2, "go package net: dynamic selectio"..., 50go package net: dynamic selection of DNS resolver 
) = 50 
[pid 11557] read(3, "127.0.0.1\tlocalhost MyLocalHost\n"..., 65536) = 330 
[pid 11557] read(3, "", 65206)          = 0 
[pid 11557] read(3, "", 65536)          = 0 
[pid 11557] write(1, "[127.0.0.1 ::1]\n", 16[127.0.0.1 ::1] 
) = 16 
[pid 11560] +++ exited with 0 +++ 
[pid 11562] +++ exited with 0 +++ 
[pid 11561] +++ exited with 0 +++ 
[pid 11559] +++ exited with 0 +++ 
[pid 11558] +++ exited with 0 +++ 
+++ exited with 0 +++ 

bradfitz@go:~$ CGO_ENABLED=0 go build lookup.go
bradfitz@go:~$ GODEBUG=netdns=+1 strace -e socket,open,connect,read,write -f ./lookup localhost
strace: Process 11670 attached
strace: Process 11669 attached
strace: Process 11671 attached
strace: Process 11672 attached
[pid 11668] read(3, "# /etc/nsswitch.conf\n#\n# Example"..., 1024) = 497
[pid 11668] read(3, "", 1024)           = 0
[pid 11668] read(3, "domain c.symbolic-datum-552.inte"..., 65536) = 119
[pid 11668] read(3, "", 65417)          = 0
[pid 11668] read(3, "", 65536)          = 0
[pid 11668] write(2, "go package net: built with netgo"..., 68go package net: built with netgo build tag; using Go's DNS resolver
) = 68
[pid 11668] read(3, "127.0.0.1\tlocalhost MyLocalHost\n"..., 65536) = 330
[pid 11668] read(3, "", 65206)          = 0
[pid 11668] read(3, "", 65536)          = 0
[pid 11668] write(1, "[127.0.0.1 ::1]\n", 16[127.0.0.1 ::1]
) = 16
[pid 11672] +++ exited with 0 +++
[pid 11669] +++ exited with 0 +++
[pid 11671] +++ exited with 0 +++
[pid 11670] +++ exited with 0 +++
+++ exited with 0 +++

@bradfitz bradfitz added the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label May 15, 2019
@bradfitz
Copy link
Contributor

Got a repro?

@kraney
Copy link
Author

kraney commented May 15, 2019

Unfortunately, I don't have a simple reproduction. I observed this behavior from Kubernetes' apiserver. I'm not a Kubernetes developer. I just started reading code to try and track this down.

Out of curiosity, what's in your /etc/nsswitch.conf?

@bradfitz
Copy link
Contributor

Out of curiosity, what's in your /etc/nsswitch.conf?

Mine has:

hosts:          files dns

Do you have dns first? I'll try that later.

@kraney
Copy link
Author

kraney commented May 15, 2019

In the original circumstance that triggered the bug report, actually no mine is also set to "files dns". But looking at the code it looked like setting dns first would trigger the behavior I found in the code.

I confirmed it - if I set it to "dns files" and use your code, I see a DNS lookup of a FQDN first.

This may indicate I haven't correctly root caused my original issue, but I still see this behavior as an issue.

@bradfitz
Copy link
Contributor

Yes, I see that too, but I also see that glibc does the same, so it seems like you get what you're asking for if you have your nsswitch.conf configured to do that.

So this bug seems like just a dup of #22826, which IIRC would just be special-casing the meaning in "localhost" in some places.

Closing for now, but let me know if I missed something.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Projects
None yet
Development

No branches or pull requests

4 participants