Skip to content

Conversation

@lschuermann
Copy link
Member

Pull Request Overview

Motivated by #4588, this carves out an exception for Tock-controlled and maintained dependencies in our policy around external dependencies.

Testing Strategy

N/A

TODO or Help Wanted

N/A

Documentation Updated

  • Updated the relevant files in /docs, or no updates are required.

Formatting

  • Ran make prepush.

@lschuermann
Copy link
Member Author

lschuermann commented Sep 9, 2025

@bradjc Let's move the discussion of #4588 (comment) here, which I've seen only after filing this PR.

Copy link
Contributor

@bradjc bradjc left a comment

Choose a reason for hiding this comment

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

I'm concerned that we don't control crates.io, and once we include a crate from crates.io we lose any traceability about that external dependency.

I'm comfortable including tock org dependencies via github with a commit hash.

@ppannuto
Copy link
Member

ppannuto commented Sep 9, 2025

(moving the comments from other thread here)


@bradjc said

But I acknowledge that was for dependencies of boards (and perhaps individual capsule-crates), and we'd need to codify this exception in the ExternalDependencies.md document. I can draft up a PR.

Is there a reason to use crates.io or can we link to the repo directly? It seems like the crates.io source can change arbitrarily (as I think we are demonstrating here), but it would be nice if a change to the project required a change in Tock. But, now that I write that, github would probably redirect automatically if we moved the tock-reg repo to some other org (e.g., TockUnsafeCorp). Can we specify a git commit hash (and require that)?

Another semantics question we haven't addressed in this document is what constitutes an "external dependency"? External to the kernel git repo? External to the Tock git org? Is depending on a crate maintained in the kernel repo, but pulled in using crates.io external? What about a crate in the Tock GitHub org, and depended upon with a git hash?

I think of this based on what is in cargo.toml (ie what we can inspect by looking at this repo). I think anything through crates.io is clearly external. I think in github.com/tock is not external or internal, it's like pub(crate). Maybe those are "organizational external dependencies". While I think we can generally treat "organizational external dependencies" like internal dependencies, I don't think of them as the same because I think realistically they will not have the same CI treatment nor the same code review scrutiny. I think with good reason we should create "organizational external dependencies", but, not by default or without review.

I think tock-reg is an example where an "organizational external dependency" is appropriate.


@ppannuto said

IIUC, the concerns you're describing are literally the purpose of Cargo.Lock files.

In theory, we include Lock files in our releases for these reasons; in practice, we don't look to have actually done that.

More generally/usefully, I wonder whether this indicates that we should perhaps revisit our policy around Lock files — perhaps things have updated such that the churn would not be as painful? Messing around a little, I only get lock file changes when I switch to builds on/off the flux branch, which actually does change dependencies.

@bradjc
Copy link
Contributor

bradjc commented Sep 9, 2025

IIUC, the concerns you're describing are literally the purpose of Cargo.Lock files.

But that would require us to actually review lock file changes? Github hides those diffs (I think), and I personally don't want to review lock files.

Comment on lines +102 to +106
kernel repository itself. At the discretion of the Tock Core Working Group,
these crates may be depended upon by other Tock kernel crates, either through
pinned git revisions, tags, or through external repositories such as
<https://crates.io>.
Copy link
Member

Choose a reason for hiding this comment

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

I simultaneously get the value in not being overly prescriptive in policy (especially before we do much of this) and feel that we should just pick one instead of possibly ending up with multiple different approaches.

I suspect this will fall out organically as a result of the primary discussion on the thread.

My instinct is that we should try to keep moving towards "easing integration with the way the rest of the Rust ecosystem works" — i.e., we should use crates.io and provide Cargo.Lock files.

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason why I deliberately made this text very permissive is that we as the Tock project are the only ones who can promote any crate to be a "Tock-project controlled and maintained" crate.

With this documented motivated as to give guidance for contributors on which types of dependencies is acceptable, that didn't seem to apply here in quite the same way: we exercise discretion on how and what types of "Tock-project controlled" dependencies are included, depending on what works best for a particular situation.

At the same time, I see value in having clear guidance, even for us (who can collectively change, and thus "overrule" this document). So if this conversation leads to us deciding one way or the other, we should clarify here.

Copy link
Contributor

Choose a reason for hiding this comment

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

This document was created to provide guidance, as the policy before this was leaving everything up to the discretion of the tock project maintainers. Going back to that seems like undoing what we tried to make explicit with this document.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair, we can change this document to reflect the outcome of the discussion on this PR.

@ppannuto
Copy link
Member

ppannuto commented Sep 9, 2025

Github hides those diffs (I think), and I personally don't want to review lock files.

We removed Lock files from the repo in 2019 because of churn, they're .gitignore'd now.

I'll repeat [and update with time context] myself here:

Perhaps [over the last five years] things have updated such that the churn would not be as painful? Messing around a little, I only get lock file changes when I switch to builds on/off the flux branch, which actually does change dependencies.

@ppannuto
Copy link
Member

ppannuto commented Sep 9, 2025

We are certainly not the only people balancing Lock file churn, yet upstream changed the default to include Lock files in new projects in 2023. I am hoping this is indicative of tooling and support ergonomics improving.

@lschuermann lschuermann force-pushed the dev/external-deps-tock-crates-exception branch from 0d0f0dd to 0661f77 Compare September 9, 2025 23:23
@lschuermann
Copy link
Member Author

I do expect Tock to, due to its relatively stable crate structure and low number of relatively stable dependencies, have few if any lockfile churn. But it may well happen occasionally, and I acknowledge they're not very human-readable at all.

@bradjc
Copy link
Contributor

bradjc commented Sep 10, 2025

I am no lockfile expert, but since 2.2 (8 months) we have created 14 new Cargo.toml files. I think that would mean we would be reviewing two lock files a month, but I don't know how often they change.

❯ git co release-2.2
HEAD is now at 9554639b1 Merge pull request #4280 from tock/dev/release-2.2-version-bump-release-notes
❯ find . -name 'Cargo.toml' | wc -l
     110
❯ git co master
Previous HEAD position was 9554639b1 Merge pull request #4280 from tock/dev/release-2.2-version-bump-release-notes
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
❯ find . -name 'Cargo.toml' | wc -l
     124

Github hides those diffs (I think), and I personally don't want to review lock files.

We removed Lock files from the repo in 2019 because of churn, they're .gitignore'd now.

In the main tock repo, but not in others. In any case, is it not true that github hides lockfile diffs? I think by agreeing to lockfiles at least one person is committing to reviewing them?

@lschuermann
Copy link
Member Author

