Skip to content

Should we automatically increase the file descriptor limit? #2016

Description

@njsmith

Just read this interesting article about the file descriptor limit on Linux: http://0pointer.net/blog/file-descriptor-limits.html

The short version is:

  • Unix's enforce a limit on how many file descriptors a process can have open
  • This limit is kind of archaic. Back in the day I guess this was needed to stop processes overloading the kernel by opening too many file descriptors. But these days we have things like "dynamic memory allocation", and a file descriptor is really just a bit of kernel memory, and the kernel already accounts for how much memory a process is using.
  • In fact, the only reason the limit is useful at all today is that some programs might corrupt memory if they blindly use select and have more than 1024 file descriptors open. This is because as part of select's API, file descriptor values are effectively used as unchecked indices into a fixed-size memory buffer. So if an fd has value 1024 or higher, you end up scribbling on random memory. Sigh.
  • So on modern Linux systems, processes all start with a "soft" limit of 1024 to protect against this memory corruption, but any program that knows its not doing stupid things with select can freely remove this limit. (On my laptop with stock ubuntu, ulimit -Ha says that my actual per-process limit is 2**20 file descriptors.)

Arguments in favor of Trio silently raising the limit:

  • Hitting the fd limit is a common source of surprise problems. 1024 concurrent clients is pretty easy to miss in testing but hit in production. We have special hacks to try to degrade gracefully when this happens:

    (There are several different errors that trigger the graceful degradation, but EMFILE is by far the easiest to hit.)

    Generally speaking, Trio tries to deal with this kind of arcane nonsense so that our users don't have to become experts in low-level trivia.

  • It actually isn't possible to cause the stupid memory corruption problem from Python directly; Python's select wrapper correctly checks for too-large file descriptor values, and errors out if you try to pass them. (Though it is possible for a C extension to mess this up, if it uses select or calls into some C library that uses select.) Also, hopefully no-one is using select and Trio together anyway?

  • Also, with glibc, if you build with -D_FORTIFY_SOURCE=1 or higher, then glibc tweaks the select API to detect the buffer overrun and crash your program if you try. This is extremely standard for distribution-built binaries. But this may not apply to locally-built binaries, and musl doesn't have any similar protection.

  • The limit doesn't even fix select; it just makes it so that processes that use too many fds will give an error, instead of silently corrupting memory. Now, don't get me wrong, giving an error is way better than silently corrupting memory.

Arguments against Trio silently raising the limit:

  • It's a process-wide limit, and we're just a library – we have no idea what other code might be running in our process.

  • If someone does somehow manage to use select in the broken way, raising the limit could in theory produce a remotely-exploitable vulnerability. (It's not a slam dunk by any means, but "how many file descriptors the program has open" is something that a remote attacker could exert some control over, and maybe if they're clever enough they could cause a controlled number of file descriptors to be opened, and then a certain one to be passed to the code that's using select, to flip a controlled bit in memory.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions