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

Fix memory safety of Array.copy_to #4173

Closed
wants to merge 49 commits into from
Closed

Conversation

stefandd
Copy link
Contributor

@stefandd stefandd commented Aug 6, 2022

This fixes #4172 and #4174 by ensuring that only available elements get copied and that they are copied without any gap into the target array.

The reason for the crash in #4172 was that _ptr can be NULL when the Array is instanced as empty Array, e.g. let x: Array[I32] = [], whereas #4174 is more generally related to a request to copy more elements than there are or using a destination array index that creates a "gap" in the destination error.

stefandd and others added 10 commits July 27, 2022 15:35
This attempts to fix issues ponylang#4130 and ponylang#4153.

The issue was when a private type in another package was used as a default value in a method call.

Fix to ponylang#4130
Since it has been decided to treat this as a bug instead of a missing error, this PR implements the fix suggested by @ergl, namely using `lookup_try()` instead of lookup() in call.c's `check_partial_function_call()` since it allows to permit private types.

Fix to ponylang#4153
This is also a simply fix to lookup.c that prevents a potential segfault by a dereferencing of `opt` (`typecheck_t* t = &opt->check;`) *before* `opt != NULL` was checked. As pointed out by @SeanTAllen, opt should not be NULL to begin with when lookup_nominal is called, and instead, an assert should be added and the NULL checks in that function removed.
This attempts to fix ponylang#4130

This crash stems from the use of a private type as defined in another package when it was used as a default value in a method call. Since it has been decided to treat this as a bug instead of a missing error, this PR implements the fix suggested by @ergl, namely using `lookup_try()` instead of lookup() in call.c's `check_partial_function_call()` since the former can be configured to permit private types.

Fix to ponylang#4153
This is a simply change to `lookup_nominal()` in lookup.c that prevents a potential segfault by a dereferencing of `opt` (`typecheck_t* t = &opt->check;`) *before* `opt != NULL` was checked. As pointed out by @SeanTAllen, opt should not be NULL to begin with when `lookup_nominal()` is called, and instead, an assert should be added and the NULL checks in that function removed.

With this PR, the two examples below that crashed the compiler now both compile:

Original example:
```pony
// inside the "useful" package

primitive _PrivateDefault

actor Useful[A: Any val]
  fun tag config(value: (A | _PrivateDefault) = _PrivateDefault): Useful[A] => this

// inside "main"

use "useful"

primitive This
primitive That

type Stuff is (This | That)

actor Main
  new create(env: Env) =>
    let u = Useful[Stuff].config()
```

Minimal example:
```pony
// In the "lib" pacakge

primitive _Private

primitive Public
  fun apply[T](v: (T | _Private) = _Private): None => None

// In main
use lib = "lib"

actor Main
  new create(env: Env) =>
    let p = lib.Public.apply[U8]()
    env.out.print(p.string())
```
Adding release notes
@ponylang-main ponylang-main added the discuss during sync Should be discussed during an upcoming sync label Aug 6, 2022
@SeanTAllen
Copy link
Member

The release notes appear to be for a different change. For a test, please add a unit test to builtin_tests not a runner test.

@SeanTAllen SeanTAllen added the changelog - fixed Automatically add "Fixed" CHANGELOG entry on merge label Aug 6, 2022
@stefandd
Copy link
Contributor Author

stefandd commented Aug 6, 2022

The release notes appear to be for a different change. For a test, please add a unit test to builtin_tests not a runner test.

The release notes are still the one from another PR I submitted for this release -- my branch was still based on release. I will submit release notes momentarily.

Since this PR fixes a segfault, I assumed the test was supposed to show that's gone. However, I am not clear on how to test for the absence of a segfault outside a runner test. Could you give me some hints?

Add release notes for ponylang#4173
@SeanTAllen
Copy link
Member

A test that calling copy_to with an uninitialized array leaves the destination array unchanged is a sufficient test.

@SeanTAllen
Copy link
Member

SeanTAllen commented Aug 6, 2022

@stefandd have you read the how to contribute doc? It has good pointer like "use a different branch for each PR"