lschuermann commented Sep 10, 2025

In the main tock repo, but not in others. In any case, is it not true that github hides lockfile diffs? I think by agreeing to lockfiles at least one person is committing to reviewing them?

I think we should establish what exactly our threat model around lockfiles is. Is the fear that someone will lock a dependency to a deliberately incorrect or malicious version? Or just that an update to a dependency will be suck in, when we didn't intend that dependency to be updated?

From that, we could explore mechanical solutions. For instance, you could imagine having a CI workflow that posts a summary of the lockfile changes to a comment on PRs changing them.

For that, a tool like cargo-lockdiff looks promising.

@bradjc
Copy link
Contributor

bradjc commented Sep 10, 2025

IIUC, the concerns you're describing are literally the purpose of Cargo.Lock files.

In theory, we include Lock files in our releases for these reasons; in practice, we don't look to have actually done that.

More generally/usefully, I wonder whether this indicates that we should perhaps revisit our policy around Lock files — perhaps things have updated such that the churn would not be as painful? Messing around a little, I only get lock file changes when I switch to builds on/off the flux branch, which actually does change dependencies.

Lockfiles seem like a big hammer for our use case. In contrast, specifying a git project in our organization with a specific commit seems like precisely the tool we want for using our own external dependencies. We don't need the flexibility that comes with being able to use any crates.io repo from any underlying development source. We also don't need to be restricted to published releases, which was one hesitation for moving this repo outside tock/tock. Plus, what needs to be audit is trivially inspectable by humans, without relying on yet another external tool. Having github repos in Cargo.toml is well supported and widely used.

It's not clear to me what the upside of lockfiles is, for our use case. For the more conventional use case of using crates.io dependencies liberally, sure. But that isn't what we do or intend to do.

@ppannuto
Copy link
Member

is it not true that github hides lockfile diffs?

Huh, you are right. GitHub will detect generated files, a list which currently includes Cargo.Lock.

Interestingly, yarn lock files were removed from hidden-by-default policy because reviewing dependency information was considered important for security.

If we opt to bring back committed lock files, GitHub does allow you to use .gitattributes to override the decision, i.e., to mark Cargo.Lock as a non-generated file, and thus show its diffs by default. I agree that we should do this.

@lschuermann
Copy link
Member Author

lschuermann commented Sep 10, 2025

I am no lockfile expert, but since 2.2 (8 months) we have created 14 new Cargo.toml files. I think that would mean we would be reviewing two lock files a month, but I don't know how often they change.

Yes, adding a new crate will change the lockfile to at least include that new crate. The changes are rather minimal though:

leons@iron ~/p/t/kernel (dev/external-deps-tock-crates-exception)> diff Cargo.lock.1 Cargo.lock.2
572a573,586
> name = "litex_sim"
> version = "0.2.3-dev"
> dependencies = [
>  "capsules-core",
>  "capsules-extra",
>  "capsules-system",
>  "components",
>  "kernel",
>  "litex_vexriscv",
>  "rv32i",
>  "tock_build_scripts",
> ]
> 
> [[package]]
leons@iron ~/p/t/kernel (dev/external-deps-tock-crates-exception) [1]> cargo lockdiff --from Cargo.lock.1 --to Cargo.lock.2
| Package   | From | To        | Compare |
|-----------|------|-----------|---------|
| litex_sim | NEW  | 0.2.3-dev |         |

For doing things like adding Workspace-internal dependencies (e.g., adding tock-registers as a dependency to litex-sim), or dependencies that are already included in the Workspace elsewhere (e.g., adding ghash to litex-sim), cargo-lockdiff wouldn't generate a diff (as no versions have changed):

leons@iron ~/p/t/kernel (dev/external-deps-tock-crates-exception)> diff Cargo.lock.2 Cargo.lock.3
579a580
>  "ghash",
582a584
>  "tock-registers",
leons@iron ~/p/t/kernel (dev/external-deps-tock-crates-exception) [1]> cargo lockdiff --from Cargo.lock.2 --to Cargo.lock.3
No changes

And finally, adding an entirely new dependency or changing the version of one generates a diff accordingly:

Full `Cargo.lock` diff:
leons@iron ~/p/t/kernel (dev/external-deps-tock-crates-exception)> diff Cargo.lock.3 Cargo.lock.4
426c426
< version = "0.4.4"
---
> version = "0.5.1"
428c428
< checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
---
> checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
1085c1085
< version = "0.5.3"
---
> version = "0.6.2"
1087c1087
< checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
---
> checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
1461c1461
< version = "2.4.1"
---
> version = "2.6.1"
1463c1463
< checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
---
> checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1507c1507
< version = "0.4.1"
---
> version = "0.5.1"
1509c1509
< checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
---
> checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
1511c1511
<  "generic-array",
---
>  "crypto-common",

cargo-lockdiff output:

leons@iron ~/p/t/kernel (dev/external-deps-tock-crates-exception) [1]> cargo lockdiff --from Cargo.lock.3 --to Cargo.lock.4
| Package              | From  | To    | Compare   |
|----------------------|-------|-------|-----------|
| [ghash][01]          | 0.4.4 | 0.5.1 | [...][02] |
| [polyval][03]        | 0.5.3 | 0.6.2 | [...][04] |
| [subtle][05]         | 2.4.1 | 2.6.1 | [...][06] |
| [universal-hash][07] | 0.4.1 | 0.5.1 | [...][08] |

[01]: https://crates.io/crates/ghash
[02]: https://diff.rs/ghash/0%2E4%2E4/ghash/0%2E5%2E1/Cargo.toml
[03]: https://crates.io/crates/polyval
[04]: https://diff.rs/polyval/0%2E5%2E3/polyval/0%2E6%2E2/Cargo.toml
[05]: https://crates.io/crates/subtle
[06]: https://diff.rs/subtle/2%2E4%2E1/subtle/2%2E6%2E1/Cargo.toml
[07]: https://crates.io/crates/universal-hash
[08]: https://diff.rs/universal%2Dhash/0%2E4%2E1/universal%2Dhash/0%2E5%2E1/Cargo.toml

@lschuermann
Copy link
Member Author

It's not clear to me what the upside of lockfiles is, for our use case. For the more conventional use case of using crates.io dependencies liberally, sure. But that isn't what we do or intend to do.

This is somewhat tangential to this PR, but another problem with lockfiles is that they're "all or nothing". And right now, we're already pulling in crates from crates.io (such as ghash), which, without lockfiles, may change today -- which is a huge problem.

So, while I agree that lockfiles are a "big hammer", esp. for what we're proposing today, in the grand scheme of things I'm inclined to think that we actually want / need that "big hammer" to solve this and the other issues around dependency locking we have.

@ppannuto
Copy link
Member

Perhaps [over the last five years] things have updated such that the churn would not be as painful?

Exploring this question for a bit this morning... one of the things that was historically painful is Tock's "many binaries" situation. I.e., every time you change which board you are building for, you change the dependency graph, which could result in changes to Cargo.lock. I think this was the root of lots of the weird "lockfiles changing across developer machines" we used to experience.

Now that everything is in one Cargo workspace, if I understand correctly, Cargo considers the full dependency graph of all possible configurations, regardless of which one you are building. I.e., just building a different board (or boards in a different order, etc etc) should never result in changes to Cargo lockfiles.

This does come with the possibly-negative side effect of an 'overly broad' lockfile. I.e., a board that doesn't actually include the ghash dependency noted above would still "see it" in the Cargo lockfile — I'm not sure that's a substantial issue — but it could be misleading if someone were to (only) review our lockfile as a source of truth around external dependencies, since (again, IIUC) the lockfile will show the union of all dependencies for all boards.

@lschuermann
Copy link
Member Author

it could be misleading if someone were to (only) review our lockfile as a source of truth around external dependencies, since (again, IIUC) the lockfile will show the union of all dependencies for all boards.

Indeed, the lockfile isn't indicative of the exact set of dependencies for any particular board. It locks a superset of these revisions (although will remove entries when they're no longer depended upon anywhere in the workspace), but you can extract the actual transitive dependencies from it: start from the board crate, and walk the dependency tree. I'm sure there must be a tool for that already.

@lschuermann
Copy link
Member Author

Oh, and one final note: for crates.io dependencies the lockfile contains a checksum, so we're confident that we're building the right, known-good source, regardless of the crates.io infrastructure. But for workspace-local dependencies it does not. This means that any PR not touching the crate and dependency structure should not impact the lockfile.

@bradjc
Copy link
Contributor

bradjc commented Sep 10, 2025

We only have one lock file, in the root, correct? That is certainly an improvement over the prior situation. Having all the dependencies is strange for boards but makes sense for workspaces. Honestly, a single lockfile that isn't constantly changing isn't so bad.

What I find dissatisfying about using lockfiles, however, is how passive/cargo-only they are. There is a checksum, that cargo can check, but it's opaque (I think?) to everything else. They are autogenerated, and not (really) for human consumption. That seems like a weird middle ground to me: useless and annoying for users who don't care about dependency tracking, and oddly passive for users who do (ie us). Something like brew files makes more sense to me: the brew file itself contains the hash (eg https://github.com/Homebrew/homebrew-core/blob/0f44549c1203ae616c22ec21f120ce9f588f0a40/Formula/r/rustup.rb#L10). The developer makes a conscious decision about what is correct and actively inserts the valid hash in the file meant for inspection.

It would be nice if we could have some other source of truth about what the correct dependency is. We could even let cargo handle all of the sub dependencies.

For example, with a git commit hash in a cargo.toml I can go to the github and look at what that commit is and independently see if I think it is an ok commit to use as a dependency. Is there some way to do that with cargo? Can I download something from crates.io and generate that checksum?

The obvious resolution to me is if cargo would let us put that checksum in cargo.toml. If that checksum changes it is obvious something changed, and we would have some way to check that what it changed to is ok. It doesn't make sense to me that cargo.lock should ever change without a change to cargo.toml. Hmm, but it can change if cargo decides to use a different version of the crate than specified to meet some dependency mismatch?

My hesitation is definitely based on a healthy distrust of cargo in general. Outsourcing everything to it and its own autogenerated files seems like it carries some risk. Again, I think that risk would be mitigated if I, as a board author, can specify a hash in my cargo.toml and know that the guarantee is that cargo uses that version, or I get an error. No letting cargo make unintuitive decisions.

We have been buoyed by the simple approach we have generally used: no external dependencies. Changing that, I think, means we need new workflows to handle risks we don't have today. Changing that to cargo is not my preference, as cargo has a track record of not supporting us:

  • Mandates hidden files for configuration
  • No scanning of dependencies for unsafe
  • No cross-platform testing

It just seems so much more intuitive to me to drop crates.io entirely and just specify github repos and commit hashes for dependencies.

@lschuermann
Copy link
Member Author

lschuermann commented Sep 10, 2025

We only have one lock file, in the root, correct?

Correct. I think we technically have two workspaces (one for tools/), but checking in one lockfile doesn't force us to make a choice about the other workspace.

It just seems so much more intuitive to me to drop crates.io entirely and just specify github repos and commit hashes for dependencies.

The main argument against this is transitive dependencies. Even if we pin ghash by git revision, it itself will depend on crates on crates.io, and then those will not be pinned. This leaves us in about as bad of a place as not pinning dependency revisions at all.

We hypothetically could override that dependencies' sub-dependencies, and then those sub-dependencies' sub-sub-dependencies, but I think this will become tiring very fast. We're going to easily miss some dependencies, esp. when they're added or removed across updates.

So, while I empathize with and share many of your frustrations around lockfiles and the state of the ecosystem right now, I don't feel like it'll be great for us to either

  • fight the standard that's been established and the direction in which the ecosystem is moving (lockfiles), or
  • just don't pin external dependency versions. Cargo is incredibly permissive about implicitly updating revisions if semver indicates they should be compatible, and that's a recipe for disaster in a security-sensitive setting like Tock.

In summary, despite these concerns, I think lockfiles are the path of least resistance. Hopefully we can make them a little less painful with more automation and tooling.

@ppannuto
Copy link
Member

Ugh... in the spirit of "cargo doesn't give what I would want..."

I think it is the case that we would like the default behavior for all tock builds to be cargo build --locked, i.e., Cargo.lock files should not change unless the developer does something explicit, e.g. cargo build --unlocked. I think forcing developers to be explicit about "unlocking" would go a long way towards limiting accidental churn as well.

I would like to express this in Cargo.toml as a project configuration.... However, I'm inferring from the documentation mentioning net.offline, but nothing for locked as well as a failure to ctrl-f for locked successfully on the cargo config documentation page that cargo doesn't support such a feature. I would love to be wrong about this...

We could do the "build defaults to --locked" policy via our Makefiles... does the idea of "build defaults to --locked" make sense, or am I missing something obvious?


edit: Oh yeah, here's the five year old issue asking for the feature; not a lot of traction :(

@ppannuto
Copy link
Member

i.e., we could do:

$ git diff
diff --git a/boards/Makefile.common b/boards/Makefile.common
index 4d370d5d3..0f06290cd 100644
--- a/boards/Makefile.common
+++ b/boards/Makefile.common
@@ -323,10 +323,10 @@ $(TOCK_ROOT_DIRECTORY)tools/build/sha256sum/target/debug/sha256sum:

 .PHONY: $(TARGET_PATH)/release/$(PLATFORM)
 $(TARGET_PATH)/release/$(PLATFORM):
-       $(Q)$(CARGO) rustc $(VERBOSE_FLAGS) --bin $(PLATFORM) --release
+       $(Q)$(CARGO) rustc $(VERBOSE_FLAGS) --bin $(PLATFORM) --release --locked
        $(Q)$(SIZE) $(SIZE_FLAGS) $@

 .PHONY: $(TARGET_PATH)/debug/$(PLATFORM)
 $(TARGET_PATH)/debug/$(PLATFORM):
-       $(Q)$(CARGO) build $(VERBOSE_FLAGS) --bin $(PLATFORM)
+       $(Q)$(CARGO) build $(VERBOSE_FLAGS) --bin $(PLATFORM) --locked
        $(Q)$(SIZE) $(SIZE_FLAGS) $@

Which, for a fun (?) data point, did affect 20 crates:

[-bash] Wed 10 Sep 15:32 [[master] ~/temp/tock]
$ make -C boards/hail
    Updating crates.io index
     Locking 20 packages to latest compatible versions
      Adding base16ct v0.2.0
      Adding block-buffer v0.10.4
      Adding const-oid v0.9.6
      Adding crypto-bigint v0.5.5
      Adding crypto-common v0.1.6
      Adding der v0.7.10
      Adding digest v0.10.7
      Adding ecdsa v0.16.9
      Adding elliptic-curve v0.13.8
      Adding ff v0.13.1
      Adding group v0.13.0
      Adding hmac v0.12.1
      Adding p256 v0.13.2
      Adding primeorder v0.13.6
      Adding rand_core v0.6.4
      Adding rfc6979 v0.4.0
      Adding sec1 v0.7.3
      Adding sha2 v0.10.9
      Adding signature v2.2.0
      Adding zeroize v1.8.1
   Compiling tock-tbf v0.1.0 (/Users/ppannuto/temp/tock/libraries/tock-tbf)
   Compiling tock-registers v0.10.0 (/Users/ppannuto/temp/tock/libraries/tock-register-interface)
   Compiling tock-cells v0.1.0 (/Users/ppannuto/temp/tock/libraries/tock-cells)
   Compiling enum_primitive v0.1.0 (/Users/ppannuto/temp/tock/libraries/enum_primitive)
   Compiling tickv v2.0.0 (/Users/ppannuto/temp/tock/libraries/tickv)
   Compiling hail v0.2.3-dev (/Users/ppannuto/temp/tock/boards/hail)
   Compiling kernel v0.2.3-dev (/Users/ppannuto/temp/tock/kernel)
   Compiling cortexm v0.2.3-dev (/Users/ppannuto/temp/tock/arch/cortex-m)
   Compiling capsules-core v0.2.3-dev (/Users/ppannuto/temp/tock/capsules/core)
   Compiling cortexv7m v0.2.3-dev (/Users/ppannuto/temp/tock/arch/cortex-v7m)
   Compiling segger v0.2.3-dev (/Users/ppannuto/temp/tock/chips/segger)
   Compiling capsules-system v0.2.3-dev (/Users/ppannuto/temp/tock/capsules/system)
   Compiling cortexm4 v0.2.3-dev (/Users/ppannuto/temp/tock/arch/cortex-m4)
   Compiling sam4l v0.2.3-dev (/Users/ppannuto/temp/tock/chips/sam4l)
   Compiling capsules-extra v0.2.3-dev (/Users/ppannuto/temp/tock/capsules/extra)
   Compiling components v0.2.3-dev (/Users/ppannuto/temp/tock/boards/components)
    Finished `release` profile [optimized + debuginfo] target(s) in 18.18s
   text	   data	    bss	    dec	    hex	filename
 112172	      0	  19916	 132088	  203f8	/Users/ppannuto/temp/tock/target/thumbv7em-none-eabi/release/hail
75e53c39c68f829d5a1fdde037012560160922f07578471a19c8882983a6fe0b  /Users/ppannuto/temp/tock/target/thumbv7em-none-eabi/release/hail.bin

which lends some credence to me for Leon's most recent point that (1) we aren't doing a service to reproducibility with our current setup, and (2) managing (sub-)dependencies will be annoying in practice.

@lschuermann
Copy link
Member Author

lschuermann commented Sep 10, 2025

I like the idea of a "default frozen" lockfile. But, do we actually care about downstream users making changes to lockfiles? I don't know what this would buy us.

We already have a gatekeeper for ensuring we keep lockfiles current: CI. We should definitely add a check that, for whatever is merged, the lockfile matches what's in tree (i.e., do a cargo check --locked in CI).

For regular Cargo operations, it should also never on its own update dependency versions that are tracked in the lockfile. Only cargo update should ever touch existing locked dependency versions. And a tool like cargo-lockdiff could help us spot pretty easily when that's being done in a PR.

@ppannuto
Copy link
Member

ppannuto commented Sep 10, 2025

But, do we actually care about downstream users making changes to lockfiles? I don't know what this would buy us.

My motivation is more about "least surprise" for downstream users. I.e., if I understand how Cargo works, if I clone the Tock repo and run cargo build on a fresh machine, cargo will

1. Use `Cargo.toml` to determine what dependencies are needed
2. Check the local, machine-specific cache of downloaded crates (empty)
3. Resolve missing dependencies by downloading the newest versions that match semver requirements for all crates
4. Update `Cargo.lock` to note which crates were actually used in this build

Whereas, cargo build --locked would change step 3 to:

3. Download the exact versions in the existing `Cargo.lock` file

and omit step 4.


IIUC, by default, if I clone the Tock repo, and one of its dependencies happens to have updated, I'll do a build, and suddenly have a dirty git tree. That's a terrible user experience!

Now, this kinda-sorta all goes away if we never use semver in Tock Cargo.toml files, but as-discussed with pinning earlier, we can't enforce that for dependencies, so that's not really a solution I don't think.

(or, do I mis-understand how cargo works?)

@brghena
Copy link
Contributor

brghena commented Sep 10, 2025

IIUC, by default, if I clone the Tock repo, and one of its dependencies happens to have updated, I'll do a build, and suddently have a dirty git tree. That's a terrible user experience!

Poking in to agree with this. It would be great to avoid users automatically, unintentionally updating lock files that are part of the build system.

@lschuermann
Copy link
Member Author

lschuermann commented Sep 11, 2025

IIUC, by default, if I clone the Tock repo, and one of its dependencies happens to have updated, I'll do a build, and suddently have a dirty git tree. That's a terrible user experience!

Poking in to agree with this. It would be great to avoid users automatically, unintentionally updating lock files that are part of the build system.

This should never happen, and if it does, I'd consider this a bug.

The lock file should only ever change when adjusting the crate and dependency tree, or when running cargo update. The entire point of lockfiles is to not have any implicit dependency updates.

And on those changes that do require updates to the lockfile, we will have CI reject PRs that don't update it.

@ppannuto
Copy link
Member

It is, as always, very possible that I do not "UC" :)

@bradjc
Copy link
Contributor

bradjc commented Sep 11, 2025

This should never happen, and if it does, I'd consider this a bug.

The lock file should only ever change when adjusting the crate and dependency tree, or when running cargo update. The entire point of lockfiles is to not have any implicit dependency updates.

And on those changes that do require updates to the lockfile, we will have CI reject PRs that don't update it.

What is the difference between cargo build and cargo build --locked? I'm having a hard time wrapping my head around what --locked could possibly do. Isn't the point of _lock_ing something (in a lockfile) that it is fixed? How can there be an option to ignore that (not using --locked?) or not (use --locked)? Why is this left to the user and not the crate developer?

@ppannuto
Copy link
Member

image

Okay AI, I actually found this helpful. I am cautiously optimistic that moving to workspaces fixed most of our lockfile ergonomics headaches. It used to suck because we used to change the dependency tree every time we changed boards.

My scenario above was wrong because there will now be a Cargo.lock checked into the repo, so a fresh clone will use that information, i.e., it's more like:

1. Use `Cargo.toml` to determine what dependencies are needed
2. Check `Cargo.lock` to see if the versions listed there satisfy the dependencies outlined in `Cargo.toml`
  2-Yes: Use the versions listed in the lock file to create a build plan
  2-No: Query crates.io (or equivalent) to pick versions that will allow creation of a valid build plan
3. Check the local, machine-specific cache of downloaded crates for the versions called for in the build plan (empty)
4. Resolve missing dependencies by downloading the selected versions
5. Do the build
6. [If 2-No above] Update `Cargo.lock` to note which crates were actually used in this build

So the common-case path is 2-Yes

@lschuermann
Copy link
Member Author

lschuermann commented Sep 11, 2025

IIUC the LLM got one thing wrong: when you change a dependency in Cargo.toml, then a cargo build will update the Cargo.lock as needed, but that's not the same as cargo update.

cargo update updates all dependencies to their latest semver-compatible versions.

In that sense cargo build does exactly what we want: it makes the most conservative, minimal changes possible to the lockfile to make the build work according to our dependency specifications and the existing lockfile.

@bradjc
Copy link
Contributor

bradjc commented Sep 11, 2025

I think we can add this to the list of things that cargo doesn't support us on. In my opinion, the correct behavior is:

  • Is there a cargo.lock?
    • YES: Enforce it. It must match, or error. Cargo can provide some other tool to help update it, but things are locked! There is no way around this.
    • NO: Just build

So, cargo build --locked-if-present should be the default.

What is the value if you think "tock robustly handles dependencies so i don't have to worry" and then cargo just decides to update the lockfile and build the kernel however it wants? If you happen to be using git, sure, you will see a changed file. If not using git, good luck. But why would a git diff showing output be the way to signal "CAUTION: your dependencies have not been vetted!!"? I'm thinking how we would teach this to someone.

I hope I'm still missing something.

@lschuermann
Copy link
Member Author

@bradjc Cargo tries to strike a balance between not getting in your way for development (automatically adjusting your lockfile if you make any changes that mandate those updates), and providing a reliable way to build a release using the exact set of locked versions (--locked).

I think the Cargo behavior is a good tradeoff.

@ppannuto
Copy link
Member

For common-case development, I agree. For the security/correctness domain that Tock lives in, I think I'm of the same mind of Brad.

I wish that we as a project could "opt in" to a stronger, if slightly-less-"developer-friendly" behavior. I want to set a build.locked in the workspace Cargo.toml, and if you as a Tock developer do need to do something that will update a lockfile, you should explicitly call cargo build --unlocked (or temporarily remove the config line).

I want the build to error if developers accidentally do something that changes dependencies—that's not a common need, and I think Tock should introduce a little bit of intentional friction to make sure users have really thought through what they are doing and understand the implications.

@bradjc
Copy link
Contributor

bradjc commented Sep 11, 2025

I wish that we as a project could "opt in" to a stronger, if slightly-less-"developer-friendly" behavior. I want to set a build.locked in the workspace Cargo.toml, and if you as a Tock developer do need to do something that will update a lockfile, you should explicitly call cargo build --unlocked (or temporarily remove the config line).

I want the build to error if developers accidentally do something that changes dependencies—that's not a common need, and I think Tock should introduce a little bit of intentional friction to make sure users have really thought through what they are doing and understand the implications.

I completely agree, and I think this is the best articulation so far.

Our current approach is maximal friction: hard no. In comparison, having to run one command or update a hash is of, relatively speaking, minimal overhead.

@ppannuto
Copy link
Member

Sadly, as noted yesterday, Cargo doesn't support that workflow natively.

I don't think that's a deal-breaker for turning back on lockfiles; I think I'd like to do it with the --locked flag in our Makefiles at least, with some ergonomics we provide akin to the --unlock concept and some explainer text we emit if the build fails.

@bradjc
Copy link
Contributor

bradjc commented Sep 11, 2025

Sadly, as noted yesterday, Cargo doesn't support that workflow natively.

I don't think that's a deal-breaker for turning back on lockfiles; I think I'd like to do it with the --locked flag in our Makefiles at least, with some ergonomics we provide akin to the --unlock concept and some explainer text we emit if the build fails.

I'm concerned about swinging too far. I think Alex noted this concern as well. Going from "hard no, too risky" to "mostly checked, unlikely to be bad" is pretty drastic. Particularly because we have a very easy alternative: just do nothing. We don't strictly need to follow through with our split-out-tock-reg plan.

We can still do the --locked thing for the capsule dependencies, which does improve things.

@lschuermann
Copy link
Member Author

lschuermann commented Sep 11, 2025

"hard no, too risky" to "mostly checked, unlikely to be bad" is pretty drastic

The problem here is that we're discussing a potential solution to two separate issues:

  • splitting out tock-registers, and ensuring that adding additional dependencies from crates.io does not expose us to more risk (even for dependencies we control)

  • addressing the fact that we don't do any locking at all of any kind with the existing external dependencies we have

In that sense, I think it's a mischaracterization to say we currently have a "hard no, too risky" policy, at least in practice. The status quo is bad. In blunt terms, I think we screwed up with how we implemented the external dependency policy in practice. What we have right now is not "mostly checked, unlikely to be bad", it's "we blindly pull in whatever". The situation right now was the one we wanted to avoid.

So, I strongly believe we should discuss lockfiles in that context, or we have to reconsider our external dependency story more broadly.


On a less abstract level, I wouldn't mind if we had a build.locked = true flag we could set. But I'm still convinced that the default behavior is an improvement over what we have now, and a CI check can ensure we retain control of, and always have an up-to-date lockfile.

@ppannuto
Copy link
Member

addressing the fact that we don't do any locking at all of any kind with the existing external dependencies we have

Yeah, the discussion on this PR really deviated from its title, its actual changes, (and frankly its original purpose).

We probably need a new PR [and probably resolve it first] that brings back Cargo.lock independent of any changes to tock-registers or our external dependency policy.

@bradjc
Copy link
Contributor

bradjc commented Sep 11, 2025

In that sense, I think it's a mischaracterization to say we currently have a "hard no, too risky" policy, at least in practice. The status quo is bad. In blunt terms, I think we screwed up with how we implemented the external dependency policy in practice. What we have right now is not "mostly checked, unlikely to be bad", it's "we blindly pull in whatever". The situation right now was the one we wanted to avoid.

This is fair, but the reason we didn't focus more on this is the ability to opt out is trivial. Boards can easily just not include the "special" capsules that have dependencies. Still, we can improve this. It seems like lockfiles are not as bad as they were, and for these dependencies I think "we blindly pull in whatever" to "mostly checked, unlikely to be bad" is a nice improvement.

In contrast, no one can opt out of including kernel/, hence, at least my, increased scrutiny. Here, we do currently have a hard no policy. By adding dependencies we are moving the enforcement of guarantees from what is directly in the tock repo to a combination of us sticking to policy around dependencies we control and cargo. I think we should weigh the pros and cons of this on its own, regardless of what we do with capsules.

For example, does the tock/tock ExternalDependencies.md apply to tock/tock-registers? I think today it clearly would not by default (we use dependencies in libtock-rs, for example). I assume we wouldn't want to add dependencies to tock-registers, but I also think we would need to somehow recreate the work we've done in ExternalDependencies.md to apply to kernel-affiliated tock repos.

It's also easier for me at least to understand how we enforce our policies today via PR review in one place. Maybe this is too hypothetical, but if a PR changed the tock-registers version in tock/tock, is there any assumed trust (because presumably the change was reviewed before being merged to tock-registers) if the dependency tree changes? Or is the expectation that it would be entirely re-reviewed as a kernel change?


Ok to get more pragmatic, I prefer the status quo. What we have today allows tock-registers to be on crates.io and to be included directly from the repo thanks to cargo. I don't think there is any specific, concrete limitation of our current approach. Perhaps certain dependency trees where registers are passed between tock/tock crates and other crates it causes, friction, but we believe that can be resolved.

That being said, I think I am ok with an alternative, including:

  1. Adding back cargo.lock to tock/tock.
  2. Adding an exception to ExternalDependencies.md that explicitly permits tock-registers (and only tock-registers) to be an external dependency of the kernel. ExternalDependencies.md would need to also include:
    1. The exact justification for why tock-registers has more value outside the repository (and as such needs an exception)
    2. Explicit language that says just because tock-registers is external does not in any way imply other crates are likely to also get an exception from the policy.
  3. The documentation of tock/tock-registers would clarify that the tock-registers crate can not have any dependencies of any kind, and that changing that policy requires unanimous approval from wg/core.

I think that is consistent with the spirit of our no dependency policy.

@ppannuto
Copy link
Member

2.i The exact justification for why tock-registers has more value outside the repository (and as such needs an exception)

I've been thinking back some on why we came to this decision in the first place. Here are some of the motivating factors I can recall. I don't think there's any one "killer need" here, but more of a "pressure of factors" kind of situation.

I think they all stem from a philosophical question on whether we, as the Tock project, see value in providing tock-registers to the greater Rust ecosystem as something wholly independent of Tock. (I bolded and separated this after writing the below, since I think this is really the discussion here—if we do, I don't really see a way to do this well leaving it inside the main repo)

  1. Forcing Consideration of External Users with API Changes — We historically didn't differentiate much between changes to a tock "library" from other parts of Tock. I.e., if tock needed a new feature in tock-registers, we'd just add it. But at the time*, we were seeing more adoption external users (this was not long after an interface-breaking change tripped up Andre IIRC; it was an easy, mechanical fix, but nonetheless surprising). I think we do a much better job of this by implicit policy today, but it requires reviewers to notice the directory the change is in, and to think to consider external folks.

  2. Encouraging Regular Releases for Updates (independent of Tock releases) — Tock doesn't really have any "pressure" to publish new tock-registers releases. Any bugfixes or feature enhancements come 'immediately' to Tock. In practice, this means that we don't really do a tock-registers release until someone downstream bothers to ask us. Now, tock-register also doesn't change much, but the practical reality is that we've left useful changes lingering for years between releases.

  3. Easing Engagement with External Users — As a small folder in a big project, it's hard for downstream tock-registers users to track the development of tock-registers in isolation, both to monitor for salient changes, or to provide feedback. The relates to the next point.

  4. Optics / Invitation for External Users — It's not necessarily obvious that the Tock project would entertain changes or improvements to tock-registers that aren't explicitly needed by Tock itself. Or relatedly, whether it's a reasonable thing for tock-registers to be used standalone from Tock. Plenty of 'mono-projects' publish multiple crates (e.g. flux has like 6-8 crates, but they can't really be used independently). Being in a standalone repo makes it clearer that tock-registers is a standalone thing, and can be used as such.

*I do think external adoption has slowed, and I'm not sure that's a good thing. I think we do have a better MMIO approach, and people should use it...

@bradjc
Copy link
Contributor

bradjc commented Sep 11, 2025

Based on commit history descriptions, there doesn't seem to have been any meaningful changes to tock-registers since 01/2024. https://github.com/tock/tock/commits/master/libraries/tock-register-interface

The reasons are certainly compelling. In a perfect world cargo would make this easy for us and allow us to specify constraints on the dependency from kernel/cargo.toml.

It would be cool to have more visibility around tock-registers 2.0.

@lschuermann
Copy link
Member Author

I'm sympathetic to distinguishing the tock-registers case (and, by extension, all external dependencies of crates in the kernel or arch subdirectories) from the other dependencies we have.

I feel much less strongly about needing to split out tock-registers (though I agree with the points raised in @ppannuto's summary), than I think we should remedy our current handling of other external dependencies. To that end, I'm happy to open another PR that proposes to add back the Cargo.lock, detached from this discussion.


A remaining question on my end: why would a lockfile only deliver "mostly checked, unlikely to be bad", vs. "fully checked, guaranteed to point to vetted versions"? It would seem to me that it actually allows us to have pretty strong guarantees about which particular revision of tock-registers (or other crates) we're using.

In particular, if a crate is once added to Cargo.lock, then it should never implicitly change, unless a Cargo.toml in a crate requests a version incompatible with the pinned one, or a user runs cargo update.

So, assuming we'd be adding back a lockfile for other, external dependencies, what's the missing piece for using this same mechanism for Tock-owned "external" crates?

@ppannuto
Copy link
Member

why would a lockfile only deliver "mostly checked, unlikely to be bad"

IIUC, because cargo does not default to --locked (nor can we force it to). "Unlikely" but feasible scenario:

  • User gets a tarball/zip of Tock (i.e., no version control to detect/notice Cargo.lock changes)
  • It's a new machine, so no cache of existing stuff
  • For some reason the version specified in the lockfile isn't available, but there is a semver-compatible version available
  • Tock gets built using something other than what we specified

(n.b., I write this as answer to the question / to better understand cargo limitations, not as a critique of moving forward with lockfiles, since currently we offer no opinion to the tarball user about which versions to use)

@ppannuto
Copy link
Member

Or more generally,

In particular, if a crate is once added to Cargo.lock, then it should never implicitly change

I believe "unlikely" is every feasible scenario that prevents that "should" from being "must" :)

@lschuermann
Copy link
Member Author

lschuermann commented Sep 12, 2025

For some reason the version specified in the lockfile isn't available, but there is a semver-compatible version available

AFAIK, even if you yank a version from crates.io, it'll still be available to everyone who has it in their lockfile. It just won't be publicly listed any more. At least that's what I was told some years back. EDIT: the docs seem to confirm this:

The yank command removes a previously published crate’s version from the server’s index. This command does not delete any data, and the crate will still be available for download via the registry’s download link.

Cargo will not use a yanked version for any new project or checkout without a pre-existing lockfile, and will generate an error if there are no longer any compatible versions for your crate.

It'd be a good experiment to see what Cargo's behavior is when specifying a dependency version in the lockfile that doesn't exist at all (e.g., never published to crates.io). I'd hope it to error, and not to implicitly choose a different version. If it doesn't do that, I think we should file a bug.

Otherwise, assuming we have CI that ensures that upstream lockfiles are always current, I don't think this should ever happen, and cannot conceive of how it would happen.

But I full acknowledge and empathize with the concern that users can relatively easily do something locally (like change their Cargo.toml) which will implicitly cause the lockfile to change. Which is not great. Pushing for that Cargo issue or trying to implement this ourselves seems good regardless of us adding back lockfiles. But realistically, even if we could get that into upstream soon, it probably won't be available to us for a few months/years on stable...

That being said, Cargo also should never touch dependencies for which the user hasn't adjusted the semver string (except for an explicit cargo update). So in that sense, having the file be updated is at least somewhat predictable.

@bradjc
Copy link
Contributor

bradjc commented Sep 12, 2025

Otherwise, assuming we have CI that ensures that upstream lockfiles are always current, I don't think this should ever happen, and cannot conceive of how it would happen.

I find it pretty convincing that if cargo felt the need to add --locked then what they have without --locked is not, fully, locked. Having --locked is a safety valve for them (e.g., well if users actually care about X, then they would be using --locked, so doing Y, which affects X, is ok).

If it turns out that I'm wrong, and that if you have a cargo.lock file, and whether you include --locked or not has exactly identical behavior forever, then great! That is a win.


What about:

  1. An attacker manages to get your local copy of a package corrupted somehow. Maybe this isn't needed, but in any case, cargo can't use it.
  2. You do something like what Tyler is doing and add the same dependency to another crate. You assume that because it is in Tock upstream it has been vetted (which I think is fair). You use the same version number in cargo.toml as in upstream tock.
  3. Cargo can't use your local version. Cargo sees that there is new, semver compatible version on crates.io which it uses. It silently updates your "lock" file. You don't notice the version number in the build output. You aren't in git so you don't notice the "lock" file has changed.

Note: I don't actually know if this could happen today with cargo, but it doesn't seem to violate any promises cargo makes.

Before the xz/ssh attack I didn't think convoluted stuff like this was possible. But that illusion has been shattered for me. If cargo can't guarantee that it will always enforce the lockfile (without --locked), then I don't trust that there isn't some path some sufficiently savvy attacker can find around it.

@lschuermann
Copy link
Member Author

lschuermann commented Sep 17, 2025

Before the xz/ssh attack I didn't think convoluted stuff like this was possible. But that illusion has been shattered for me. If cargo can't guarantee that it will always enforce the lockfile (without --locked), then I don't trust that there isn't some path some sufficiently savvy attacker can find around it.

I'm always happy to entertain weird hypotheticals, but I think we're getting pretty off track at this point. If your basic assumption starts with "an attacker gets your crate corrupted", it's a pretty easy jump to "your entire toolchain is compromised". Updating to a newer revision of a crate would be a pretty benign outcome in that scenario, all things considered.

However, because I couldn't let this go untested, here's the result. As suspected, Cargo doesn't do any integrity checks of any locally stored crates. Once anybody has write access to your ~/.cargo folder it's game over, regardless of how we specify dependencies and whether we use lockfiles or not:

Console output:
leons@iron ~/h/d/tock-registers-modified-dep (main)> cargo add tock-registers
    Updating crates.io index
      Adding tock-registers v0.10.0 to dependencies
             Features:
             + register_types
    Updating crates.io index
     Locking 1 package to latest Rust 1.88.0 compatible version
leons@iron ~/h/d/tock-registers-modified-dep (main)> ff src/main.rs 
Waiting for Emacs...
leons@iron ~/h/d/tock-registers-modified-dep (main)> cat src/main.rs
fn main() {
    use tock_registers::interfaces::Readable;
    println!("InMemoryRegister value: {}", tock_registers::registers::InMemoryRegister::<u8, ()>::new(42).get());
}
leons@iron ~/h/d/tock-registers-modified-dep (main)> cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/tock-registers-modified-dep`
InMemoryRegister value: 42
leons@iron ~/h/d/tock-registers-modified-dep (main)> ff ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tock-registers-0.10.0/src/registers.rs
leons@iron ~/h/d/tock-registers-modified-dep (main)> grep -C2 "HACKED" ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tock-registers-0.10.0/src/registers.rs
impl<T: UIntLike, R: RegisterLongName> InMemoryRegister<T, R> {
    pub const fn new(value: T) -> Self {
        panic!("HACKED!!!");
        InMemoryRegister {
            value: UnsafeCell::new(value),
leons@iron ~/h/d/tock-registers-modified-dep (main)> cargo clean
     Removed 32 files, 7.7MiB total
leons@iron ~/h/d/tock-registers-modified-dep (main)> cargo run
   Compiling tock-registers v0.10.0
   Compiling tock-registers-modified-dep v0.1.0 (/home/leons/hack/deps/tock-registers-modified-dep)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.39s
     Running `target/debug/tock-registers-modified-dep`

thread 'main' panicked at /home/leons/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tock-registers-0.10.0/src/registers.rs:187:2:
HACKED!!!
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
leons@iron ~/h/d/tock-registers-modified-dep (main) [101]> rm -rf ~/.cargo/

If, however, your lockfile contains a hash that does not correspond to the fetched crate contents, or if it points to a crate version that does not exit, Cargo will (as it should) refuse with an error and leave the lockfile untouched:

leons@iron ~/h/d/tock-registers-modified-dep (main)> git show
commit 79accd06abd2c5408809b080e6e5c6b194c0377f (HEAD -> main)
Author: Leon Schuermann <[email protected]>
Date:   Wed Sep 17 13:31:48 2025 -0400

    Change lockfile hash

diff --git a/Cargo.lock b/Cargo.lock
index 33af7b3..2e9ceb8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6,7 +6,7 @@ version = 4
 name = "tock-registers"
 version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0293f99756f16ff352cc78c99673766a305bdb5ed7652e78df649e9967c885a"
+checksum = "b0293f99756f16ff352cc78c99673766a305bdb5ed7652e78df649e9967c885b"
 
 [[package]]
 name = "tock-registers-modified-dep"
leons@iron ~/h/d/tock-registers-modified-dep (main)> cargo run
error: checksum for `tock-registers v0.10.0` changed between lock files

this could be indicative of a few possible errors:

    * the lock file is corrupt
    * a replacement source in use (e.g., a mirror) returned a different checksum
    * the source itself may be corrupt in one way or another

unable to verify that `tock-registers v0.10.0` is the same as when the lockfile was generated

leons@iron ~/h/d/tock-registers-modified-dep (main) [101]> git status
On branch main
nothing to commit, working tree clean
leons@iron ~/h/d/tock-registers-modified-dep (main)> git show
commit f3f4874c97f98c7c40176bcb7fe05e82ae28ed60 (HEAD -> main)
Author: Leon Schuermann <[email protected]>
Date:   Wed Sep 17 13:33:28 2025 -0400

    Point to unreleased crate version

diff --git a/Cargo.lock b/Cargo.lock
index 2e9ceb8..12a89d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,7 +4,7 @@ version = 4
 
 [[package]]
 name = "tock-registers"
-version = "0.10.0"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b0293f99756f16ff352cc78c99673766a305bdb5ed7652e78df649e9967c885b"
 
leons@iron ~/h/d/tock-registers-modified-dep (main)> cargo run
    Updating crates.io index
error: failed to select a version for the requirement `tock-registers = "^0.10.0"` (locked to 0.10.1)
candidate versions found which didn't match: 0.10.0, 0.9.0, 0.8.1, ...
location searched: crates.io index
required by package `tock-registers-modified-dep v0.1.0 (/home/leons/hack/deps/tock-registers-modified-dep)`
leons@iron ~/h/d/tock-registers-modified-dep (main) [101]> git status
On branch main
nothing to commit, working tree clean

As said previously, Cargo should only ever update the lockfile when the manifest files change (Cargo.toml files), or when running a command designed to explicitly update lockfiles (like cargo update or cargo generate-lockfile). The above is at least empirical evidence showing that Cargo implements this behavior in practice.


If it turns out that I'm wrong, and that if you have a cargo.lock file, and whether you include --locked or not has exactly identical behavior forever, then great! That is a win.

So, from the above, the answer to this question is no, but I don't think it's as bad as this thread may suggest it to be: the lockfile will be updated when changes to the manifests (Cargo.toml files) require it to be updated---the point behind --locked is to avoid this---or when explicitly requesting it be updated.

For building a released version of Tock including a lockfile, neither of these things will happen. So downstream consumers can have solid assurance they're build a blessed release, with transitively locked dependencies.

@bradjc
Copy link
Contributor

bradjc commented Sep 23, 2025

So, from the above, the answer to this question is no, but I don't think it's as bad as this thread may suggest it to be: the lockfile will be updated when changes to the manifests (Cargo.toml files) require it to be updated---the point behind --locked is to avoid this---or when explicitly requesting it be updated.

How strong is "require"? So if all dependencies specify the same version of a crate, then exactly that version will be used? But if one Cargo.toml specifies a newer semver compliant version, they will all use the new version (and cargo will update the lockfile)? If so, that's at least something.

However, because I couldn't let this go untested, here's the result. As suspected, Cargo doesn't do any integrity checks of any locally stored crates. Once anybody has write access to your ~/.cargo folder it's game over

But, why couldn't cargo check that the dependencies match the lockfile? We have long known that cargo doesn't have the same opinion of dependencies as Tock does, so I'm not surprised about what cargo does here.

If your basic assumption starts with "an attacker gets your crate corrupted", it's a pretty easy jump to "your entire toolchain is compromised".

Yeah, maybe. "No external dependencies" is a defense mechanism against putting trust in a variety of places outside our control. I'm not saying we have to be paranoid. But, this thread has convinced me that cargo is not focused on a very strict notion of dependency control, and isn't signaling that this is a priority going forward. If cargo had a policy (like we do with ExtDep.md) on their stance, and it was aligned with ours, and over time we could reasonably expect cargo to improve and add features to make this more robust, then I would say that is somewhere we could offload some of our trust. But that's not what I'm seeing.

I still think we can add the external dependency for tock-registers (see here). We can add --locked to our makefiles. We can note that everyone should build with cargo build --locked. But, I don't think anything has fundamentally changed with rust dependency management that would lead me to say we should relax our "no dependencies" policy.

@lschuermann
Copy link
Member Author

Superseded by #4616 and #4605.

@lschuermann lschuermann closed this Oct 2, 2025
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.

5 participants