Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stdenv: don't include drvs in disallowedRefs as build-time deps. #211783

Merged
merged 1 commit into from
Jan 31, 2023

Conversation

r-vdp
Copy link
Contributor

@r-vdp r-vdp commented Jan 20, 2023

Derivations listed as disallowedReferences or disallowedRequisites, currently end up as build-time dependencies.
This is problematic since the disallowed derivations will be built by nix as build-time dependencies, while those derivations might take a very long time to build, or might not even build successfully on the platform used. However, in order to scan for disallowed references in the final output, knowing the out path is sufficient, and the out path can be calculated from the derivation without needing to build it, saving time and resources.

While the problem is less severe for allowedReferences and allowedRequisites, since we want the derivation to be built eventually, we would still like to get the error early and without having to wait while nix builds a derivation that might not be used (e.g. if we listed the wrong one).

Before this PR:

$ nix-store --query --references (nix-store --query --deriver (nix build --print-out-paths --impure --expr 'with import <nixpkgs> {}; hello.overrideAttrs(_: { disallowedReferences = [ busybox ]; })'))
/nix/store/6xg259477c90a229xwmb53pdfkn6ig3g-default-builder.sh
/nix/store/8xiy49msk7nv3k3rmqzjxsspk3ssdmx8-bash-5.2-p15.drv
/nix/store/49dccsl44fyr6dvq9rwcgfr83girbyld-hello-2.12.1.tar.gz.drv
/nix/store/kh9inglxgaqj5098maivgy9iljbxpjww-stdenv-linux.drv
/nix/store/ip726rdk102f49mfkkkks81bqixrrqli-busybox-1.35.0.drv

After this PR:

$ nix-store --query --references (nix-store --query --deriver (nix build --print-out-paths --impure --expr 'with import ./. {}; hello.overrideAttrs(_: { disallowedReferences = [ busybox ]; })'))
/nix/store/6xg259477c90a229xwmb53pdfkn6ig3g-default-builder.sh
/nix/store/vqc7gh7myk6jpj642f9w2amygvazvjs6-bash-5.2-p15.drv
/nix/store/5ygyqwm9b47jfsxwck22fva8h15d50dg-stdenv-linux.drv
/nix/store/m58pdpyr80sais1bi36kicwc2bcd0s51-hello-2.12.1.tar.gz.drv

Note how in the first case busybox ends up as a build-time dependency, and will be built or substituted if it's not yet in the nix store. In the second case, with the changes in this PR, it no longer ends up as a build-time dependency and won't be built or substituted.

There is also an issue for nix itself to fix this on a lower level, where the approach implemented in this PR was suggested.
I don't agree with the conclusion on that issue and I think that it is a perfectly sensible use case to disallow references so that they cannot be accidentally included in the future and that we should not expect from our users to have to use unsafeDiscardStringContext to get the correct behaviour in that case.

Description of changes
Things done
  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandbox = true set in nix.conf? (See Nix manual)
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 23.05 Release Notes (or backporting 22.11 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
    • (Release notes changes) Ran nixos/doc/manual/md-to-db.sh to update generated release notes
  • Fits CONTRIBUTING.md.

Comment on lines 456 to 464
# knowing the out path is sufficient, and the out path can be calculated from
# the derivation without needing to build it, saving time and resources.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this interact with CA derivations

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should probably work (although a bit fragile) because drv.outPath will evaluate to a placeholder that will be rewritten just before the build if drv is part of the derivation closure. Which means that

  1. If drv is part of the closure (because it's refered to somewhere else), then it will do the right thing (the unsafeDiscardStringContext will morally be a no-op)
  2. Otherwise, the placeholder won't be rewritten, but it's fine since because the derivation isn't part of the build-time closure we know that it can't be in the runtime closure either

@Artturin
Copy link
Member

should target staging https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md#rebasing-between-branches-ie-from-master-to-staging and commit msg should follow the guide

@r-vdp r-vdp changed the base branch from master to staging January 20, 2023 20:34
@r-vdp r-vdp changed the title Don't include drvs in disallowedRefs as build-time deps. stdenv: don't include drvs in disallowedRefs as build-time deps. Jan 20, 2023
@r-vdp
Copy link
Contributor Author

r-vdp commented Jan 20, 2023

@Artturin: right, I didn't know about staging, that should be fixed now.
I updated the commit message as well.

Copy link
Contributor

@trofi trofi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I wonder if nix's derivation() builtin should already do the right thing. WDYT of filing a bug there? Sounds reasonable?

CA case is interesting. I think (hope) CA behaviour will not be changed by this context discard: our scanning operation does not need to pull in contents of checked derivations, but will still require building one to get the actual path for CA case. Thus I would guess this change will only help to non-CA builds. Which might be good enough. For CA-enabled builds such scans are probably already incorrect as there is no guarantee that build-CA has different store path from host-CA.

@r-vdp
Copy link
Contributor Author

r-vdp commented Jan 20, 2023

@trofi, agreed. Apparently there was an issue for nix already, but it got dismissed as not a bug, even though I think that the current behaviour is definitely surprising and unproductive and I don't think we should expect people to understand this intricacy and know about builtins.unsafeDiscardStringContext.

Regarding CA derivations, I must admit that I did not look into them enough to know how this would work in that case.

@trofi
Copy link
Contributor

trofi commented Jan 20, 2023

@trofi, agreed. Apparently there was an issue for nix already, but it got dismissed as not a bug, even though I think that the current behaviour is definitely surprising and unproductive and I don't think we should expect people to understand this intricacy and know about builtins.unsafeDiscardStringContext.

Good find! Worth adding a link to to the comment around builtins.unsafeDiscardStringContext?

Regarding CA derivations, I must admit that I did not look into them enough to know how this would work in that case.

I think it's OK to ignore it for now. We can always conditionally restore it back.

@r-vdp
Copy link
Contributor Author

r-vdp commented Jan 22, 2023

@trofi sure! I updated the description to include the reference, I didn't know about the issue when I made the PR.

@Artturin
Copy link
Member

i added a link to the nix issue

@Artturin
Copy link
Member

Artturin commented Jan 28, 2023

work for the future

__structuredAttrs supports per output closure checks

https://nixos.org/manual/nix/stable/release-notes/rl-2.2.html

outputChecks."out" = {
  # The closure of 'out' must not be larger than 256 MiB.
  maxClosureSize = 256 * 1024 * 1024;

  # It must not refer to C compiler or to the 'dev' output.
  disallowedRequisites = [ stdenv.cc "dev" ];
};

outputChecks."dev" = {
  # The 'dev' output must not be larger than 128 KiB.
  maxSize = 128 * 1024;
};

perhaps this could even be fixed in nix because there's not many users of structuredAttrs outputChecks yet

Artturin
Artturin previously approved these changes Jan 28, 2023
Copy link
Member

@Artturin Artturin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

built sway

Copy link
Member

@thufschmitt thufschmitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

Adding this to stdenv makes a lot of sense since we can't really fix it in nixpkgs itself

Comment on lines 456 to 464
# knowing the out path is sufficient, and the out path can be calculated from
# the derivation without needing to build it, saving time and resources.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should probably work (although a bit fragile) because drv.outPath will evaluate to a placeholder that will be rewritten just before the build if drv is part of the derivation closure. Which means that

  1. If drv is part of the closure (because it's refered to somewhere else), then it will do the right thing (the unsafeDiscardStringContext will morally be a no-op)
  2. Otherwise, the placeholder won't be rewritten, but it's fine since because the derivation isn't part of the build-time closure we know that it can't be in the runtime closure either

Comment on lines 463 to 470
disallowedReferences =
map unsafeDerivationToUntrackedOutpath (attrs.disallowedReferences or [ ]);
disallowedRequisites =
map unsafeDerivationToUntrackedOutpath (attrs.disallowedRequisites or [ ]);
allowedReferences =
lib.mapNullable unsafeDerivationToUntrackedOutpath (attrs.allowedReferences or null);
allowedRequisites =
lib.mapNullable unsafeDerivationToUntrackedOutpath (attrs.allowedRequisites or null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe (?) we could reduce the number of rebuilds by only adding these attributes if they were present if attrs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yes this introduces the attrs to all derivations

nix-repl> bash.allowedRe
bash.allowedReferences
bash.allowedRequisites
nix-repl> bash.disallowedRe
bash.disallowedReferences
bash.disallowedRequisites

Also why do the allowed ref use or null

Copy link
Contributor Author

@r-vdp r-vdp Jan 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They use or null since that is the no-op value for those attrs, an empty list would mean that nothing is allowed in the output.

Agreed for the other point though, we should not add the attributes if they were not present to begin with. I'll push a fix for that tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed an update to fix this and to include the insights on the outPath in the comment.

@thufschmitt
Copy link
Member

Apparently there was NixOS/nix#4629, but it got dismissed as not a bug, even though I think that the current behaviour is definitely surprising and unproductive and I don't think we should expect people to understand this intricacy and know about builtins.unsafeDiscardStringContext.

Nope. It is undoubtedly a bug, but a bug that we can't get rid of in the forseeable future because fixing it would change the hash of some derivations, which is a hard stop for Nix

@Artturin Artturin dismissed their stale review January 29, 2023 18:36

Introduces unnecessary attrs to all derivations

@trofi
Copy link
Contributor

trofi commented Jan 29, 2023

Apparently there was NixOS/nix#4629, but it got dismissed as not a bug, even though I think that the current behaviour is definitely surprising and unproductive and I don't think we should expect people to understand this intricacy and know about builtins.unsafeDiscardStringContext.

Nope. It is undoubtedly a bug, but a bug that we can't get rid of in the forseeable future because fixing it would change the hash of some derivations, which is a hard stop for Nix

Would an opt-in flag into derivation() to enable new behaviour be an option? (like __contentAddressed)

@thufschmitt
Copy link
Member

Would an opt-in flag into derivation() to enable new behaviour be an option? (like __contentAddressed)

It would. It would be less useful though since the main issue here is that it's fairly unintuitive, so people will likely overlook it until it actually bites.
Ideally we could warn if the only reference to a path is in one of these attributes, but that would be quite hard to implement because we don't really carry this information along.

Derivations listed as disallowedReferences or disallowedRequisites,
currently end up as build-time dependencies.
This is problematic since the disallowed derivations will be built by nix as
build-time dependencies, while those derivations might take a very long time
to build, or might not even build successfully on the platform used.
However, in order to scan for disallowed references in the final output,
knowing the out path is sufficient, and the out path can be calculated from
the derivation without needing to build it, saving time and resources.

While the problem is less severe for allowedReferences and allowedRequisites,
since we want the derivation to be built eventually, we would still like to
get the error early and without having to wait while nix builds a derivation
that might not be used (e.g. if we listed the wrong one).
@infinisil
Copy link
Member

I think this should really be fixed in Nix. With a proper deprecation strategy this should not be a problem. E.g. introduce a derivationSemantics attribute for derivation, defaulting to 0, but if it's explicitly set to 1 it will have the new behavior. Then to deprecate 0 and make 1 the default:

  • First give a warning when (derivationSemantics is unspecified or set to 0) and setting it to 1 would change semantics
  • In a future Nix version (once a NixOS release has passed), change this to an error, forcing everybody making use of the old semantics to set it to 1 or to not rely on them (with a builtins.unsafeDiscardStringContext)
  • Finally after more Nix and NixOS releases, change the default of derivationSemantics to 1, so that the new semantics are the default

If you want this to be more expressive you can also do something like __semantics.depCheckDeps = "include" | "exclude"

We should be brave enough to deprecate problematic behavior, otherwise these problems will never be fixed.

@Artturin Artturin merged commit fa5f0d6 into NixOS:staging Jan 31, 2023
@r-vdp r-vdp deleted the fix_disallowed_references branch February 1, 2023 00:04
@trofi
Copy link
Contributor

trofi commented Feb 1, 2023

Noticed content-addressed failures on today's system build. Small reproducer against staging:

$ nix build -f. ruby --arg config '{ contentAddressedByDefault = true; }'
error: derivation contains an illegal reference specifier '/0mklx5bj004nxkcf7snqj2ssbnlj5s04fllg1gmdqjdrdhjyvz3r'
error: 1 dependencies of derivation '/nix/store/nq950lipshdnjnv3j64nj88x7xhbczxs-ruby-2.7.7.drv' failed to build

Did not look in detail yet. Will spend some time this evening to understand it unless someone gets earlier to it.

@roberth
Copy link
Member

roberth commented Feb 1, 2023

  • Finally after more Nix and NixOS releases, change the default of derivationSemantics to 1, so that the new semantics are the default

No, that's an unnecessary breaking change. stdenv.mkDerivation hides this from users, so there's virtually nothing to be gained from this break, yet we lose the ability to import ancient nixpkgs, which is one of our unique selling points.

We should be brave enough to deprecate problematic behavior, otherwise these problems will never be fixed.

Deprecate yes, remove no. A subtle difference until it punches you in the face while you're trying to get some old software to work.

If you want this to be more expressive you can also do something like __semantics.depCheckDeps = "include" | "exclude"

I think it'd be ok for supposedly easy changes like this to be linearized into a single __edition int and perhaps not even have an individual flag.
If we'd do this often and exclusively in the way you suggest, we'd end up with a bunch of unnecessary bloat in each .drv file. Not the end of the world, but it is a cost that is multiplied.

@infinisil
Copy link
Member

  • Finally after more Nix and NixOS releases, change the default of derivationSemantics to 1, so that the new semantics are the default

No, that's an unnecessary breaking change. stdenv.mkDerivation hides this from users, so there's virtually nothing to be gained from this break, yet we lose the ability to import ancient nixpkgs, which is one of our unique selling points.

Fair enough. Instead then (and I think that's what you're implying), the default should be changed for just stdenv.mkDerivation so that most people have the benefits of the new semantics.


I think we should rethink this policy though, because not ever being able to deprecate old features really locks us in, requiring maintenance on code that may be only very rarely needed.

What if instead we were to not only specify a minimum support Nix version for nixpkgs (backwards compat), but also a maximum (forwards compat, perhaps with a date). Nixpkgs CI should make sure that it works with the minimum supported Nix version, and Nix CI should make sure it works with the earliest nixpkgs that supports its own version. This could be something like supporting Nix versions released around a NixOS release ± 5 years.

This does mean that we can't import arbitrary old nixpkgs versions anymore (there should be a check warning/erroring for that in nixpkgs), essentially limiting the time range of nixpkgs versions in a single Nix evaluation. But with a good error message we can present some workarounds:

  • Use an older Nix version, suggesting one that works (only works if you don't need to evaluate any super modern nixpkgs)
  • Show the semantic changes that happened between the newest supported version and the version you have, users can copy the expressions and update them according to those semantics if needed

This also very much relates to NixOS/rfcs#137, I'm abusing this issue as a scratch pad at this point 😅

trofi added a commit to trofi/nixpkgs that referenced this pull request Feb 1, 2023
Without the change build for packages that use `disallowedReferences`
fails in `contentAddressedByDefault = true` mode:

    $ nix build -f. ruby_3_1 --arg config '{ contentAddressedByDefault = true; }'
    ...
    error: derivation contains an illegal reference specifier '/0j3hif3ni7zl5zhlzzr5q2q23z66136mnzp75pyiqp5c72q14im2'
    error: 1 dependencies of derivation '/nix/store/39ji7qp225pxvrm8cgvzmyjqsyis2n0h-ruby-3.1.2.drv' failed to build

Original intent of NixOS#211783 was to
avoid pulling in actual derivation for reference scanning purposes.

Unfortunately CA derivations's outputPath are placeholders until they
are instantiated.

Let's restore string context for CA derivations for now.
@trofi
Copy link
Contributor

trofi commented Feb 1, 2023

Noticed content-addressed failures on today's system build. Small reproducer against staging:

$ nix build -f. ruby --arg config '{ contentAddressedByDefault = true; }'
error: derivation contains an illegal reference specifier '/0mklx5bj004nxkcf7snqj2ssbnlj5s04fllg1gmdqjdrdhjyvz3r'
error: 1 dependencies of derivation '/nix/store/nq950lipshdnjnv3j64nj88x7xhbczxs-ruby-2.7.7.drv' failed to build

Did not look in detail yet. Will spend some time this evening to understand it unless someone gets earlier to it.

Proposed restore of context just for CA derivations as #214044.

@roberth
Copy link
Member

roberth commented Feb 4, 2023

@infinisil I appreciate your care towards the Nix team, but "too much backcompat behavior" is not a problem I perceive in the Nix code.

I can build a 2013 zlib (using static = true to show that it actually builds; not just substitutes). We can probably build much older stuff, but 2013 is where the release branch names start.

$ nix-build -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/release-13.10.tar.gz --expr 'with import <nixpkgs> {}; zlib.override(o: { static = true; })'

This capability can be a lifesaver if you have to deal with a really bad legacy binary in some academic, corporate or government environment or whatever. It's even useful for running old binary linux games. Being able to do this (with care of course) is uniquely valuable.

Use an older Nix version, suggesting one that works

This prevents installing anything old alongside anything new in a single expression.

Show the semantic changes that happened between the newest supported version and the version you have, users can copy the expressions and update them according to those semantics if needed

That's a lot of effort for very little gain (removing a couple of lines of code from Nix).

Furthermore, creating and implementing a policy for breaking stuff is also work. A bad kind of work that will make nobody happy.

Let's not make new problems if we don't have to. We can revisit this when compatibility becomes a proper burden, but until then, enjoy a working Nix and Nixpkgs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants