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

Only prevent VTs to be mounted inside privileged systemd containers #17055

Merged
merged 1 commit into from
Jan 12, 2023

Conversation

mupuf
Copy link
Contributor

@mupuf mupuf commented Jan 10, 2023

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?

Privileged containers running systemd will once again mount /dev/tty* devices into the container, ignoring virtual terminals (/dev/tty[0-9]+) (https://github.com/containers/podman/issues/16925).

@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
isMatched, _ := regexp.MatchString("/dev/tty[\\d]+", deviceName)
isMatched, _ := regexp.MatchString(`/dev/tty[\d]+`, deviceName)

With backquotes \ does not need to be escaped

@@ -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)
Copy link
Contributor

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().

Copy link
Contributor Author

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!

Copy link
Contributor

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.

@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
isMatched, _ := regexp.MatchString("/dev/tty[\\d]+", deviceName)
isMatched, _ := regexp.MatchString("^/dev/tty[\\d]*$", deviceName)
  1. /dev/tty exists on my system, it probably also must be matched?
  2. Use begin and end markers to prevent that it also matches weird unexpected dev paths like /dev/tty100ABC, better be safe :-)

Copy link
Contributor Author

@mupuf mupuf Jan 10, 2023

Choose a reason for hiding this comment

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

  1. /dev/tty is not a virtual terminal though, so we definitely want to keep it, so we shouldn't match it.
  2. 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)

Copy link
Contributor

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

@@ -70,6 +71,11 @@ func FindDeviceNodes() (map[string]string, error) {
return nodes, nil
}

func IsVirtualTerminalDevice(deviceName string) bool {
Copy link
Contributor

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.

@mupuf
Copy link
Contributor Author

mupuf commented Jan 10, 2023

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!

@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jan 10, 2023
@github-actions github-actions bot added the kind/api-change Change to remote API; merits scrutiny label Jan 10, 2023
@openshift-merge-robot openshift-merge-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jan 10, 2023
@github-actions github-actions bot removed the kind/api-change Change to remote API; merits scrutiny label Jan 10, 2023
pkg/util/utils_linux.go Outdated Show resolved Hide resolved
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]>
@mheon
Copy link
Member

mheon commented Jan 11, 2023

Code LGTM. I think this technically matches paths like /dev/tty0abcd, but I'm not aware of any devices that actually use a path layout like that, so we ought to be fine.

@fho
Copy link
Contributor

fho commented Jan 11, 2023

@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)
			}
		})
	}
}

@rhatdan
Copy link
Member

rhatdan commented Jan 11, 2023

LGTM

@rhatdan
Copy link
Member

rhatdan commented Jan 11, 2023

/approve

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Jan 11, 2023

[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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jan 11, 2023
@TomSweeneyRedHat
Copy link
Member

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.

@edsantiago
Copy link
Member

podman-machine flake is #16789, which is hitting us multiple times a day. Restarted.

@rhatdan
Copy link
Member

rhatdan commented Jan 12, 2023

/lgtm

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Jan 12, 2023
@openshift-merge-robot openshift-merge-robot merged commit a7ba63d into containers:main Jan 12, 2023
@mupuf
Copy link
Contributor Author

mupuf commented Jan 12, 2023

@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)
			}
		})
	}
}

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.

@mheon
Copy link
Member

mheon commented Jan 12, 2023

Sure, new PR would be fine.

@fho
Copy link
Contributor

fho commented Jan 13, 2023

@mheon true,
I suggest the following instead:
[...]
Since this code already landed, mind sending it as a new MR? I can offer some testing and whatever my review is worth.

I created a PR:
#17104

@fho
Copy link
Contributor

fho commented Jan 16, 2023

We forgot a call.

We also have to use isVirtualConsoleDevice here:

if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") {
).
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 :-)

@mupuf
Copy link
Contributor Author

mupuf commented Jan 16, 2023

We forgot a call.

We also have to use isVirtualConsoleDevice here:

if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") {

).
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!

@mupuf
Copy link
Contributor Author

mupuf commented Jan 16, 2023

@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?

@fho
Copy link
Contributor

fho commented Jan 16, 2023

@mupuf
It looks to me like it exist to prevent the same systemd issue and only was introduced separately.
In my local version I adapted that part and tested it in non-root mode with a ttyACM device manually and it worked.

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.

fho added a commit to fho/podman that referenced this pull request Jan 20, 2023
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]>
rhatdan pushed a commit to rhatdan/podman that referenced this pull request Jan 28, 2023
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]>
openshift-cherrypick-robot pushed a commit to openshift-cherrypick-robot/podman that referenced this pull request Jan 30, 2023
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]>
@github-actions github-actions bot added the locked - please file new issue/PR Assist humans wanting to comment on an old issue or PR with locked comments. label Sep 15, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. locked - please file new issue/PR Assist humans wanting to comment on an old issue or PR with locked comments. release-note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

not possible to share tty (e.g. usb-dongle) devices with privileged containers
7 participants