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: delay stack-snooping syscalls beyond import time #16789

Closed
tych0 opened this issue Aug 18, 2016 · 17 comments
Closed

net: delay stack-snooping syscalls beyond import time #16789

tych0 opened this issue Aug 18, 2016 · 17 comments

Comments

@tych0
Copy link

tych0 commented Aug 18, 2016

Please answer these questions before submitting your issue. Thanks!

  1. What version of Go are you using (go version)?
    go version go1.6.2 linux/amd64
  2. What operating system and processor architecture are you using (go env)?
    GOARCH="amd64"
    GOBIN=""
    GOEXE=""
    GOHOSTARCH="amd64"
    GOHOSTOS="linux"
    GOOS="linux"
    GOPATH="/home/tycho/packages/go"
    GORACE=""
    GOROOT="/usr/lib/go-1.6"
    GOTOOLDIR="/usr/lib/go-1.6/pkg/tool/linux_amd64"
    GO15VENDOREXPERIMENT="1"
    CC="gcc"
    GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"
    CXX="g++"
    CGO_ENABLED="1"
  3. What did you do?
/tmp cat tester.go 
package main

import (
    "fmt"
    "net/http"
)

func main() {
    fmt.Printf("using net/http: %s\n", http.Header{})
}
/tmp strace go run tester.go 2>&1 | grep bind
bind(3, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
bind(4, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0

The full interesting part of the strace is:

socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
close(3)                                = 0
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 3
setsockopt(3, SOL_IPV6, IPV6_V6ONLY, [1], 4) = 0
bind(3, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 4
setsockopt(4, SOL_IPV6, IPV6_V6ONLY, [0], 4) = 0
bind(4, {sa_family=AF_INET6, sin6_port=htons(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
close(4)                                = 0
close(3)                                = 0

I don't think golang should be doing a bind() here, since it doesn't otherwise use the socket it bind()s to. This means that clients that import net/http can't live in an environment that restricts the bind syscall (via e.g. seccomp).

tych0 pushed a commit to tych0/snapd that referenced this issue Aug 18, 2016
One way to connect to things is to bind the address to the socket before
connect(). Any golang client that imports net/http does this [1], and so we
need to allow it.

[1]: golang/go#16789

Signed-off-by: Tycho Andersen <tycho.andersen@canonical.com>
@bradfitz
Copy link
Contributor

This isn't the http package. This is the net package.

You can see this if you change your program to just import _ "net".

The net package is probing whether your system supports IPv6. If it fails, it should be harmless.

If your seccomp policy is to explode your computer, though, Go can't help there. All Go can do is look at the return value from the system call.

What is your policy?

@bradfitz bradfitz added the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Aug 18, 2016
@tych0
Copy link
Author

tych0 commented Aug 18, 2016

The policy is to send a SIGSYS (i.e. SECCOMP_RET_KILL), not to return an EPERM or something like that, which freaks golang out (naturally :). It would be nice to only do this probe if we actually invoke some IPV6 support.

@bradfitz
Copy link
Contributor

It's also testing IPv4 and figuring out whether the system can do dual-stack stuff. See comments in ipsock_posix.go:

func probeIPv4Stack() bool {
        s, err := socketFunc(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
        switch err {
        case syscall.EAFNOSUPPORT, syscall.EPROTONOSUPPORT:
                return false
        case nil:
                closeFunc(s)
        }
        return true
}

// Should we try to use the IPv4 socket interface if we're                                                                                                          
// only dealing with IPv4 sockets?  As long as the host system                                                                                                      
// understands IPv6, it's okay to pass IPv4 addresses to the IPv6                                                                                                   
// interface. That simplifies our code and is most general.                                                                                                         
// Unfortunately, we need to run on kernels built without IPv6                                                                                                      
// support too. So probe the kernel to figure it out.                                                                                                               
//                                                                                                                                                                  
// probeIPv6Stack probes both basic IPv6 capability and IPv6 IPv4-                                                                                                  
// mapping capability which is controlled by IPV6_V6ONLY socket                                                                                                     
// option and/or kernel state "net.inet6.ip6.v6only".                                                                                                               
// It returns two boolean values. If the first boolean value is                                                                                                     
// true, kernel supports basic IPv6 functionality. If the second                                                                                                    
// boolean value is true, kernel supports IPv6 IPv4-mapping.                                                                                                        
func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) {

etc.

I'm not sure this is worth us changing anything, since you're the first person to complain about this.

It seems you can just change your configuration to SECCOMP_RET_ERRNO or whatever, telling Go "no" and letting us proceed.

If we supported this configuration, we would also need to test this configuration going forward (else it would inevitably regress), and it seems like a lot of work to test for marginal benefit.

Convince us otherwise?

/cc @mikioh @ianlancetaylor @mdempsky for other opinions.

@tych0
Copy link
Author

tych0 commented Aug 18, 2016

I don't necessarily mind a resolution of WONTFIX here if it's too much work, as we can probably just safely add bind() to our seccomp policy in this case; I just thought I'd point it out, as it seemed a little odd.

Anyway, we'll see what other people have to say, if anything. Otherwise feel free to close this.

@mikioh
Copy link
Contributor

mikioh commented Aug 21, 2016

It's fine to propagate sync.Once(probe{IPv4,IPv6,TCP}Stack) in the exposed API of net package to delay calling socket system calls, though I have no strong opinion about some audit trail on the stack comprised of Go APIs and kernel system calls.

@rogpeppe
Copy link
Contributor

I have also just encountered this issue (and I think there will be more in the future).

The new Ubuntu snappy system uses seccomp extensively to ensure that a "snap" only uses the privileges that it's been given. You need to require the "network-bind" interface (as opposed to the "network" interface) in order to be able to use the bind system call (see http://snapcraft.io/docs/reference/interfaces)

This init-time probe means that all Go programs that import /net, even if they're only importing it for IP address parsing, will require the "network-bind" interface.

It would be more appropriate to do this probe with sync.Once only when actual access to the network stack is required. Would that result in significant performance loss?

@bradfitz
Copy link
Contributor

@rogpeppe, did you write your seccomp policy, or did it come with a snap?

That is, did somebody publish a Go binary in a snap with a non-working policy?

@gdm85
Copy link

gdm85 commented Aug 28, 2016

I think it doesn't matter what is the policy of distros, but the language standard library should fit the minimum necessary privileges good practice.

In this specific case, can't something simpler be used, like reading /proc/net/if_inet6? I know this might require splitting the logic for the BSDs, but they're not really uniform already anyway..

@bradfitz
Copy link
Contributor

@gdm85, I agree we should do the minimum amount possible, and as late as possible. I actually started working on this a bit the other day.

I'm still curious about my question to @rogpeppe, though.

@gdm85
Copy link

gdm85 commented Aug 28, 2016

The very best would be to not attempt any bind unless asked to explicitly ("do what I tell you to do, and nothing else"); also my 2c from a security perspective: software that binds when not asked to is one of the first trigger signs for further analysis 😉 which would of course turn out to be noise in the case of Go binaries.

As far as I know IPv4 cannot be disabled entirely from Linux kernel (not sure about BSDs); talking about IPv6 instead, I've verified that checking /proc/net/if_inet6 is not enough (it would still be there even when IPv6 connectivity is not possible) because there are other settings that can be used to disable IPv6:

net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
## and even the individual interface's parameter

I understand why using bind() is more preferable, as it attempts the exact thing that might be asked later on, however one has to distinguish being able to bind() IPv6 addresses (server model) from being able to use IPv6 addresses (client model), hence I suggest better not using it as a test, not even a lazy one.

By reading around and on the man ipv6 (http://man7.org/linux/man-pages/man7/ipv6.7.html), it seems like attempting connection to the local IPv6 loopback address ::1 should be a good test for this, but then I cannot really tell if there is a connection that would always work (like port 0/ICMP).

On lack of practical solutions for this, then one could just avoid probing and fail with the OS-level error when something which is not supported is being attempted.

@bradfitz
Copy link
Contributor

The very best would be to not attempt any bind unless asked to explicitly ("do what I tell you to do, and nothing else"); also my 2c from a security perspective: software that binds when not asked to is one of the first trigger signs for further analysis 😉 which would of course turn out to be noise in the case of Go binaries.

I think you're repeating yourself, or at least other points already made in this thread. No need to argue the point further. I think everybody agrees.

The current implementation was done out of pragmatism and portability. @mikioh can elaborate further.

The best help right now would be somebody working on a patch.

@gdm85
Copy link

gdm85 commented Aug 28, 2016

@bradfitz I am asking in both comments if alternative approaches would be viable, and that has been left unanswered. I would always ask for this before attempting a patch. Specifically:

  • if querying /proc/net/if_inet6 is viable (already verified it's not)
  • if connecting to ::1 port 0 would be viable
  • if skipping probing completely would be viable

Known the above, any patch would be greatly more useful and less of a waste :)

@bradfitz
Copy link
Contributor

bradfitz commented Aug 28, 2016

I don't remember how all dozen operating systems use/need the three probe variables.

Whatever you do, you can't regress any behavior. Linux specific things are fine on Linux if they're reliable and not only in super recent kernel versions. But there's also Windows, Solaris, BSDs, Darwin, etc.

edit: the word "can't" above was wrong when I typed it on my phone.

@bradfitz
Copy link
Contributor

In any case, most of closing this bug is doing the research on why it was done how it's currently done, and figuring out what to do instead on each operating system. I don't have the answers off hand. @mikioh might, but I don't.

Ideally any patch would also document things better, since it wasn't entirely obvious to me from reading the source.

I think we could probably start by doing the computation of the three supportsIP* booleans lazily, but still doing the bind-based probing on all operating systems. And then in a second change, you could change the probing on Linux to do something Linux-specific without bind.

Maybe the three probe variables also need to be split into more finer-grained questions, if the variables are used for a combination of questions (client vs server related) and bind is appropriate for some questions and not others. Again, somebody needs to read the code and figure it out.

@quentinmit quentinmit added this to the Go1.8Maybe milestone Sep 6, 2016
@rsc rsc modified the milestones: Go1.9, Go1.8Maybe Oct 20, 2016
@rsc rsc changed the title net/http: importing net/http makes a bind syscall net: delay stack-snooping syscalls beyond import time Oct 20, 2016
@bradfitz bradfitz removed the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Jan 6, 2017
@gopherbot
Copy link

CL https://golang.org/cl/40750 mentions this issue.

@mdempsky
Copy link
Member

Is the problem just the bind system call? Could we just switch the probe test from binding TCP sockets to connecting UDP sockets?

@tych0
Copy link
Author

tych0 commented Apr 14, 2017 via email

@golang golang locked and limited conversation to collaborators Apr 15, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

9 participants