Siler
Siler

Reputation: 9484

Closing the listening socket after a fork()

A common server socket pattern on Linux/UNIX systems is to listen on a socket, accept a connection, and then fork() to process the connection.

So, it seems that after you accept() and fork(), once you're inside the child process, you will have inherited the listening file descriptor of the parent process. I've read that at this point, you need to close the listening socket file descriptor from within the child process.

My question is, why? Is this simply to reduce the reference count of the listening socket? Or is it so that the child process itself will not be used by the OS as a candidate for routing incoming connections? If it's the latter, I'm a bit confused for two reasons:

(A) What tells the OS that a certain process is a candidate for accepting connections on a certain file descriptor? Is it the fact that the process has called accept()? Or is it the fact that the process has called listen()?

(B) If it's the fact that the process has called listen(), don't we have a race condition here? What if this happens:

  1. Parent process listens on socket S.
  2. Incoming connection goes to Parent Process.
  3. Parent Process forks a child, child has a copy of socket S
  4. BEFORE the child is able to call close(S), a second incoming connection goes to Child Process.
  5. Child Process never calls accept() (because it's not supposed to), so the incoming connection gets dropped

What prevents the above condition from happening? And more generally, why should a child process close the listening socket?

Upvotes: 7

Views: 4707

Answers (5)

Alexis Wilke
Alexis Wilke

Reputation: 20725

In the socket() manual, a paragraph says:

SOCK_CLOEXEC
Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for reasons why this may be useful.

Unfortunately, that doesn't do anything when you call fork(), it's only for when you call execv() and other similar functions. Anyway, reading the info in the open() function manual we see:

O_CLOEXEC (since Linux 2.6.23)
Enable the close-on-exec flag for the new file descriptor. Specifying this flag permits a program to avoid additional fcntl(2) F_SETFD operations to set the FD_CLOEXEC flag.

Note that the use of this flag is essential in some multithreaded programs, because using a separate fcntl(2) F_SETFD operation to set the FD_CLOEXEC flag does not suffice to avoid race conditions where one thread opens a file descriptor and attempts to set its close-on-exec flag using fcntl(2) at the same time as another thread does a fork(2) plus execve(2). Depending on the order of execution, the race may lead to the file descriptor returned by open() being unintentionally leaked to the program executed by the child process created by fork(2). (This kind of race is in principle possible for any system call that creates a file descriptor whose close-on-exec flag should be set, and various other Linux system calls provide an equivalent of the O_CLOEXEC flag to deal with this problem.)

Okay so what does all of that mean?

The idea is very simple. If you leave a file descriptor open when you call execve(), you give the child process access to that file descriptor and thus it may be given access to data that it should not have access to.

When you create a service which fork()s and then executes code, that code often starts by dropping rights (i.e. the main apache2 service runs as root, but all the spawned fork() actually run as the httpd or www user—it is important for the main process to be root in order to open ports 80 and 443, any port under 1024, actually). Now, if a hacker is somehow able to gain control of that child process, they at least won't have access to that file descriptor if closed very early on. This is much safer.

On the other hand, my apache2 example works differently: it first opens a socket and binds it to port 80, 443, etc. and then creates children with fork() and each child calls accept() (which by default blocks). The first incoming connection will wake up one of the children by returning from the accept() call. So I guess that one is not that risky after all. It will even keep that connection open and call accept() again, up to the max. defined in your settings (something like 100 by default, depends on the OS you use). After max. accept() calls, that child process exits and the server creates a new instance. This is to make sure that the memory footprint doesn't grow too much.

So in your case, it may not be that important. However, if a hacker takes over your process, they could accept other connections and handle them with their canny version of your server... something to thing about. If your service is internal (only runs on your Intranet), then the danger is lesser (although from what I read, most thieves in companies are employees working there...)

Upvotes: 1

vilanova
vilanova

Reputation: 77

A child process inherits all files descriptors from its parent. A child process should close all listening sockets to avoid conflicts with its parent.

Upvotes: 0

D White
D White

Reputation: 36

The child process won't be listening on the socket unless accept() is called, in which case incoming connections can go to either process.

Upvotes: 0

Matthias Wimmer
Matthias Wimmer

Reputation: 3999

The incoming connection will be 'delivered' to which ever process is calling accept(). After you forked before closing the file descriptor you could accept the connection in both processes.

So as long as you never accept any connections in the child thread and the parent is continuing to accept the connections everything would work fine.

But if you plan to never accept connections in your child process, why would you want to keep resources for the socket in this process?

The interesting question would be what happens if both processes call accept() on the socket. I could not find definite information on this at the moment. What I could find is, that you can be sure, that every connection is only delivered to only one of these processes.

Upvotes: 2

Colonel Thirty Two
Colonel Thirty Two

Reputation: 26539

Linux queues up pending connections. A call to accept, from either the parent or child process, will poll that queue.

Not closing the socket in the child process is a resource leak, but not much else. The parent will still grab all the incoming connections, because it's the only one that calls accept, but if the parent exits, the socket will still exist because it's open on the child, even if the child never uses it.

Upvotes: 7

Related Questions