-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Only prevent VTs to be mounted inside privileged systemd containers #17055
Conversation
69f6821
to
f5bb22b
Compare
pkg/util/utils_linux.go
Outdated
@@ -70,6 +71,11 @@ func FindDeviceNodes() (map[string]string, error) { | |||
return nodes, nil | |||
} | |||
|
|||
func IsVirtualTerminalDevice(deviceName string) bool { | |||
isMatched, _ := regexp.MatchString("/dev/tty[\\d]+", deviceName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isMatched, _ := regexp.MatchString("/dev/tty[\\d]+", deviceName) | |
isMatched, _ := regexp.MatchString(`/dev/tty[\d]+`, deviceName) |
With backquotes \
does not need to be escaped
pkg/util/utils_linux.go
Outdated
@@ -70,6 +71,11 @@ func FindDeviceNodes() (map[string]string, error) { | |||
return nodes, nil | |||
} | |||
|
|||
func IsVirtualTerminalDevice(deviceName string) bool { | |||
isMatched, _ := regexp.MatchString("/dev/tty[\\d]+", deviceName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about making this a precompiled regex on package lvl (regexp.MustCompile()
)?
Then the regexp would only be compiled once instead of on every invocation of IsVirtualTerminalDevice()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good idea! I was operating under the assumption that it would get cached anyway, but I can't seem to find any reference of that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After you pointed out that /dev/tty
must not match, path.Match("/dev/tty[0-9]*"
became an alternative. It should be faster then using a regex and a global variable would also not be needed.
pkg/util/utils_linux.go
Outdated
@@ -70,6 +71,11 @@ func FindDeviceNodes() (map[string]string, error) { | |||
return nodes, nil | |||
} | |||
|
|||
func IsVirtualTerminalDevice(deviceName string) bool { | |||
isMatched, _ := regexp.MatchString("/dev/tty[\\d]+", deviceName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isMatched, _ := regexp.MatchString("/dev/tty[\\d]+", deviceName) | |
isMatched, _ := regexp.MatchString("^/dev/tty[\\d]*$", deviceName) |
/dev/tty
exists on my system, it probably also must be matched?- Use begin and end markers to prevent that it also matches weird unexpected dev paths like
/dev/tty100ABC
, better be safe :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/dev/tty
is not a virtual terminal though, so we definitely want to keep it, so we shouldn't match it.- I agree with the idea of using the begin/end markers, but we should be very precise as to what a virtual terminal would be, and was isn't. Virtual terminals are /dev/ttyN+, nothing else.
So, here would be the final result:
regexp.MatchString(`^/dev/tty[\d]+$`, deviceName)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, reference for others: https://man7.org/linux/man-pages/man4/tty.4.html
pkg/util/utils_linux.go
Outdated
@@ -70,6 +71,11 @@ func FindDeviceNodes() (map[string]string, error) { | |||
return nodes, nil | |||
} | |||
|
|||
func IsVirtualTerminalDevice(deviceName string) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend to make it a private function because there is no usecase for it outside of the package currently.
f5bb22b
to
b856b64
Compare
Thanks for the review, @fho! That was great feedback, as I am a noob in golang! I ended up removing the function altogether, since I could pre-compile the regex, and the function would end up being a single line! |
b856b64
to
fa80132
Compare
fa80132
to
3107c85
Compare
While mounting virtual console devices in a systemd container is a recipe for disaster (I experienced it first hand), mounting serial console devices, modems, and others should still be done by default for privileged systemd-based containers. v2, addressing the review from @fho: - use backticks in the regular expression to remove backslashes - pre-compile the regex at the package level - drop IsVirtualTerminalDevice (not needed for a one-liner) v3, addressing the review from @fho and @rhatdan: - re-introduce a private function for matching the device names - use path.Match rather than a regex not to slow down startup time Closes containers#16925. Fixes: 5a2405a ("Don't mount /dev/tty* inside privileged...") Signed-off-by: Martin Roukala (né Peres) <[email protected]>
3107c85
to
f4c81b0
Compare
Code LGTM. I think this technically matches paths like |
@mheon true, I suggest the following instead: func isVirtualConsoleDevice(path string) bool {
suffix := strings.TrimPrefix(path, "/dev/tty")
if suffix == path || suffix == "" {
return false
}
// bitsize could be lowered to max. number of tty devices
_, err := strconv.ParseUint(suffix, 10, 64)
return err == nil
} and a unit-test: func TestIsVirtualConsoleDevice(t *testing.T) {
testcases := []struct {
expectedResult bool
path string
}{
{
expectedResult: true,
path: "/dev/tty10",
},
{
expectedResult: false,
path: "/dev/tty",
},
{
expectedResult: false,
path: "/dev/ttyUSB0",
},
{
expectedResult: false,
path: "/dev/tty0abcd",
},
}
for _, tc := range testcases {
t.Run(tc.path, func(t *testing.T) {
result := isVirtualConsoleDevice(tc.path)
if result != tc.expectedResult {
t.Errorf("isVirtualConsoleDevice returned %t, expected %t", result, tc.expectedResult)
}
})
}
} |
LGTM |
/approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mupuf, rhatdan The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Changes LGTM, but some of the tests aren't happy. I don't think the test failure is related, but I don't know for sure. |
podman-machine flake is #16789, which is hitting us multiple times a day. Restarted. |
/lgtm |
This looks like an excellent idea and would protect ourselves against /dev/tty1abc! Since this code already landed, mind sending it as a new MR? I can offer some testing and whatever my review is worth. |
Sure, new PR would be fine. |
We forgot a call. We also have to use isVirtualConsoleDevice here: podman/pkg/util/utils_linux.go Line 110 in cdcd2ed
Otherwise it won't work with rootless container. I can create a PR when I find some time in my off hours. I also would not mind if somebody is faster :-) |
Good find! I was initially developing the patch for my project which only cared about the rootful path, and I completely forgot about this path... Silly me... I think I'll spin up a new series to get this fixed, since you already have an active MR of your own! Thanks again! |
@fho: Weeeeellll, I am currently investigating why there is this filtering in place. Here are my findings:
And now I can't figure out where this code originally comes from... so I can't figure out why it is there... and thus I don't feel confident changing it to unify the codepath. What are your thoughts? |
@mupuf I think we should create a PR to also adapt that. If there is an issue with it, it could also be caught during the codereview or by the tests. |
As @mheon pointed out in PR containers#17055[^1], isVirtualConsoleDevice() does not only matches VT device paths but also devices named like /dev/tty0abcd. This causes that non VT device paths named /dev/tty[0-9]+[A-Za-z]+ are not mounted into privileged container and systemd containers accidentally. This is an unlikely issue because the Linux kernel does not use device paths like that. To make it failproof and prevent issues in unlikely scenarios, change isVirtualConsoleDevice() to exactly match ^/dev/tty[0-9]+$ paths. Because it is not possible to match this path exactly with Glob syntax, the path is now checked with strings.TrimPrefix() and strconv.ParseUint(). ParseUint uses a bitsize of 16, this is sufficient because the max number of TTY devices is 512 in Linux 6.1.5. (Checked via 'git grep -e '#define' --and -e 'TTY_MINORS'). The commit also adds a unit-test for isVirtualConsoleDevice(). Fixes: f4c81b0 ("Only prevent VTs to be mounted inside...") [^1]: containers#17055 (comment) Signed-off-by: Fabian Holler <[email protected]>
As @mheon pointed out in PR containers#17055[^1], isVirtualConsoleDevice() does not only matches VT device paths but also devices named like /dev/tty0abcd. This causes that non VT device paths named /dev/tty[0-9]+[A-Za-z]+ are not mounted into privileged container and systemd containers accidentally. This is an unlikely issue because the Linux kernel does not use device paths like that. To make it failproof and prevent issues in unlikely scenarios, change isVirtualConsoleDevice() to exactly match ^/dev/tty[0-9]+$ paths. Because it is not possible to match this path exactly with Glob syntax, the path is now checked with strings.TrimPrefix() and strconv.ParseUint(). ParseUint uses a bitsize of 16, this is sufficient because the max number of TTY devices is 512 in Linux 6.1.5. (Checked via 'git grep -e '#define' --and -e 'TTY_MINORS'). The commit also adds a unit-test for isVirtualConsoleDevice(). Fixes: f4c81b0 ("Only prevent VTs to be mounted inside...") [^1]: containers#17055 (comment) Signed-off-by: Fabian Holler <[email protected]> Signed-off-by: Daniel J Walsh <[email protected]>
As @mheon pointed out in PR containers#17055[^1], isVirtualConsoleDevice() does not only matches VT device paths but also devices named like /dev/tty0abcd. This causes that non VT device paths named /dev/tty[0-9]+[A-Za-z]+ are not mounted into privileged container and systemd containers accidentally. This is an unlikely issue because the Linux kernel does not use device paths like that. To make it failproof and prevent issues in unlikely scenarios, change isVirtualConsoleDevice() to exactly match ^/dev/tty[0-9]+$ paths. Because it is not possible to match this path exactly with Glob syntax, the path is now checked with strings.TrimPrefix() and strconv.ParseUint(). ParseUint uses a bitsize of 16, this is sufficient because the max number of TTY devices is 512 in Linux 6.1.5. (Checked via 'git grep -e '#define' --and -e 'TTY_MINORS'). The commit also adds a unit-test for isVirtualConsoleDevice(). Fixes: f4c81b0 ("Only prevent VTs to be mounted inside...") [^1]: containers#17055 (comment) Signed-off-by: Fabian Holler <[email protected]> Signed-off-by: Daniel J Walsh <[email protected]>
While mounting virtual terminal devices in a systemd container is a
recipe for disaster (I experienced it first hand), mounting serial
console devices, modems, and others should still be done by default
for privileged systemd-based containers.
Closes #16925.
Fixes: 5a2405a ("Don't mount /dev/tty* inside privileged...")
Signed-off-by: Martin Roukala (né Peres) [email protected]
Does this PR introduce a user-facing change?