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

clean cached rlimit nofile in go runtime #4237

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ls-ggg
Copy link

@ls-ggg ls-ggg commented Mar 29, 2024

fix:#4195
In a relatively new runc version, in other words a new go version (in my environment the go version is go1.20.13),
the runtime will cache the value of rlimit nofile, which will cause the container's rlimit nofile configuration to not take effect. This problem must occur when the customer configures the CAP_SYS_RESOURCE permission.

refer to:[commit](// golang/go@f5eef58#diff-ec665e9789f8cf5cd1828ad7fa9f0ff4ebc1f5b5dd0fc82a296da5c07da7ece6)

go runtime caches rlimit-nofile during startup phase
image

and before exec,runtime will reset rlimit-nofile.
image

@AkihiroSuda AkihiroSuda added the backport/todo/1.1 A PR in main branch which needs to be backported to release-1.1 label Apr 1, 2024
@kolyshkin
Copy link
Contributor

@ls-ggg can you do a rebase instead of a merge? I.e. do not add merge commits.

Also, can we have a test case?

@ls-ggg ls-ggg force-pushed the fix-rlimit-nofile-wrong branch 4 times, most recently from b275b24 to 83eebdf Compare April 2, 2024 05:09
@ls-ggg
Copy link
Author

ls-ggg commented Apr 2, 2024

@ls-ggg can you do a rebase instead of a merge? I.e. do not add merge commits.

Also, can we have a test case?

@kolyshkin I've done the rebase and added test cases.

@kolyshkin
Copy link
Contributor

OK, I took a deeper look and in fact this is already fixed in the main branch via #3813, which brings in this change:
https://go-review.googlesource.com/c/sys/+/476695

So, to my best understanding, this PR is not needed.

For release-1.1 branch to fix this, we need something like #3813.

@kolyshkin
Copy link
Contributor

For release-1.1 branch to fix this, we need something like #3813.

Opened #4239; PTAL

@kolyshkin
Copy link
Contributor

OK, I took a deeper look and in fact this is already fixed in the main branch via #3813, which brings in this change: https://go-review.googlesource.com/c/sys/+/476695

So, to my best understanding, this PR is not needed.

Closing (not needed in main branch, opened #4239 for release-1.1 branch).

@kolyshkin kolyshkin closed this Apr 2, 2024
@kolyshkin
Copy link
Contributor

Interestingly, runc-1.1.12 (as used in the original bug report, #4195) is compiled with Go 1.20.13:

$ ./runc-1.1.12 --version
runc version 1.1.12
commit: v1.1.12-0-g51d5e946
spec: 1.0.2-dev
go: go1.20.13
libseccomp: 2.5.4

and I was able to reproduce the issue with this version.

Found out that Go 1.20 has CL 476097 backported (as CL 478659), so Go 1.20 is also affected (since Go1.20.4).

@kolyshkin
Copy link
Contributor

Found out that Go 1.20 has CL 476097 backported (as CL 478659), so Go 1.20 is also affected (since Go 1.20.4).

As well as Go 1.19.+ (CL 478660).

@kolyshkin kolyshkin reopened this Apr 3, 2024
Copy link
Contributor

@kolyshkin kolyshkin left a comment

Choose a reason for hiding this comment

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

Maybe we should drop the test case entirely, as it takes up to 100000 execs (in my setup) to catch the issue, and we can't afford it in CI.

tests/integration/resources.bats Outdated Show resolved Hide resolved
tests/integration/resources.bats Outdated Show resolved Hide resolved
tests/integration/resources.bats Outdated Show resolved Hide resolved
libcontainer/utils/utils.go Outdated Show resolved Hide resolved
libcontainer/utils/utils.go Outdated Show resolved Hide resolved
@kolyshkin
Copy link
Contributor

For some reason, my alternative approach (#4239) is not working.

Testing this PR, I got this:

$ RUNC=~/git/runc/runc-4237-go1.21; i=0; $RUNC --version && while [ $i -lt 100000 ]; do LIM=$(sudo $RUNC exec bionic sh -c "ulimit -n"); if [ $LIM -ne 65536 ]; then echo "WHOOPSIE (iter $i) got numfile $LIM"; break; fi; ((i%100==0)) && printf "[%s] %10d\n" "$(date)" "$i"; let i++; done
runc version 1.1.0+dev
commit: v1.1.0-994-g4641f17e-dirty
spec: 1.2.0
go: go1.21.7
libseccomp: 2.5.1
[Tue 02 Apr 2024 10:10:36 PM PDT]          0
[Tue 02 Apr 2024 10:10:38 PM PDT]        100
...
[Tue 02 Apr 2024 10:23:23 PM PDT]      42300
WHOOPSIE (iter 42333) got numfile 1048576

So there is still some race going on :(

@kolyshkin
Copy link
Contributor

WHOOPSIE (iter 42333) got numfile 1048576

So there is still some race going on :(

Note the number is now as go runtime sets it for itself.

Copy link
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.

With these changes, I would be okay with taking something like this as a hotfix but IMHO this is clearly a Go stdlib bug. We use prlimit(2) to set another process's rlimits (to avoid permission issues) and it's not reasonable for Go to assume that this won't happen.

libcontainer/utils/utils.go Outdated Show resolved Hide resolved
libcontainer/utils/utils.go Outdated Show resolved Hide resolved
@kolyshkin
Copy link
Contributor

Most importantly, this fix does not work :-\

@ls-ggg
Copy link
Author

ls-ggg commented Apr 12, 2024

OK, I took a deeper look and in fact this is already fixed in the main branch via #3813, which brings in this change: https://go-review.googlesource.com/c/sys/+/476695

So, to my best understanding, this PR is not needed.

For release-1.1 branch to fix this, we need something like #3813.

@kolyshkin
The #3813 cannot fix it. the correct way is to clear the cache in the runtime.

@ls-ggg ls-ggg force-pushed the fix-rlimit-nofile-wrong branch 2 times, most recently from d2c509f to bbb035c Compare April 12, 2024 16:18
@ls-ggg ls-ggg requested review from kolyshkin and cyphar April 12, 2024 16:20
Comment on lines +56 to +62
rlimit := syscall.Rlimit{}
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
return err
}
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
return err
}
Copy link
Member

Choose a reason for hiding this comment

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

As I suggested in the earlier review, for correctness reasons, I think

Suggested change
rlimit := syscall.Rlimit{}
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
return err
}
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
return err
}
for _, rlimit := range l.config.Rlimits {
if rlimit.Type == unix.RLIMIT_NOFILE {
if err := unix.Setrlimit(rlimit.Type, &unix.Rlimit{
Cur: rlimit.Soft,
Max: rlimit.Hard,
}); err != nil {
return fmt.Errorf("failed to re-apply nofile rlimit: %w", err)
}
}
}

makes more sense.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, the proposed implementation is better

Copy link
Author

Choose a reason for hiding this comment

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

But running the unit test failed and I encountered an error:
=== RUN TestInitJoinNetworkAndUser
exec_test.go:1543: unexpected error: unable to start container process: error during container init: failed to re-apply nofile rlimit: operation not permitte

My code just clears the cache in go runtime and has no meaning to the operating system.

Copy link
Member

Choose a reason for hiding this comment

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

Just curious about 2 things:

  1. I think the code provided by you and cyphar have the same effect, but I don't know why cyphar's code will be fail, could you please give some explainations? If yes, it will be appreciated.
  2. If I understand right, there is no similar bug for runc run, this bug is only in runc exec? If yes, in setns_init_linux, I think we can use procReady msg to tell the parent process do Setrlimit, like what is doing in standard_init_linux.

Copy link
Member

Choose a reason for hiding this comment

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

2. like what is doing in standard_init_linux.

case procReady:
seenProcReady = true
// set rlimits, this has to be done here because we lose permissions
// to raise the limits once we enter a user-namespace
if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil {
return fmt.Errorf("error setting rlimits for ready process: %w", err)
}

As reported in issue opencontainers#4195, the new version of go runtime will
cache rlimit-nofile. before executing exec, the rlimit-nofile
of the process will be updated with the cache. in runc, this will
cause the rlimit-nofile set by the parent process for the container
to become invalid. this can be solved by clearing the cache.

Signed-off-by: ls-ggg <335814617@qq.com>
@ls-ggg
Copy link
Author

ls-ggg commented Apr 28, 2024

Can this mr be approved?

@kolyshkin
Copy link
Contributor

Can this mr be approved?

As I said a month ago, this fix does not work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport/todo/1.1 A PR in main branch which needs to be backported to release-1.1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants