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

Alias settings_client internals to see it in a memory dump. #26654

Merged
merged 3 commits into from
Nov 22, 2024
Merged

Conversation

goodov
Copy link
Member

@goodov goodov commented Nov 20, 2024

Look into the client to see what the actual type was when we got the list. I have a slight suspicion that we get the non-overridden version of WorkerContentSettingsClient, because any other code path should return a non-empty ShieldsSettingsPtr object.

Related brave/brave-browser#41889

Submitter Checklist:

  • I confirm that no security/privacy review is needed and no other type of reviews are needed, or that I have requested them
  • There is a ticket for my issue
  • Used Github auto-closing keywords in the PR description above
  • Wrote a good PR/commit description
  • Squashed any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
  • Added appropriate labels (QA/Yes or QA/No; release-notes/include or release-notes/exclude; OS/...) to the associated issue
  • Checked the PR locally:
    • npm run test -- brave_browser_tests, npm run test -- brave_unit_tests wiki
    • npm run presubmit wiki, npm run gn_check, npm run tslint
  • Ran git rebase master (if needed)

Reviewer Checklist:

  • A security review is not needed, or a link to one is included in the PR description
  • New files have MPL-2.0 license header
  • Adequate test coverage exists to prevent regressions
  • Major classes, functions and non-trivial code blocks are well-commented
  • Changes in component dependencies are properly reflected in gn
  • Code follows the style guide
  • Test plan is specified in PR before merging

After-merge Checklist:

Test Plan:

@goodov goodov mentioned this pull request Nov 20, 2024
24 tasks
@goodov goodov changed the title Alias settings_client internals to see in a memory dump. Alias settings_client internals to see it in a memory dump. Nov 20, 2024
@goodov goodov marked this pull request as ready for review November 20, 2024 08:33
@goodov goodov requested review from a team as code owners November 20, 2024 08:33
@goodov
Copy link
Member Author

goodov commented Nov 20, 2024

@bridiver @iefremov @atuchin-m inviting some C++ experts to validate the approach I'm using here to have the additional data in a memory dump.

I'm pretty sure the undefined behavior @cdesouza-chromium mentions is not applicable here, because no access to the copied memory is performed, but maybe I'm missing something.

@cdesouza-chromium
Copy link
Collaborator

cdesouza-chromium commented Nov 20, 2024

@bridiver @iefremov @atuchin-m inviting some C++ experts to validate the approach I'm using here to have the additional data in a memory dump.

I'm pretty sure the undefined behavior @cdesouza-chromium mentions is not applicable here, because no access to the copied memory is performed, but maybe I'm missing something.

For some context let me add my comment from the original PR here as well:

After scouring everywhere for this, I finally found some concrete answers about the object model and how StackObjectCopy may be violating it. Unfortunately we can't have this type, and I think that makes sense why chromium doesn't offer one. The issue here is the C++ Object Model. We are invoking UB both when we reinterpret_cast the byte array, and when we copy the bytes to the array too[1].

Objects of trivially-copyable types that are not potentially-overlapping subobjects are the only C++ objects that may be safely copied with std::memcpy or serialized to/from binary files with std::ofstream::write() / std::ifstream::read().

Of course, if the objects were trivially copyable, we could just copy them to the stack, and we wouldn't need this StackObjectCopy in the first place. However, memcpying non-trivially-copiable objects ends up subverting the language's memory model, and invoking Undefined Behaviour.

This wouldn't invoke undefined behaviour if T was constrained to std::is_trivially_copyable[2], but I suspect StackObjectCopy wouldn't be of use with that constraint.

[1] https://en.cppreference.com/w/cpp/types/is_trivially_copyable
[2] https://eel.is/c++draft/basic.types.general#2

Now for some added context. The access itself is not not the problem, although access would also be undefined behaviour, but rather the copy itself is undefined, and that makes the reinterpret cast undefined too. Undefined behaviour is banned as a foregone conclusion by the guidelines. It is what the standard calls illegal. It is really annoying that C++ makes this undefined, because of the rigid object lifetime model.

