Personal option: Separating leak from UAF and claim the former is more acceptable should not be the case, because leak applies to more than memory, and leaky fds can also create security holes like [0]
Accidentally "leaking" fds to a child process due to a resource leak would be a great example! However, I still feel memory/resource leaks are not the same as memory corruption. Memory corruption is more fundamental: it can be used to cause a leak, but a leak cannot be used to cause memory corruption. That is, being memory corruption-free is a necessary prerequisite for a program to also be leak-free (it isn't sufficient, though).
Additionally, that example doesn't seem to be a resource leak in the sense of a memory leak. The exact same problem occurs if the file description is purposefully used after the 'system' call (that is, it isn't being leaked by not being used again). That's an issue more with 'system'/'fork' implicitly inheriting file descriptors than a resource being leaked.
This is more of an issue with setuid (which has all sorts of problems) than with leaks. Note that this issue would still occur with C++, or in any GC'd language if the GC didn't happen to run before the call to system().
That kind of mitigation just makes me one step away from Rust - system programming languages should stick to the platform behavior, otherwise it's just another Python. Moreover this change is not documented anywhere else. Have a look at how GLib handle these cases[0]:
- during the GSubprocess discussion, I originally held the opposite
opinion, but eventually became convinced (by Colin) to see the
inherit-by-default behaviour of exec() as nothing more than a
questionable implementation detail of the underlying OS. Consequently,
at the high level, GSubprocess provides an API that gives the caller
direct control over what is inherited and what is not, and that's just
the way that it should be.
- this behaviour is not limited to GSubprocess. Closing all fds before
calling exec() is a common practice in modern libraries and runtimes,
and for good reason.
That kind of mitigation just makes me one step away from Rust - system programming languages should stick to the platform behavior, otherwise it's just another Python.
That's silly. The standard library of new systems programming languages should do the safe thing, not inherit all the mistakes of the OS that the OS can't fix due to backwards compatibility. If Unix were being designed today, I am certain O_CLOEXEC would have been the default. Besides, Rust's behavior matches what happens on Windows, and cross-platform consistency in the standard library is desirable.
In any case, what glib does matches what the Rust standard library does. The latter uses posix_spawn() directly in order to make sure no file descriptors are passed between parent and child. There is an API available to request file descriptor sharing explicitly.
Comments
Personal option: Separating leak from UAF and claim the former is more acceptable should not be the case, because leak applies to more than memory, and leaky fds can also create security holes like [0]
[0]: https://labs.portcullis.co.uk/blog/exploiting-inherited-file...
Accidentally "leaking" fds to a child process due to a resource leak would be a great example! However, I still feel memory/resource leaks are not the same as memory corruption. Memory corruption is more fundamental: it can be used to cause a leak, but a leak cannot be used to cause memory corruption. That is, being memory corruption-free is a necessary prerequisite for a program to also be leak-free (it isn't sufficient, though).
Additionally, that example doesn't seem to be a resource leak in the sense of a memory leak. The exact same problem occurs if the file description is purposefully used after the 'system' call (that is, it isn't being leaked by not being used again). That's an issue more with 'system'/'fork' implicitly inheriting file descriptors than a resource being leaked.
This is more of an issue with setuid (which has all sorts of problems) than with leaks. Note that this issue would still occur with C++, or in any GC'd language if the GC didn't happen to run before the call to system().
Also note that Rust has contained a mitigation for that issue for a long time now: https://github.com/rust-lang/rust/pull/24034
That kind of mitigation just makes me one step away from Rust - system programming languages should stick to the platform behavior, otherwise it's just another Python. Moreover this change is not documented anywhere else. Have a look at how GLib handle these cases[0]:
[0]: https://mail.gnome.org/archives/gtk-devel-list/2015-March/ms...
That's silly. The standard library of new systems programming languages should do the safe thing, not inherit all the mistakes of the OS that the OS can't fix due to backwards compatibility. If Unix were being designed today, I am certain O_CLOEXEC would have been the default. Besides, Rust's behavior matches what happens on Windows, and cross-platform consistency in the standard library is desirable.
In any case, what glib does matches what the Rust standard library does. The latter uses posix_spawn() directly in order to make sure no file descriptors are passed between parent and child. There is an API available to request file descriptor sharing explicitly.