I'd suggest that you should at some point, start over with a new fork and never do anything with the main branch except track the upstream.

@stefandd
Copy link
Contributor Author

stefandd commented Aug 6, 2022

A test that calling copy_to with an uninitialized array leaves the destination array unchanged is a sufficient test.

So If I add such a test, you'd like me to remove the segfault-is-gone test?

@SeanTAllen
Copy link
Member

A test that calling copy_to with an uninitialized array leaves the destination array unchanged is a sufficient test.

So If I add such a test, you'd like me to remove the segfault-is-gone test?

Yes please

@stefandd
Copy link
Contributor Author

stefandd commented Aug 6, 2022

A test that calling copy_to with an uninitialized array leaves the destination array unchanged is a sufficient test.

So If I add such a test, you'd like me to remove the segfault-is-gone test?

Yes please

Will do. But for my understanding: why would such a test be sufficient? What if that test segfaulted? Is that being caught? I just built a builtin_test executable without the patch and it simply aborts. Is this tested in the CI?

@SeanTAllen
Copy link
Member

It doesn't matter if it segfaults. It matters that it does the right thing for a given test case.

That it segfaults currently isn't of much interest. It matters that there's no test coverage to show it does the correct thing.

If someone were to reintroduce the bug, the unit tests for the standard library would crash and we'd know something was wrong.

There's plenty of ways that code could be wrong. So it's the "is it right" test that is important.

There's compiler bugs that we need runner tests for and compiler features we need runner tests for. This isn't like that though. A good old unit test gives us all the coverage we need.

@stefandd stefandd requested a review from SeanTAllen August 7, 2022 06:02
@SeanTAllen
Copy link
Member

Small nit. This isn't a bug with empty arrays. It's with uninitialized ones. If you initialized an array but let it empty, it wouldn't have segfaulted.

@SeanTAllen
Copy link
Member

This still has the extra release notes from a previous PR.

Copy link
Member

@SeanTAllen SeanTAllen left a comment

Choose a reason for hiding this comment

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

  • Please change "empty array" to "uninitialized array" in the various places including PR title that you use to the terminology
  • Please remove the extra release notes file

@stefandd
Copy link
Contributor Author

stefandd commented Aug 7, 2022

@stefandd if we moved forward with this PR for address #4174 then release notes etc would need to be updated as well. But again, I think it is reasonable to table this for now until #4174 is discussed.

Well this fixes both issues, but if you feel it is not ready to be included it is fine. If you change your mind, I'd like to ask you to adjust the release notes here to reflect both issues since you manage to be more concise.

@SeanTAllen
Copy link
Member

This is lacking test coverage to be accepted for 4174. The release notes should be updated, and it still has an extra release notes file that needs to be removed.

If we moved forward with this fix rather than making partial, all of the above would need to be fixed and the PR title would need to be updated.

@stefandd
Copy link
Contributor Author

stefandd commented Aug 7, 2022

This is lacking test coverage to be accepted for 4174. The release notes should be updated, and it still has an extra release notes file that needs to be removed.

If we moved forward with this fix rather than making partial, all of the above would need to be fixed and the PR title would need to be updated.

Could I win you as a contributor for this? I attempted (did it work?) to make you a collaborator on the branch I worked from. I also changed the PR title - Ok so?

I do not know how to remove leftover previous committs while the PR is open -- however I believed them to be benign since those committs were already merged with main. However, I deleted the offending .md file.

I added several tests to cover #4174 - let me know if you feel things are missing. I would suggest to edit the release-notes once concensus has been reached to move forward.

Further small test improvement
Ouch -- USize overflow when `_size - src_index` < 0!
@SeanTAllen SeanTAllen changed the title Fix potential segfaults in Array.copy_to in case of insufficient source elements Fix memory safety of Array.copy_to Aug 7, 2022
Comment on lines +1792 to +1793
src2.copy_to(dest2, 0, 0, 10) // copies the sole available element
h.assert_array_eq[U8]([1; 1; 2; 3; 4; 5; 6], dest2)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this should be the desired behavior. I think if you give an out of range index it should be an error. This is a good point of conversation for the next sync call.

