Skip to content

Consider deprecating and/or modifying behavior of std::env::set_var #90308

Description

@leo60228

Observing an environment variable concurrently with a call to setenv constitutes a data race. Rust currently handles this by using a mutex, but that only protects against functions in std::env. My interpretation of POSIX (which appears to be the same as the glibc developers') is that any libc function is allowed to call getenv. This has already caused problems with getaddrinfo in #27970.
Additionally, a large amount of C code calls getenv but is otherwise thread-safe. While this isn't necessarily the standard library's issue, it's impossible for third-party libraries to soundly use this code without requiring the user to certify that this isn't an issue via an unsafe block. Some examples of this happening in practice are in time-rs/time#293 and rust-lang/flate2-rs#272.
rustsec/advisory-db#926 had several proposals on how this could be handled brought up.

Make std::env::set_var unsafe

This is arguably the cleanest solution to the issue. This could be considered a soundness fix, which would make it an option, but the ecosystem impact feels like it'd be too big.

Don't actually call setenv

std could keep track of the environment itself, and make set_var changes only visible to var and subprocesses. This is probably the way to solve the issue with the least impact on existing code, but the behavior is somewhat unexpected and not zero-cost.

Only call setenv in single-threaded code

This would reduce the impact further, but seems even less expected for negligible benefit.

Deprecate std::env::set_var

This would make it clear that setting an environment variable in the current process is discouraged. It could also be combined with not actually calling setenv, which would be my preferred solution to this issue.

Activity

  1. vi commented on Oct 29, 2021

    @vi
    Contributor

    Shall it be made unsafe fn, just like std::os::unix::process::CommandExt::pre_exec?

  2. leo60228 commented on Oct 29, 2021

    @leo60228
    ContributorAuthor

    Shall it be made unsafe fn, just like std::os::unix::process::CommandExt::pre_exec?

    The discussion in #39575 is definitely helpful here, but I don't think the same conclusions can reasonably apply to std::env::set_var.

  3. tarcieri commented on Oct 29, 2021

    @tarcieri
    Contributor

    Indeed. Notably they retroactively changed pre_exec to unsafe based on a justification that there weren't real-world uses of it.

    That's not the case with set_var: https://github.com/search?l=Rust&type=Code&q=set_var

    I think to accomplish a similar effect, set_var would have to be deprecated, and a new unsafe fn introduced in its place (which is more or less combining options 1 & 4 proposed in the OP)

  4. leo60228 commented on Oct 29, 2021

    @leo60228
    ContributorAuthor

    I think to accomplish a similar effect, set_var would have to be deprecated, and a new unsafe fn introduced in its place (which is more or less combining options 1 & 4 proposed in the OP)

    That's what happened with pre_exec. before_exec is the old name.

  5. leo60228 commented on Nov 10, 2021

    @leo60228
    ContributorAuthor

    For reference, .NET takes option #2.

  6. cgwalters commented on Nov 11, 2021

    @cgwalters
    Contributor

    Hi, there's a bunch of discussion on this issue, but I think https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475 has the most information right now.

  7. briansmith commented on Jan 4, 2022

    @briansmith
    Contributor

    Hi, there's a bunch of discussion on this issue, but I think https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475 has the most information right now.

    There is a lot of discussion in #27970 that's really about this issue. This is largely my fault because I didn't know this issue existed.

  8. briansmith commented on Jan 4, 2022

    @briansmith
    Contributor

    I looked at an application that is using set_var in multiple places:

    • To provide a default value for RUST_LOG=debug when it wasn't already set. We'd prefer a way to do this without setting the environment variable.
    • To force RUST_BACKTRACE=full regardless of what the environment says. Again, we'd prefer a way to do this without setting an environment variable.
    • To test functions that read from the environment. At some point we should have a new solution for supporting this.
    • To do shell scripting, but in Rust. Not a big deal for us to use an unsafe function for this, though not ideal. [Edit: We can use Command::{env,envs}.]
  9. Nemo157 commented on Jan 4, 2022

    @Nemo157
    Contributor
    • To provide a default value for RUST_LOG=debug when it wasn't already set. We'd prefer a way to do this without setting the environment variable.

    https://docs.rs/env_logger/latest/env_logger/struct.Env.html#method.filter_or

    • To force RUST_BACKTRACE=full regardless of what the environment says. Again, we'd prefer a way to do this without setting an environment variable.

    #79085

    EDIT: Actually, this only affects the library backtrace, the panic runtime probably does also need a in-code setter too.

  10. leo60228 commented on Jan 4, 2022

    @leo60228
    ContributorAuthor
  11. briansmith commented on Jan 4, 2022

    @briansmith
    Contributor

    First, it turns out that our use of RUST_LOG was a bit different than I described. I agree current APIs are sufficient, at least for the logging crates we use.

    As far as testing goes, if the serial_test crate truly ensures no other threads are doing anything during the test, then that might work for our test uses of set_var and remove_var.

  12. briansmith commented on Jan 4, 2022

    @briansmith
    Contributor

    In order to use serial_test to make set_var and remove_var in a test safe, the tests would have to be integration tests and where there are no other tests in the same file (really, resultant binary), so that all the tests in the binary are marked #[serial], because #[serial] from the serial_test crate only serializes #[serial] tests; it doesn't prevent non-#[serial] tests from executing concurrently with #[serial] tests. Further, all of those #[serial] tests would need to avoid spawning threads (that outlive the test). So it'd be pretty error-prone.

  13. RalfJung commented on Jan 5, 2022

    @RalfJung
    Member

    To provide a default value for RUST_LOG=debug when it wasn't already set. We'd prefer a way to do this without setting the environment variable.

    Miri needs something similar here, and I think we somehow have two loggers here in this process so I don't know if we can use the env_logger API as a replacement.

  14. cgwalters commented on Jan 5, 2022

    @cgwalters
    Contributor

    Further, all of those #[serial] tests would need to avoid spawning threads (that outlive the test).

    And even inside such a serial test, any use of e.g. tokio or rayon could spawn threads in a not immediately obvious way. So I wouldn't bet on this approach as a good mitigation.

  15. 35 remaining items

  16. added 2 commits that reference this issue on May 2, 2024
    49f8cf1
    89fd614
  17. added a commit that references this issue on May 4, 2024
    f7e8269
  18. added 2 commits that reference this issue on May 13, 2024
    9358a14
    0da4674
  19. added 3 commits that reference this issue on May 24, 2024
    6dcfc96
    830780a
    5d8f9b4
  20. added a commit that references this issue on May 30, 2024
    a736755
  21. added a commit that references this issue on May 30, 2024
    91c0823
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-processArea: `std::process` and `std::env`C-bugCategory: This is a bug.E-help-wantedCall for participation: Help is requested to fix this issue.T-libs-api[DEPRECATED; DO NOT USE]

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions