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

Do not assume that ZST-typed symbols refer to unique objects #3134

Merged
merged 11 commits into from
Apr 30, 2024

Conversation

tautschnig
Copy link
Member

@tautschnig tautschnig commented Apr 8, 2024

The Rust specification does not guarantee that ZST-typed symbols are backed by unique objects, and rustc appears to make use of this as can be demonstrated for both locals and statics. For parameters no such example has been found, but as there remains a lack of a guarantee we err on the safe side.

Resolves: #3129

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

It seems that rustc does not necessarily create unique objects for
ZST-typed symbols, unless they are parameters.

Resolves: model-checking#3129
@tautschnig tautschnig requested a review from a team as a code owner April 8, 2024 15:01
@github-actions github-actions bot added the Z-BenchCI Tag a PR to run benchmark CI label Apr 8, 2024
Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Can you please confirm that is really the case and paste a reference here?

@tautschnig
Copy link
Member Author

Can you please confirm that is really the case and paste a reference here?

Not exactly a "reference", but still a very extensive discussion including pointers to the actual implementation (yes, this is all about the implementation of the Rust compiler, nothing here is specified in the documentation): https://stackoverflow.com/questions/58529934/why-do-zero-sized-types-cause-real-allocations-in-some-cases

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Thanks @tautschnig for working on this, but I really think we should ask the Rust team (maybe create an issue in https://github.com/rust-lang/unsafe-code-guidelines) about what guarantees we should expect when it comes to comparing two raw pointers to ZST objects.

I always find that searching for ZST related issues and guarantees is a rabbit hole. Just from my latest search:

kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs Outdated Show resolved Hide resolved
kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs Outdated Show resolved Hide resolved
kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs Outdated Show resolved Hide resolved
@celinval
Copy link
Contributor

celinval commented Apr 9, 2024

Ok, I gave this a thought, and I think the correct solution would be to add a UB check if users try to compare two ZST pointers.

My reasoning is:

  1. We shouldn't try to model UB. Any code following an UB is incorrect.
  2. Accessing ZST pointers is a well defined operation. There's no need to change that.

@tautschnig
Copy link
Member Author

tautschnig commented Apr 9, 2024

Why should we add a UB check? Comparing any two (raw) pointers is explicitly permitted in Rust as far as I can tell?!

Edit:

Accessing ZST pointers is a well defined operation. There's no need to change that.

  1. I am not sure whether accessing ZSTs already is a well defined operation. To me, Tracking Issue for allowing zero-sized memory accesses and offsets rust-lang/rust#117945 suggests that this is still in the making.
  2. This PR does not actually change anything about the well-definedness of using ZST pointers in Kani.

@tautschnig
Copy link
Member Author

Can you please confirm that is really the case and paste a reference here?

Not exactly a "reference", but still a very extensive discussion including pointers to the actual implementation (yes, this is all about the implementation of the Rust compiler, nothing here is specified in the documentation): https://stackoverflow.com/questions/58529934/why-do-zero-sized-types-cause-real-allocations-in-some-cases

A further note: the implementation pointed to in the above SO-post (rust-lang/rust#63635) continued to exist until rust-lang/rust#111768. It does seem, however, that at that point from_const_alloc wasn't actually used anymore (outside the SSA back-end). So I don't know how this really works in the compiler.

@celinval
Copy link
Contributor

celinval commented Apr 9, 2024

I created rust-lang/unsafe-code-guidelines#503 to hopefully get some answers to these questions.

@tautschnig
Copy link
Member Author

I created rust-lang/unsafe-code-guidelines#503 to hopefully get some answers to these questions.

Thank you!!! Given their answers, my interpretation is that we should not do the parameter-specific piece and will instead have to modify the Closure/zst_param.rs test, because the non-equality asserted in there is not actually guaranteed.

@tautschnig
Copy link
Member Author

I created rust-lang/unsafe-code-guidelines#503 to hopefully get some answers to these questions.

Thank you!!! Given their answers, my interpretation is that we should not do the parameter-specific piece and will instead have to modify the Closure/zst_param.rs test, because the non-equality asserted in there is not actually guaranteed.

I have now updated the implementation to the above effect.

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

The current fix will work for the exact issue at hand, but the following will still always evaluate to false, even though it could be true:

let a1 = &() as *const _;
let a2 = &() as *const _;
let a3 = &() as *const _;
let a4 = &() as *const _;
cover!(a1 == a2 && a3 == a4 && a1 != a3);

So to make this really sound, you need to set all addresses to be any non-null + aligned address value.

tests/expected/zst/main.rs Show resolved Hide resolved
tests/expected/zst/main.rs Show resolved Hide resolved
kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs Outdated Show resolved Hide resolved
kani-compiler/src/codegen_cprover_gotoc/codegen/place.rs Outdated Show resolved Hide resolved
@tautschnig
Copy link
Member Author

[...]

So to make this really sound, you need to set all addresses to be any non-null + aligned address value.

Hmm, yes, but still those also need to be addresses such that dereferencing pointers with such addresses also works. The following might work:

  1. Create a global scope array of infinite size.
  2. Each ZST "object" is just a non-deterministic index into to this array.

A problem, however, will arise when lifetimes of ZST objects are required to be honoured. Then we'd have to reference-count each entry in this array, which seems like unreasonable overhead.

@celinval
Copy link
Contributor

Hmm, yes, but still those also need to be addresses such that dereferencing pointers with such addresses also works. The following might work:

De-referencing a ZST dangling pointer is considered valid in Rust since it is a no-op. So as long as the pointer is aligned, there should be no problem. I believe Kani also treats that as a no-op, i.e., it does not emit a de-reference instruction. I don't think we will be able to track ZST lifetimes until we have a different mechanism to handle provenance.

@celinval
Copy link
Contributor

Which means that Kani cannot detect invalid ZST access due to deallocation. This is a limitation that should be documented, if you don't mind.

@tautschnig
Copy link
Member Author

tautschnig commented Apr 17, 2024

Summary of a discussion that @celinval and I had:

  1. Given that Kani does not emit dereference instructions for ZSTs we should actually be fine when we just produce a nondet value for an address-of.
  2. The constraints on that (numeric) nondet value should just be that it must not be 0 (to not produce a null pointer) and that casting to a pointer must result in an aligned address.
  3. We can read the alignment from the type.
  4. Taking these together, we will produce a statement expression that first creates a numeric nondet value, then assumes it is not zero, then assumes that the value modulo alignment equals zero, and finally casts to the desired pointer type.
  5. We should confirm that CBMC simplifies modulo-1 (we believe the alignment will typically be 1) to zero. Edit: simplify_exprt::simplify_mod takes care of this.
  6. if metadata.dangling(marker) as *const _ == data_ptr {
    crate::assert(sz == 0, "Dangling pointer is only valid for zero-sized access")
    } else {
    should be adjusted so as to just assert when sz != 0.

@tautschnig tautschnig assigned tautschnig and unassigned celinval Apr 17, 2024
@celinval
Copy link
Contributor

For 5, we should change the if to be:

if sz == 0 {
    true
} else {
    // invoke read ok
}

@celinval
Copy link
Contributor

@tautschnig any updates?

@tautschnig
Copy link
Member Author

@tautschnig any updates?

I have now pushed my update, but I am yet to understand why mem::tests::test_dangling_char and mem::tests::test_dangling_slice are now failing (or, rather, how to debug those tests as they are now reported to be running forever).

@celinval
Copy link
Contributor

@tautschnig any updates?

I have now pushed my update, but I am yet to understand why mem::tests::test_dangling_char and mem::tests::test_dangling_slice are now failing (or, rather, how to debug those tests as they are now reported to be running forever).

Those tests used to exercise a path that no longer exists. With the new implementation, non-zst dangling pointers will reach the Kani intrinsic, which in regular tests is a loop.

I.e., we are no longer able to test those two cases with concrete tests, and they should be removed.

@tautschnig
Copy link
Member Author

[...]

Those tests used to exercise a path that no longer exists. With the new implementation, non-zst dangling pointers will reach the Kani intrinsic, which in regular tests is a loop.

I.e., we are no longer able to test those two cases with concrete tests, and they should be removed.

Thanks, now done.

@tautschnig tautschnig assigned celinval and unassigned tautschnig Apr 26, 2024
Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

Can you please take a look at the s2n-quic test that regressed?

Everything else looks good!

@tautschnig
Copy link
Member Author

Can you please take a look at the s2n-quic test that regressed?

I have now locally re-run this test multiple times both with current main and this branch/PR: the time spent to run cargo kani --harness inet::checksum::tests::differential (after running once to have the crate built) is always in the range of 178 to 183 seconds. Given this test has very high memory usage (see #3030), it is not too surprising that we are seeing high fluctuations on GitHub runners (where the memory usage is very close to the maximum available memory). But I don't think this PR actually has a meaningful impact on that fluctuation.

@tautschnig tautschnig merged commit 9e34364 into model-checking:main Apr 30, 2024
22 of 23 checks passed
@tautschnig tautschnig deleted the fix-3129 branch April 30, 2024 20:35
karkhaz added a commit that referenced this pull request May 7, 2024
For reference, here is the auto-generated changelog

## What's Changed
* Upgrade toolchain to 2024-04-18 and improve toolchain workflow by
@celinval in #3149
* Automatic toolchain upgrade to nightly-2024-04-19 by @github-actions
in #3150
* Stabilize cover statement and update contracts RFC by @celinval in
#3091
* Automatic toolchain upgrade to nightly-2024-04-20 by @github-actions
in #3154
* Bump tests/perf/s2n-quic from `2d5e891` to `5f88e54` by @dependabot in
#3140
* Automatic cargo update to 2024-04-22 by @github-actions in
#3157
* Automatic toolchain upgrade to nightly-2024-04-21 by @github-actions
in #3158
* Bump tests/perf/s2n-quic from `5f88e54` to `9730578` by @dependabot in
#3159
* Fix cargo audit error by @jaisnan in
#3160
* Fix cbmc-update CI job by @tautschnig in
#3156
* Automatic cargo update to 2024-04-29 by @github-actions in
#3165
* Bump tests/perf/s2n-quic from `9730578` to `1436af7` by @dependabot in
#3166
* Do not assume that ZST-typed symbols refer to unique objects by
@tautschnig in #3134
* Fix copyright check for `expected` tests by @adpaco-aws in
#3170
* Remove kani::Arbitrary from the modifies contract instrumentation by
@feliperodri in #3169
* Automatic cargo update to 2024-05-06 by @github-actions in
#3172
* Bump tests/perf/s2n-quic from `1436af7` to `6dd41e0` by @dependabot in
#3174
* Avoid unnecessary uses of Location::none() by @tautschnig in
#3173


**Full Changelog**:
kani-0.50.0...kani-0.51.0

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 and MIT licenses.

---------

Co-authored-by: Adrian Palacios <[email protected]>
zpzigi754 pushed a commit to zpzigi754/kani that referenced this pull request May 8, 2024
…hecking#3134)

The Rust specification does not guarantee that ZST-typed symbols are
backed by unique objects, and `rustc` appears to make use of this as can
be demonstrated for both locals and statics. For parameters no such
example has been found, but as there remains a lack of a guarantee we
err on the safe side.

Resolves: model-checking#3129
qinheping pushed a commit to qinheping/kani that referenced this pull request May 9, 2024
…hecking#3134)

The Rust specification does not guarantee that ZST-typed symbols are
backed by unique objects, and `rustc` appears to make use of this as can
be demonstrated for both locals and statics. For parameters no such
example has been found, but as there remains a lack of a guarantee we
err on the safe side.

Resolves: model-checking#3129
qinheping pushed a commit to qinheping/kani that referenced this pull request May 9, 2024
For reference, here is the auto-generated changelog

## What's Changed
* Upgrade toolchain to 2024-04-18 and improve toolchain workflow by
@celinval in model-checking#3149
* Automatic toolchain upgrade to nightly-2024-04-19 by @github-actions
in model-checking#3150
* Stabilize cover statement and update contracts RFC by @celinval in
model-checking#3091
* Automatic toolchain upgrade to nightly-2024-04-20 by @github-actions
in model-checking#3154
* Bump tests/perf/s2n-quic from `2d5e891` to `5f88e54` by @dependabot in
model-checking#3140
* Automatic cargo update to 2024-04-22 by @github-actions in
model-checking#3157
* Automatic toolchain upgrade to nightly-2024-04-21 by @github-actions
in model-checking#3158
* Bump tests/perf/s2n-quic from `5f88e54` to `9730578` by @dependabot in
model-checking#3159
* Fix cargo audit error by @jaisnan in
model-checking#3160
* Fix cbmc-update CI job by @tautschnig in
model-checking#3156
* Automatic cargo update to 2024-04-29 by @github-actions in
model-checking#3165
* Bump tests/perf/s2n-quic from `9730578` to `1436af7` by @dependabot in
model-checking#3166
* Do not assume that ZST-typed symbols refer to unique objects by
@tautschnig in model-checking#3134
* Fix copyright check for `expected` tests by @adpaco-aws in
model-checking#3170
* Remove kani::Arbitrary from the modifies contract instrumentation by
@feliperodri in model-checking#3169
* Automatic cargo update to 2024-05-06 by @github-actions in
model-checking#3172
* Bump tests/perf/s2n-quic from `1436af7` to `6dd41e0` by @dependabot in
model-checking#3174
* Avoid unnecessary uses of Location::none() by @tautschnig in
model-checking#3173


**Full Changelog**:
model-checking/kani@kani-0.50.0...kani-0.51.0

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 and MIT licenses.

---------

Co-authored-by: Adrian Palacios <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Z-BenchCI Tag a PR to run benchmark CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reasoning about ZST addresses
2 participants