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

Sockets are inherited by subprocesses #14630

Closed
carlhoerberg opened this issue May 26, 2024 · 5 comments · Fixed by #14632
Closed

Sockets are inherited by subprocesses #14630

carlhoerberg opened this issue May 26, 2024 · 5 comments · Fixed by #14632
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:stdlib:networking topic:stdlib:system

Comments

@carlhoerberg
Copy link
Contributor

Bug Report

require "socket"
s = TCPServer.new("127.1", 1234)
Process.new("sleep", {"99"})
s.close
s = TCPServer.new("127.1", 1234)

Result

Unhandled exception: Could not bind to '127.1:1234': Address already in use (Socket::BindError)

Reason

The first server socket is inherited by the subprocess and hence the port is still in use when the second socket is bound. Just like files are opened with O_CLOEXEC sockets should be opened with the SOCK_CLOEXEC flag.

https://www.man7.org/linux/man-pages/man2/socket.2.html
https://www.man7.org/linux/man-pages/man2/open.2.html

Monkey-patch fix

require "socket"

module Crystal::System::Socket
  private def create_handle(family, type, protocol, blocking) : Handle
    {% if LibC.has_constant?(:SOCK_CLOEXEC) %}
      type = type.to_i | LibC::SOCK_CLOEXEC
    {% end %}
    fd = LibC.socket(family, type, protocol)
    raise ::Socket::Error.from_errno("Failed to create socket") if fd == -1
    fd
  end
end
@carlhoerberg carlhoerberg added the kind:bug A bug in the code. Does not apply to documentation, specs, etc. label May 26, 2024
@Sija
Copy link
Contributor

Sija commented May 26, 2024

Reduced monkey-patch:

require "socket"

module Crystal::System::Socket
  private def create_handle(family, type, protocol, blocking) : Handle
    {% if LibC.has_constant?(:SOCK_CLOEXEC) %}
      type = type.to_i | LibC::SOCK_CLOEXEC
    {% end %}
    previous_def family, type, protocol, blocking
  end
end

@straight-shoota
Copy link
Member

Hm, this is actually a bit weird. There is a method Socket#initialize_handle which is supposed to do this, but only on platforms where LibC::SOCK_CLOEXEC is undefined. I have no idea what's the reason for this😕

private def initialize_handle(fd)
{% unless LibC.has_constant?(:SOCK_CLOEXEC) %}
# Forces opened sockets to be closed on `exec(2)`. Only for platforms that don't
# support `SOCK_CLOEXEC` (e.g., Darwin).
LibC.fcntl(fd, LibC::F_SETFD, LibC::FD_CLOEXEC)
{% end %}
end

This code was originally introduced in #1192 as #init_close_on_exec.

I'd like to understand what's the reasoning behind this. It feels like this is handling the case where SOCK_CLOEXEC is undefined, but the opposite case is missing.

@carlhoerberg
Copy link
Contributor Author

So you can always do the fcntl thing, but that introduces a race condition, that's why SOCK_CLOEXE directly to socket was introduced in linux/bsd etc, but it was never implemented in darwin. My thinking is that the PR author knew this, but just forgot/accidelty deleted the line that is now suggested in this PR.

@ysbaddaden
Copy link
Contributor

ysbaddaden commented May 27, 2024

It's also to make it in one syscall, when possible, instead of two.

That was completely overlooked. Searching the history doesn't bring much information, except that I participated in missing the whole thing (oops). File descriptors should always have CLOEXEC set by default (that, I remember).

@straight-shoota
Copy link
Member

straight-shoota commented May 27, 2024

I just verified this seems to work correctly for FileDescriptor implementations (Crystal::System::File.open, Crystal::System::FileDescriptor.pipe and #system_reopen).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:stdlib:networking topic:stdlib:system
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants