Skip to content

libcontainer: LockOSThread around Umask calls#3563

Closed
haircommander wants to merge 1 commit into
opencontainers:mainfrom
haircommander:umask-runtime-lock
Closed

libcontainer: LockOSThread around Umask calls#3563
haircommander wants to merge 1 commit into
opencontainers:mainfrom
haircommander:umask-runtime-lock

Conversation

@haircommander
Copy link
Copy Markdown
Contributor

umask operates on a thread basis, and we risk leaking the umask to other routines
if the go runtime interrupts.

Signed-off-by: Peter Hunt~ pehunt@redhat.com

Comment thread libcontainer/utils/utils_unix.go Outdated
AkihiroSuda
AkihiroSuda previously approved these changes Sep 3, 2022
Copy link
Copy Markdown
Contributor

@corhere corhere left a comment

Choose a reason for hiding this comment

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

I just so happen to be working on a very similar change over in moby/moby. Locking the goroutine to a thread is not sufficient to prevent umask changes from leaking into other threads.

Comment thread libcontainer/utils/utils_unix.go Outdated
Comment on lines +77 to +81
runtime.LockOSThread()
defer runtime.UnlockOSThread()
oldMask := unix.Umask(mask)
defer unix.Umask(oldMask)
return f()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The umask is part of the "filesystem information" whose sharing is governed by the CLONE_FS flag. The Go runtime clones new threads with the CLONE_FS flag set so any call to unix.Umask() immediately affects all goroutines in the process. runtime.LockOSThread() alone is not enough to prevent the change from leaking to other threads.

Here's what would work:

Suggested change
runtime.LockOSThread()
defer runtime.UnlockOSThread()
oldMask := unix.Umask(mask)
defer unix.Umask(oldMask)
return f()
errC := make(chan error)
go func() {
runtime.LockOSThread()
if err := unix.Unshare(unix.CLONE_FS); err != nil {
errC <- err
runtime.UnlockOSThread()
return
}
unix.Umask(mask)
errC <- f()
}()
return <-errC

unix.Unshare(CLONE_FS) is an irreversible operation; the calling thread will never again have its umask, cwd or root directory shared with other threads. The thread will no longer be indistinguishable from other threads which run goroutines. Returning from the goroutine while locked to the thread signals to the Go runtime to terminate the thread (or wedge, if it's the main thread) for situations exactly like this one. (This article has some good information as well.) Just be aware that terminating threads can have some undesirable interactions with (syscall.SysProcAttr).Pdeathsig which need to be accounted for. It's straightforward enough to deal with once you know how what's happening and why, e.g.: moby/moby#44215.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thanks for the information! updated as suggested

corhere
corhere previously approved these changes Oct 7, 2022
umask operates on a thread basis, and is part "filesystem information".
Without both locking the OS thread, and calling `unshare(CLONE_FS)`, the umask will leak
to other threads in runc.

Signed-off-by: Peter Hunt~ <pehunt@redhat.com>
Copy link
Copy Markdown
Member

@cyphar cyphar left a comment

Choose a reason for hiding this comment

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

Sorry, I wrote a review some time ago but never submitted it. In short, I think that we should just remove all usage of unix.Umask in our codebase and just explicitly set the modes of everything we create. The CLONE_NEWFS issue is enough of a problem that we should just avoid over-complicating it (burning a thread each time we have to call umask(2) is kind of insane) and just do the simple thing.

kolyshkin added a commit to kolyshkin/runc that referenced this pull request Sep 27, 2023
Umask is problematic for Go programs as it affects other goroutines
(see [1] for more details).

Instead of using it, let's just prop up with Chmod.

Note this patch misses the MkdirAll call in createDeviceNode. Since the
runtime spec does not say anything about creating intermediary
directories for device nodes, let's assume that doing it via mkdir with
the current umask set is sufficient (if not, we have to reimplement
MkdirAll from scratch, with added call to os.Chmod).

[1] opencontainers#3563 (comment)

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
@haircommander
Copy link
Copy Markdown
Contributor Author

closing in favor of #4039

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants