Bug 968674 (CVE-2016-2779)

Summary: VUL-0: CVE-2016-2779: util-linux: runuser tty hijacking via TIOCSTI ioctl
Product: [Novell Products] SUSE Security Incidents Reporter: Alexander Bergmann <abergmann>
Component: IncidentsAssignee: Stanislav Brabec <sbrabec>
Status: RESOLVED FIXED QA Contact: Security Team bot <security-team>
Severity: Normal    
Priority: P3 - Medium CC: abergmann, jkosina, jslaby, meissner, mkubecek, mmachova, pth, rfrohl, sbrabec, smash_bz, vcizek
Version: unspecified   
Target Milestone: ---   
Hardware: Other   
OS: Other   
URL: https://smash.suse.de/issue/162294/
Whiteboard: CVSSv2:SUSE:CVE-2016-2779:6.2:(AV:L/AC:H/Au:N/C:C/I:C/A:C) CVSSv3.1:SUSE:CVE-2016-2779:8.6:(AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H) maint:planned:update
Found By: Security Response Team Services Priority:
Business Priority: Blocker: ---
Marketing QA Status: --- IT Deployment: ---
Bug Depends on:    
Bug Blocks: 968375, 968675, 1018892    
Attachments: test_tiocsti.c TIOCSTI terminal injection exploit

Description Alexander Bergmann 2016-02-29 12:53:34 UTC
http://seclists.org/oss-sec/2016/q1/448

When executing a program via "runuser -u nonpriv program" the nonpriv session can escape to the parent session by using the TIOCSTI ioctl to push characters into the terminal's input buffer

    https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=815922

CVE-2016-2779 was assigned to this issue.


References:
http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-2779
http://seclists.org/oss-sec/2016/q1/448
Comment 1 Stanislav Brabec 2016-02-29 16:24:47 UTC
I don't see any fix in the util-linux git yet.
Comment 2 Stanislav Brabec 2016-03-01 22:08:45 UTC
No upstream response yet. Karel Žák seems to be offline.

There is an easy reproducer.

However it is not clean to me, what is the correct fix.

The proposed setsid() causes change of behavior.

The current implementation calls setsid() only sometimes:

 if (request_same_session || !command || !pw->pw_uid)
    same_session = 1;
...
  if (!same_session)
    setsid ();

i. e. not in the reported case, because of !command.

setsid() prevents TIOCSTI attack described in the report (easy to reproduce), but it has side effects: It disconnects the task from job control.

With setsid(), ^Z cannot be used for sending the application to background any more (easy to reproduce by calling setsid() unconditionally in the same place).

Additionally, https://bugzilla.redhat.com/show_bug.cgi?id=173008 says, that even it does not handle all possible attacks, because attacker can still write to the terminal.

Here is an example. With a modified runuser that always calls setsid(), it is still possible to steal input characters after returning back to the session.

==== steal.sh ====
#!/bin/sh
(
sleep 3
exec 0>&1
echo "Hallo" >/dev/stdout
cat >/tmp/nobody-savefile
) &
==================

~/util-linux # ./runuser -u sbrabec ./stral.sh
~/util-linux # Hallo
Comment 3 Stanislav Brabec 2016-03-02 15:22:16 UTC
Debian developers suggest to fix the TIOCSTI issue in kernel: restrict use of this ioctl():

https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=815922#40

Regarding the unwanted reading from the console, I can imagine, that the application will kill all tasks that have the console open, once it exits.

Or, if such syscall exists, refuse them to access.

Adding kernel developers to Cc:.
Comment 4 Michal Marek 2016-03-02 15:56:06 UTC
(In reply to Stanislav Brabec from comment #3)
> Debian developers suggest to fix the TIOCSTI issue in kernel: restrict use
> of this ioctl():
> 
> https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=815922#40

Adding Jiří Slabý to CC.
Comment 5 Stanislav Brabec 2016-03-02 16:12:05 UTC
Regarding the access to the console after returning:

Should we consider it as a security issue? Or should we suppose, that everybody who wants to prevent it, has to redirect stdin, stdout and stderr?

It is hard to prevent it otherwise without killing the process that has stdin, stdout or stderr opened. There were several attempts to implement revoke() syscall, but nothing similar exists in the kernel yet.
Comment 6 Stanislav Brabec 2016-03-02 17:28:23 UTC
Looking at su in util-linux and comparing it with https://bugzilla.redhat.com/attachment.cgi?id=120976&action=diff, the signal logic is the same for a long time, but setsid() logic uses the same code in comment 2.


SLE11 uses su from coreutils.

Adding maintainer of coreutils to check for SLE11.
Comment 7 Stanislav Brabec 2016-03-02 18:20:34 UTC
And sudo is affected as well:

util-linux # sudo -u nobody ./test_tiocsti
id -u -n
util-linux # id -u -n
root

The root executed "id -u -n" was injected to the terminal by a non-privileged ./test_tiocsti.

According to https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=815922#5, sudo was fixed by use_pty. I guess they mean CD_USE_PTY.


And here is a result for util-linux su:

If -s is not used, it is not affected:

util-linux # su nobody -c ./test_tiocsti
util-linux # su - nobody -c $PWD/test_tiocsti
util-linux # 

No injected command.

But for -s, the injection happens as well:

util-linux # su - nobody -s $PWD/test_tiocsti
id -u -n
util-linux # id -u -n
root
util-linux # 

For su command without -s, the first part of the code in the comment 2 is not executed, so the second part falls back to the default, which is not same session.
Comment 8 Stanislav Brabec 2016-03-02 18:28:05 UTC
Created attachment 667514 [details]
test_tiocsti.c TIOCSTI terminal injection exploit

This exploit injects "id -u -n\n" to the terminal.

What can happen:

Nothing, no exploit: pty is not accessible, sedsid() disconnected the task from pty, TIOCSTI failed.

The command is injected to the unprivileged environment pty, and you see e. g. "nobody": This is acceptable.

The command is injected to the caller (privileged) pty, and you see "root" (or caller uid name): This is not acceptable.
Comment 9 Stanislav Brabec 2016-03-02 18:42:59 UTC
util-linux su is also affected if --session-command is used:

util-linux # su - nobody --session-command $PWD/test_tiocsti
id -u -n
util-linux # id -u -n
root
util-linux #
Comment 10 Stanislav Brabec 2016-03-03 14:03:06 UTC
I just opened a discussion on the upstream util-linux list:
http://marc.info/?t=145694748900001&r=1&w=2

Discussion there proposes to fix it in the kernel:

Disallow the use of TIOCSTI to unprivileged users unless the caller has CAP_SYS_ADMIN.

It will fix util-linux issues, but not chroot.
Comment 11 Michal Marek 2016-03-04 15:11:47 UTC
For reference, the grsecurity approach can be seen here: https://gitweb.gentoo.org/proj/hardened-patchset.git/commit/?id=d47ea9080b76a7445e8a36545c539b2a62c31faa

++int gr_handle_tiocsti(struct tty_struct *tty)
++{
++#ifdef CONFIG_GRKERNSEC_HARDEN_TTY
++	if (grsec_enable_harden_tty && (current->signal->tty == tty) &&
++	    !capable(CAP_SYS_ADMIN)) {
++		gr_log_noargs(GR_DONT_AUDIT, GR_TIOCSTI_MSG);
++		return 1;
++	}
++#endif
++	return 0;
++}

It looks like it disables TIOCSTI for non-root unconditionally?
Comment 12 Marcus Meissner 2016-03-04 15:17:59 UTC
we could supply a sysctl for the check, defaulting to off
Comment 14 Michal Kubeček 2016-03-04 15:51:05 UTC
FYI: a similar problem was handled earlier for su utility, see bsc#697897
(see also a follow-up issue starting with comment 28).
Comment 15 Stanislav Brabec 2016-03-04 16:00:23 UTC
We had a talk about this bug, and we found, that there is no quick and 100% safe fix.

Here are possibilities:

1) Quick kernel fix disabling TIOCSTI ioctl() for non-root, if the PID of the terminal owner is not equal to PID of the calling process, eventually use capabilities for the same.

Pros:
+ Fix in one place.
+ Fix all possible future abuses.

Cons:
- Many utilities are potentially affected and need testing.
- Some custom code could be affected. (I can imagine for example bar code reader running with a dedicated UID, and pushing bar code to the terminal. Such code will break for sure.)


2) Per utility fix using setsid().

Pros:
+ Prevents the exploit without uncertain side effects.

Cons:
- Each affected utility needs fix.
- Loss of job control will affect working style of many people.


Conclusion:

We need a different solution:

3) Introduce new terminal ioctl() or flag in the kernel. This flag will block TIOCSTI (and possibly other dangerous actions). It will allow to implement something like setsid(), but without side effects of job control loss.

Pros:
+ No unwanted side effects at all.

Cons:
- Each affected utility needs fix.


We think, that only 3 will be safe and have no side effects.


Note:

Fixing character stealing described in comment 2 is not covered by any of these solutions. This could be possible safely only with a new syscall revoke(), which was not yet accepted to the kernel.
Comment 16 Stanislav Brabec 2016-03-04 16:05:49 UTC
Michal Kubeček (comment 14): As I look into the bug, it also changed su behavior, and introduced su --command=foo x so --session-command=foo schism.

In case su, losing the job control is more acceptable than for sudo.


Michal pointed to a simple use case that will break:

osc build
press ^Z, send build to background
type next command

It will be no more possible with fix type 2) in sudo.
Comment 17 Stanislav Brabec 2016-03-04 16:17:11 UTC
ust for curiosity, I just ran grep for TIOCSTI ioctl() over all openSUSE sources. I got about 60 matches.

I analyzed use of some cases:

util-linux: used in agetty in wait_for_term_input()
kbd: contrib utility sti equal to tiocsti utility.
irda: Used by handle_scancode() to emulate input.
tcsh: Used in ed mode and in pushback().
emacs: Used in stuff_char() (putting char to be read from terminal)
...

It seems that TIOCSTI is used for:
- Read character, and if it does not match, put it back.
- Wait for character, than put it back for processing.
- Implementing a simple line editing.


If irda daemon will run with a different UID, then it is a candidate for breakage by 1).
Comment 19 Jiri Slaby 2016-03-16 08:28:00 UTC
It's perfectly fine to add a new and safer ioctl (for these purposes) and optionally disable TIOCSTI per process. So I agree that 3) is perhaps the best way to go. So would be sufficient to add an ioctl to allow {en,dis}abling "current->signal->tty != tty" check, where only CAP_SYS_ADMIN can do so?
Comment 20 Stanislav Brabec 2016-03-16 14:22:21 UTC
As TIOCSTI does not need a special privileges, I guess that this new ioctl() could be called without any special privileges as well. Maybe re-enabling TIOCSTI (if we allow it) would require CAP_SYS_ADMIN.

This bug is reported for chroot as well, and there the inner task still has root privileges. But as we discussed on the call, it's questionable, whether it is a security issue there at all, because root inside chroot still can do anything.


And regarding the second issue, is there a way to disconnect running task from the opened terminal without killing it? Something like revoke()?
Comment 21 Marcus Meissner 2016-03-23 13:05:04 UTC
yes, that would be the setsid() call I think, this detaches the tty control from the task.
Comment 22 Stanislav Brabec 2017-11-20 16:36:16 UTC
util-linux-2.31 introduces new su option: --pty. It finally allows to fully separate privileged and unprivileged shells.
Comment 23 Bernhard Wiedemann 2017-11-27 17:50:09 UTC
This is an autogenerated message for OBS integration:
This bug (968674) was mentioned in
https://build.opensuse.org/request/show/546087 Factory / util-linux
Comment 24 Marcus Meissner 2018-08-22 07:23:21 UTC
hmm, can we backport this to older codestreams?
Comment 25 Stanislav Brabec 2018-08-24 14:34:52 UTC
I guess it is possible to backport --pty. But we cannot make this default, as many things could break.
Comment 26 Markéta Machová 2019-02-22 15:05:47 UTC
Security team, what do you think about this issue?
Comment 27 Alexander Bergmann 2019-04-25 14:52:14 UTC
I haven't looked into the details, but would it possible to have --pty optional, so that nothing breaks but the feature is available?
Comment 28 Stanislav Brabec 2019-04-25 17:12:02 UTC
Here is the main part of the --pty patch:

https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/commit/?id=eb7d0ad0fed520ecf66ef39075f404e1c7e37a51

It would probably be possible to backport to older version in SLE12.

In case of SLE11 and older, it would be more problematic, as su and runuser there comes from shadow, and it is a different implementation.
Comment 29 Markéta Machová 2019-04-26 09:20:42 UTC
I am trying to apply this patch to SLE12SP4. The code structure has changed too much (for example su has become a struct in the meantime), so we have to apply some more upstream patches before this one. What I wanted to say: it is not smooth. Do we really want to backport it?
Comment 33 Robert Frohl 2022-06-21 09:26:01 UTC
done