Skip to content

os: possible to remove or change internal finalizer on a Root #71617

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

Closed
mknyszek opened this issue Feb 7, 2025 · 7 comments
Closed

os: possible to remove or change internal finalizer on a Root #71617

mknyszek opened this issue Feb 7, 2025 · 7 comments
Labels
NeedsFix The path to resolution is known, but the work has not been done. release-blocker
Milestone

Comments

@mknyszek
Copy link
Contributor

mknyszek commented Feb 7, 2025

The implementation of os.Root sets its finalizer on the Root itself (it targets the internal root, but that struct is just embedded in Root) meaning that callers can call SetFinalizer on a Root to mutate the finalizer. Other APIs in std, like File, explicitly make this impossible by setting the finalizer on an internal object (a File contains a pointer to a file, which actually has the finalizer).

The main issue with this behavior is that it exposes an internal implementation detail, and may prevent switching from finalizers to cleanups in the future (since the cleanup is not removable unless you have the handle to it). The fix is straightforward: the internal root should be stored by reference in Root.

CC @neild

@mknyszek mknyszek added NeedsFix The path to resolution is known, but the work has not been done. release-blocker labels Feb 7, 2025
@mknyszek mknyszek added this to the Go1.24 milestone Feb 7, 2025
@neild
Copy link
Contributor

neild commented Feb 7, 2025

Agreed that this is a bug.

I have no opinion on whether we should get it into 1.24, or just backport into 1.24.1. It's pretty obscure.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/647876 mentions this issue: os: hide SetFinalizer from users of Root

@dmitshur
Copy link
Member

Since this release-blocker is in the Go 1.24 milestone, re-opening to track cherry-picking to release-branch.go1.24.

@dmitshur dmitshur reopened this Feb 10, 2025
@gopherbot
Copy link
Contributor

Change https://go.dev/cl/648135 mentions this issue: [release-branch.go1.24] os: hide SetFinalizer from users of Root

gopherbot pushed a commit that referenced this issue Feb 10, 2025
Currently Root embeds a root and calls SetFinalizer on &r.root. This
sets the finalizer on the outer root, which is visible to users of
os.Root, and thus they can mutate the finalizer attached to it.

This change modifies Root to not embed its inner root, but rather to
refer to it by pointer. This allows us to set the finalizer on this
independent inner object, preventing users of os.Root from changing the
finalizer. This follows the same pattern as os.File's finalizer.

Fixes #71617.

Change-Id: Ibd199bab1b3c877d5e12ef380fd4647b4e10221f
Reviewed-on: https://go-review.googlesource.com/c/go/+/647876
Auto-Submit: Michael Knyszek <[email protected]>
Reviewed-by: Damien Neil <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
(cherry picked from commit a704d39)
Reviewed-on: https://go-review.googlesource.com/c/go/+/648135
TryBot-Bypass: Michael Knyszek <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
@dmitshur
Copy link
Member

Closed by merging CL 648135 (commit 6d399e9) to release-branch.go1.24.

@andig
Copy link
Contributor

andig commented Feb 10, 2025

It feels like a newbie question, but why does making root a pointer hide SetFinalizer? root ist not embedded? Because SetFinalizer works on the address (i.e. allocation reference) of the object? Please ignore me if too OT, will move question to golang-nuts.

@neild
Copy link
Contributor

neild commented Feb 10, 2025

A finalizer is associated with an allocation, and there's at most one finalizer per allocation. CL 648135 changes a Root into two allocations: type Root struct { *root }, and sets the finalizer into inner one (Root.root). This means that the outer *Root object doesn't have a finalizer associated with it, and if a user creates one they won't interfere with the inner *root's finalizer.

(runtime.AddCleanup, coming in Go 1.24, (see #67535) avoids this and many other problems with finalizers. We aren't using AddCleanup on Root yet because Root and AddCleanup were developed in parallel and I didn't think of it. There's work in progress to switch all internal usage of finalizers to AddCleanup in 1.25, see #70907.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsFix The path to resolution is known, but the work has not been done. release-blocker
Projects
None yet
Development

No branches or pull requests

5 participants