What could happen then if we let this one in? It is unknown, this could work for now, and then due to code changes around, the compiler could get entirely confused and start emitting wrong code. This is the problem with undefined behaviour, it makes the program invalid, and lets the compiler makes wrong assumptions about things.

I particularly am not fond of being a nuisance here about undefined behaviour, but this is life with C++ and I hate being the annoying voice to bring that up.

Copy link
Collaborator

@cdesouza-chromium cdesouza-chromium left a comment

Choose a reason for hiding this comment

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

Okay, so I have had some further discussions about this and questions involving crashpad. I think the issue is fairly complex when we are considering crash dumps, etc, because it is already too low level, and @goodov does have a point about that, and the fact the value never gets read.

Is it undefined to copy the data? Maybe not, because no read is taking place. The thing that could potentially be UB is the reinterpret_cast at this point, but again, we don't read that pointer either. Will compilers mess this one? Meh, doesn't seem it would ever happen.

I don't want to be pedantic about undefined behaviour and end up blocking people's work for something that only theoretically could break, and that ins't even certain to be UB.

So LGTM.

I have a few recommendations though:

  • It is not clear that serialising some virtual abstract class will produce useful debugging data, because the serialisation is not guaranteed to be well formed. We are slicing the type, and even sizeof(T) is unreliable at this point, so it is better to double check if these debugging helpers are indeed helping.
  • The better approach here would be for types like blink::WebContentSettingsClient to support string serialisation for debugging. Plenty of types do that[1], and there's a case to be made StackObjectCopy is the wrong tool for the job and we should be getting debug strings in some of these types. This would involve submitting work to upstream though, which may not be convenient.

[1] https://source.chromium.org/chromium/chromium/src/+/main:base/values.h;l=1125

Copy link
Collaborator

@bridiver bridiver left a comment

Choose a reason for hiding this comment

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

approving for renderer reviewers, but this seems like something we could upstream?

@iefremov
Copy link
Contributor

I'm not sure about UB, but from university years i recall that you can't really assume that copying sizeof(T) bytes from an arbitrary T* ptr is correct.

if there is a large enough hierarchy of (virtual) multiple inheritance that T belongs to, your ptr may point to the middle of a larger object, and it is not guaranteed that members of some of T parents are to the right of ptr

private:
constexpr static size_t kSize = sizeof(T);

alignas(T) uint8_t buffer_[kSize];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would that be enough to extract and store just vtable pointer?

//
// Can't use `byte_span_from_ref` because `T` might be an abstract class.
auto original_object_memory = UNSAFE_BUFFERS(
make_span(reinterpret_cast<const uint8_t*>(original), kSize));
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think you need make_span there. It can only be span I suspect.

//
// Can't use `byte_span_from_ref` because `T` might be an abstract class.
auto original_object_memory = UNSAFE_BUFFERS(
make_span(reinterpret_cast<const uint8_t*>(original), kSize));
Copy link
Contributor

Choose a reason for hiding this comment

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

reported by reviewdog 🐶
[semgrep] Using reinterpret_cast against some data types may lead to undefined behaviour. In general, when needing to do these conversions, check how Chromium upstream does them. Most of the times a reinterpret_cast is wrong and there's no guarantee the compiler will generate the code that you thought it would.

Source: https://github.com/brave/security-action/blob/main/assets/semgrep_rules/client/reinterpret_cast.yaml


Cc @stoletheminerals @thypon @cdesouza-chromium

@goodov goodov enabled auto-merge (squash) November 21, 2024 12:12
@goodov goodov merged commit c726422 into master Nov 22, 2024
19 checks passed
@goodov goodov deleted the issues/41889 branch November 22, 2024 07:17
@github-actions github-actions bot added this to the 1.75.x - Nightly milestone Nov 22, 2024
@brave-builds
Copy link
Collaborator

Released in v1.75.44

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.

9 participants