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

syscall: wrong addrlen passed to bind()/connect()/sendto() for SockaddrUnix #15381

Closed
7AC opened this issue Apr 20, 2016 · 6 comments
Closed

syscall: wrong addrlen passed to bind()/connect()/sendto() for SockaddrUnix #15381

7AC opened this issue Apr 20, 2016 · 6 comments
Milestone

Comments

@7AC
Copy link

@7AC 7AC commented Apr 20, 2016

  1. What version of Go are you using (go version)?
    go version go1.6 darwin/amd64
  2. What operating system and processor architecture are you using (go env)?
    Compiled on darwin/amd64 for linux/386, where it's running.
  3. What did you do?
    Tried to connect a Go client to a C++ server using a UNIX domain socket
  4. What did you expect to see?
    The client connecting successfully
  5. What did you see instead?
    dial unix @/Foo/bar1234: connection refused

The C++ server looks like this:

int fd = socket(AF_UNIX, SOCK_STREAM, 0);
fcntl(fd, F_SETFD, FD_CLOEXEC);
struct sockaddr_un sun;
memset(&sun, 0, sizeof(sun));
sun.sun_family = AF_UNIX;
strncpy(sun.sun_path, "@/Foo/bar1234", sizeof(sun.sun_path));
sun.sun_path[0] = '\0';
bind(fd, (sockaddr *) &sun, sizeof(sun));
listen(fd, 255);

while the Go client simply does:

net.Dial("unix", "@/Foo/bar1234")

If I apply the following diff to the C++ code, it works:

@@ -4,6 +4,7 @@
 memset(&sun, 0, sizeof(sun));
 sun.sun_family = AF_UNIX;
 strncpy(sun.sun_path, "@/Foo/bar1234", sizeof(sun.sun_path));
+int sun_len = strlen(sun.sun_path);
 sun.sun_path[0] = '\0';
-bind(fd, (sockaddr *) &sun, sizeof(sun));
+bind(fd, (sockaddr *) &sun, sun_len + sizeof(sun.sun_family));
 listen(fd, 255);

According to bind(2):

addrlen specifies the size, in bytes, of the address structure pointed to by addr

So it appears that the C++ code is correct without the change. On the Go side, I see a comment in the sockaddr() implementation for SockaddrUnix dating back to 602a446:

// length is family (uint16), name, NUL.

but that's about all the context I could find. Shouldn't sockaddr() return something like this instead?

diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go
index 73a16f8..37a2f25 100644
--- a/src/syscall/syscall_linux.go
+++ b/src/syscall/syscall_linux.go
@@ -314,7 +314,7 @@ func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, _Socklen, error) {
                sl--
        }

-       return unsafe.Pointer(&sa.raw), sl, nil
+       return unsafe.Pointer(&sa.raw), unsafe.Sizeof(sa.raw), nil
 }

 type SockaddrLinklayer struct {
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 20, 2016

Can you show us a program that fails?

@ianlancetaylor ianlancetaylor added this to the Go1.7 milestone Apr 20, 2016
@7AC
Copy link
Author

@7AC 7AC commented Apr 20, 2016

Sure, here it is. Thank you.

Client code:

package main

import "net"

func main() {
        _, err := net.Dial("unix", "@/Foo/bar1234")
        if err != nil {
                panic(err)
        }
}

Failing server code:

#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>

int main() {
   int fd = socket(AF_UNIX, SOCK_STREAM, 0);
   fcntl(fd, F_SETFD, FD_CLOEXEC);
   struct sockaddr_un sun;
   memset(&sun, 0, sizeof(sun));
   sun.sun_family = AF_UNIX;
   strncpy(sun.sun_path, "@/Foo/bar1234", sizeof(sun.sun_path));
   sun.sun_path[0] = '\0';
   bind(fd, (sockaddr *) &sun, sizeof(sun));
   listen(fd, 255);
   while (true) {}
}

Working server code:

#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>

int main() {
   int fd = socket(AF_UNIX, SOCK_STREAM, 0);
   fcntl(fd, F_SETFD, FD_CLOEXEC);
   struct sockaddr_un sun;
   memset(&sun, 0, sizeof(sun));
   sun.sun_family = AF_UNIX;
   strncpy(sun.sun_path, "@/Foo/bar1234", sizeof(sun.sun_path));
   int sun_len = strlen(sun.sun_path);
   sun.sun_path[0] = '\0';
   bind(fd, (sockaddr *) &sun, sun_len + sizeof(sun.sun_family));
   listen(fd, 255);
   while (true) {}
}

The server was compiled with gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4).

