Change:
Currently in , Rust is hooking into the glibc extension for retrieving / even in case Rust does not manage and is e.g. just loaded into a process as a . While this is great and convenient, it's unfortunately not implemented in a safe way. Various C commandline argument parsers are modifying while parsing, so the information that gets passed into might not be correct anymore. Examples of such parsers are glibc's own  parser, noting " The default is to permute the contents of argv while scanning it so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this. " GLib's  parser, which removes all already handled arguments from and only leaves others (removed ones are set to and moved to the end). Qt does the same thing in in . It shuffles around arguments in and removes (by setting to ) arguments it handled already. This means that at least can easily be too big, and Rust would read beyond the (new) end of . This caused crashes in because of calling on when creating an around such an "removed" argument. Now the question is how this should be handled in Rust. I see three options here Remove the extension usage and handle glibc like all other libcs Copy all arguments in . This means everybody has to pay for that even if they don't use arguments, and this is theoretically still not safe if the shared library is loaded at the same time the array is modified and a partially written pointer value is read Handle pointers in by skipping over them. This means we would lose the exact length information of the iterator, and this is still theoretically not safe for the same reason as 2. See This code was in 2019 by (CC I've created as a proposed fix for this, which implements option 3. : <details<summaryBacktrace</summary<pAlso </p</details
Arguably swapping two entries of is still a lot less dangerous than setting one to NULL -- it seems totally legitimate to assume that there are no NULL pointers in , and does not insert any NULL there either. So what GLib does here is certainly questionable, more so than what does. That would be a data race. However, just like multi-threaded programs cannot mutate the process environment or write to the global pointer ( is inherently unsafe if there are other threads; also see the discussion in ), I would argue that multi-threaded programs cannot mutate . Global state such as or is inherently read-only in a concurrent program; there is just no way to make mutation safe. So, I think for concurrent programs we can already assume that there are no concurrent mutations, and if there are then that is a bug in the code that performs the mutation. What remains is the question whether we should adjust the args handling to be able to deal with NULL pointers in .
The problem here is that this behaviour of GLib exists since literally decades, and I'm sure we'll find other commandline parsers that are doing similar things if we search a bit. My first reaction also was that this feels like something to fix in GLib, but that doesn't seem to be a practical resolution because of the above. Skipping over s seems to be a relatively cheap mitigation if we decide that concurrent modifications of are simply wrong (who even does that! also seems less likely than ).
I can't judge how common it is to add NULLs there, and don't have an opinion on whether we should support that -- let's see what says. If you know of another library doing this that could be useful, though GLib doing it for decades on its own already means this is quite wide-spread. I hope GLib does this before any threads are spawned, because otherwise I'm afraid that is just inherently doomed.
I'll search a bit later. An application could surely do that, but that's just asking for problems and I wouldn't really worry about that. The following would work around this FWIW. Maybe should be conditional on glibc.
nit: s/Same/Some
Qt does the same thing in in . It shuffles around arguments in and removes (by setting to ) arguments it handled already.
's library is not affected because it always copies each string directly into a vector and then operates on that.
Another approach would be parsing . Doesn't work if proc isn't available, but so do things like .
is created by the kernel reading from the original location of argv (as specified by the auxiliary vector). If you edit argv, so will the commandline reported by the kernel.
Skipping over NULL seems less safe; would it make sense to just stop at the first NULL?
To the best of my knowledge, it's completely valid to parse argv by stopping at the first NULL; if an argument parser is replacing an argument with NULL, it should not expect that arguments after that point will be seen. I've seen plenty of C programs that just do or equivalent.
For the commandline parsers from GLib and Qt that would be sufficient, yes. Considering the below, it seems OK to do that and there shouldn't really be any risk of missing useful arguments. TIL that is -terminated. Thanks for pointing that out :)

Discussion:
// list. let argv = ARGV.load(Ordering::Relaxed); let argc = if argv.is_null() { 0 } else { ARGC.load(Ordering::Relaxed) }; <del> (0..argc) .map(|i| { let cstr = CStr::from_ptr(*argv.offset(i) as *const libc::c_char); OsStringExt::from_vec(cstr.to_bytes().to_vec()) }) .collect() </del> <ins> let mut args = Vec::with_capacity(argc as usize); for i in 0..argc { let ptr = *argv.offset(i) as *const libc::c_char; // Some C commandline parsers (e.g. GLib and Qt) are replacing already // handled arguments in `argv` with `NULL` and move them to the end. That // means that `argc` might be bigger than the actual number of non-`NULL` // pointers in `argv` at this point. // // To handle this we simply stop iterating at the first `NULL` argument. // // `argv` is also guaranteed to be `NULL`-terminated so any non-`NULL` arguments // after the first `NULL` can safely be ignored. if ptr.is_null() { break; } let cstr = CStr::from_ptr(ptr); args.push(OsStringExt::from_vec(cstr.to_bytes().to_vec())); } args </ins> } } }