Copy link
Member

Choose a reason for hiding this comment

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

As mentioned in #4174 I think that what @stefandd has here is the desired behavior ("copies the sole available element"), in that it is consistent with the precedents set by other methods.

Comment on lines +1786 to +1793
let dest2: Array[U8] = [0; 1; 2; 3; 4; 5; 6]
src2.copy_to(dest2, 0, 0, 10) // try to copy 10 non-existant elements
h.assert_array_eq[U8]([0; 1; 2; 3; 4; 5; 6], dest2)
src2.push(1) // re-add single element [1]
src2.copy_to(dest2, 11, 0, 1) // try to copy from too high start index
h.assert_array_eq[U8]([0; 1; 2; 3; 4; 5; 6], dest2)
src2.copy_to(dest2, 0, 0, 10) // copies the sole available element
h.assert_array_eq[U8]([1; 1; 2; 3; 4; 5; 6], dest2)
Copy link
Member

Choose a reason for hiding this comment

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

for things like this, please put comments on their own line above the relevant code.

Comment on lines +1787 to +1793
src2.copy_to(dest2, 0, 0, 10) // try to copy 10 non-existant elements
h.assert_array_eq[U8]([0; 1; 2; 3; 4; 5; 6], dest2)
src2.push(1) // re-add single element [1]
src2.copy_to(dest2, 11, 0, 1) // try to copy from too high start index
h.assert_array_eq[U8]([0; 1; 2; 3; 4; 5; 6], dest2)
src2.copy_to(dest2, 0, 0, 10) // copies the sole available element
h.assert_array_eq[U8]([1; 1; 2; 3; 4; 5; 6], dest2)
Copy link
Member

Choose a reason for hiding this comment

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

each of these individual tests should have a newline between them and be prefaced by a comment explaining what is being tested.

@SeanTAllen
Copy link
Member

Some happy path tests I would want to see:

  • copy into destination so that it doesn't all fit within the destination where you want to copy something in that is greater in size than destination space
  • copy into the front of destination so copied amount fits within existing destination
  • copy onto the end of a destination so that copied amount fits within the existing destination's space
  • copy into the middle of a destination

Comment on lines +1786 to +1793
let dest2: Array[U8] = [0; 1; 2; 3; 4; 5; 6]
src2.copy_to(dest2, 0, 0, 10) // try to copy 10 non-existant elements
h.assert_array_eq[U8]([0; 1; 2; 3; 4; 5; 6], dest2)
src2.push(1) // re-add single element [1]
src2.copy_to(dest2, 11, 0, 1) // try to copy from too high start index
h.assert_array_eq[U8]([0; 1; 2; 3; 4; 5; 6], dest2)
src2.copy_to(dest2, 0, 0, 10) // copies the sole available element
h.assert_array_eq[U8]([1; 1; 2; 3; 4; 5; 6], dest2)
Copy link
Member

Choose a reason for hiding this comment

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

tests that rely on previous state are easy to break. for each test, please use new variables and initialize each test on its own. don't reuse data structures across the tests.

@stefandd stefandd closed this Aug 26, 2022
@ponylang-main ponylang-main removed the discuss during sync Should be discussed during an upcoming sync label Aug 26, 2022
SeanTAllen added a commit that referenced this pull request Feb 4, 2024
This is based heavily on stefandd's work from
#4173

Closes #4174
SeanTAllen added a commit that referenced this pull request Feb 4, 2024
This is based heavily on stefandd's work from
#4173

Closes #4174
SeanTAllen added a commit that referenced this pull request Feb 4, 2024
This is based heavily on stefandd's work from
#4173

Closes #4174
SeanTAllen added a commit that referenced this pull request Feb 4, 2024
This is based heavily on stefandd's work from
#4173

Closes #4174
SeanTAllen added a commit that referenced this pull request Feb 6, 2024
This is based heavily on stefandd's work from
#4173

Closes #4174
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog - fixed Automatically add "Fixed" CHANGELOG entry on merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Segfault in StdLib Array.copy_to()
4 participants