Output:

$ gcc server_ok.cpp && ./a.out &
[1] 16593
$ go run client.go 
$ killall a.out
/bin/bash: line 15: 16603 Terminated              ./a.out
[1]+  Exit 143                gcc server_ok.cpp && ./a.out
$ gcc server_fail.cpp && ./a.out &                                                                                                                                                                                    
[1] 17557
$ go run client.go                                                                                                                                                                                                    
panic: dial unix @/Foo/bar1234: connection refused

goroutine 1 [running]:
main.main()
        /home/gvalente/id/src/tacc/client.go:8 +0x6c

goroutine 2 [runnable]:
runtime.forcegchelper()
        /usr/lib/go/src/runtime/proc.go:90
runtime.goexit()
        /usr/lib/go/src/runtime/asm_386.s:2287 +0x1

goroutine 3 [runnable]:
runtime.bgsweep()
        /usr/lib/go/src/runtime/mgc0.go:82
runtime.goexit()
        /usr/lib/go/src/runtime/asm_386.s:2287 +0x1

goroutine 4 [runnable]:
runtime.runfinq()
        /usr/lib/go/src/runtime/malloc.go:712
runtime.goexit()
        /usr/lib/go/src/runtime/asm_386.s:2287 +0x1
exit status 2
@olivecoder
Copy link

@olivecoder olivecoder commented Apr 20, 2016

From bind(2) manual:

The rules used in name binding vary between address families. Consult the manual entries in Section 7 for detailed information. For AF_INET see ip(7), for AF_INET6 see ipv6(7), for AF_UNIX see unix(7), for AF_APPLETALK see ddp(7), for AF_PACKET see packet(7), for AF_X25 see x25(7) and for AF_NETLINK see netlink(7).

From unix(7) about AF_UNIX family sockets:

pathname: a UNIX domain socket can be bound to a null-terminated file system pathname using bind(2). When the address of the socket is returned by getsockname(2), getpeername(2), and accept(2), its length is offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1, and sun_path contains the null-terminated pathname.

From sys/un.h

# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path)        \
                     + strlen ((ptr)->sun_path))

IMHO: The patched C++ code (bind(fd, (sockaddr *) &sun, sun_len + sizeof(sun.sun_family))) is the correct code, so this is not an actual bug.

@7AC
Copy link
Author

@7AC 7AC commented Apr 20, 2016

FWIW the EXAMPLES section of bind(2) has this code in it:

           if (bind(sfd, (struct sockaddr *) &my_addr,
                   sizeof(struct sockaddr_un)) == -1)
               handle_error("bind");
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Apr 21, 2016

The example in the bind(2) man page is not for an abstract socket address. I don't know that we should consider it as more relevant than the unix(7) man page. For an abstract socket address the unix(7) man page says that addrlen describes the socket address. Evidently the client and server must agree on the how the length is set, but it's not clear that the Go approach is incorrect, given that it seems to match what the unix(7) man page says.

@7AC
Copy link
Author

@7AC 7AC commented Apr 21, 2016

That's right, kind of confusing but I guess Go is doing the right thing. Thank you for looking into this.

@7AC 7AC closed this Apr 21, 2016
@golang golang locked and limited conversation to collaborators Apr 21, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.