It was discovered that the sudo noexec restriction could have been bypassed if application run via sudo executed system() or popen() C library functions with a user supplied argument. A local user permitted to run such application via sudo with noexec restriction could use this flaw to execute arbitrary commands with elevated privileges.
Florian Weimer of Red Hat reports:
the sudoers manual page says this:
EXEC and NOEXEC
If sudo has been compiled with noexec support and the underly‐
ing operating system supports it, the NOEXEC tag can be used to
prevent a dynamically-linked executable from running further
In the following example, user aaron may run /usr/bin/more and
/usr/bin/vi but shell escapes will be disabled.
aaron shanty = NOEXEC: /usr/bin/more, /usr/bin/vi
To enable noexec for a command, use the NOEXEC tag as
documented in the User Specification section above.
Here is that example again:
aaron shanty = NOEXEC: /usr/bin/more, /usr/bin/vi
This allows user aaron to run /usr/bin/more and
/usr/bin/vi with noexec enabled. This will prevent
those two commands from executing other commands (such
as a shell). If you are unsure whether or not your
system is capable of supporting noexec you can always
just try it out and check whether shell escapes work
when noexec is enabled.
However, the filtering DSO does not intercept all glibc functions which
allow to spawn a shell. At least popen, system, and wordexp are missing:
Show quoted text
0000000000000c80 T execl
0000000000000cc0 T _execl
0000000000000d00 T __execl
0000000000000d40 T execle
0000000000000d80 T _execle
0000000000000dc0 T __execle
0000000000000e00 T execlp
0000000000000e40 T _execlp
0000000000000e80 T __execlp
0000000000000ec0 T exect
0000000000000f00 T _exect
0000000000000f40 T __exect
0000000000000f80 T execv
0000000000000fc0 T _execv
0000000000001000 T __execv
00000000000011c0 T execve
0000000000001200 T _execve
0000000000001240 T __execve
0000000000001040 T execvp
0000000000001080 T _execvp
00000000000010c0 T __execvp
0000000000001100 T execvP
0000000000001140 T _execvP
0000000000001180 T __execvP
0000000000001280 T execvpe
00000000000012c0 T _execvpe
0000000000001300 T __execvpe
0000000000001340 T fexecve
0000000000001380 T _fexecve
00000000000013c0 T __fexecve
0000000000001480 T __posix_spawn
0000000000001440 T _posix_spawn
0000000000001400 T posix_spawn
0000000000001540 T __posix_spawnp
0000000000001500 T _posix_spawnp
00000000000014c0 T posix_spawnp
The source file src/sudo_noexec.c contains this comment:
* Dummy versions of the execve() family of syscalls. We don't need
* to stub out all of them, just the ones that correspond to actual
* system calls (which varies by OS). Note that it is still possible
* to access the real syscalls via the syscall() interface but very
* few programs actually do that.
This is wrong. Interposing execve does not override internal calls to
execve within glibc.
The right fix is to set a seccomp filter which blocks execve in the DSO
(after enabling PR_SET_NO_NEW_PRIVS), rather than attempting to play
catch-up with glibc.
Name: Florian Weimer (Red Hat)
Apparently upstream is aware of the problem and addressed system() and popen() cases about a year ago:
(In reply to Tomas Hoger from comment #2)
> Apparently upstream is aware of the problem and addressed system() and
> popen() cases about a year ago:
Ahh, maybe that's why I was vaguely familiar with this issue. True blocking still needs a seccomp filter, though.
The sudo version in Red Hat Enterprise Linux 5 also lacks this commit:
Blocking wrappers for posix_spawn() and posix_spawnp() are relevant. exect() seems BSD-specific API, and execvpe() was only added in glibc 2.11, while Red Hat Enterprise Linux 5 includes glibc 2.5.
(In reply to Kurt Seifried from comment #0)
> The right fix is to set a seccomp filter which blocks execve in the DSO
> (after enabling PR_SET_NO_NEW_PRIVS), rather than attempting to play
> catch-up with glibc.
As far as I can see, such seccomp approach would only be viable on Red Hat Enterprise Linux 7 or later. I'm also unsure if sudo upstream would be willing to write the code. Do you or sudo maintainer have capacity to implement this and get is merged upstream?
(In reply to Tomas Hoger from comment #5)
> (In reply to Kurt Seifried from comment #0)
> > The right fix is to set a seccomp filter which blocks execve in the DSO
> > (after enabling PR_SET_NO_NEW_PRIVS), rather than attempting to play
> > catch-up with glibc.
> As far as I can see, such seccomp approach would only be viable on Red Hat
> Enterprise Linux 7 or later. I'm also unsure if sudo upstream would be
> willing to write the code. Do you or sudo maintainer have capacity to
> implement this and get is merged upstream?
A while back I wrote:
It's not 100% complete because it does not deal with multi-arch system calls. On x86_64, a program could still evade this by jumping to short mode and doing the i386 syscalls (which have different numbers), or call the x32 syscalls. In most cases, this will need some sort of code execution exploit in the target process because the process will not expose this sort of capability to the end user.
Would this also need other tweaks for other non-x86 architectures? Would use of libseccomp be recommended to hide platform specific details?
(In reply to Tomas Hoger from comment #8)
> Would this also need other tweaks for other non-x86 architectures?
Other architectures may have different forms of compatibility system calls, yes.
> Would use of libseccomp be recommended to hide platform specific details?
I don't know if it handles multi-arch system calls in the way needed. I have asked Paul Moore.
The following changes were applied upstream for inclusion in 1.8.18p1:
* Wrapper for wordexp() was added to sudo_noexec.so which forces the use of WRDE_NOCMD flag in wordexp().
NEWS file entry:
* When sudo_noexec.so is used, the WRDE_NOCMD flag is now added
if the wordexp() function is called. This prevents commands
from being run via wordexp() without disabling it entirely.
* seccomp support was added to sudo_noexec.so.
NEWS file entry:
* On Linux systems, sudo_noexec.so now uses a seccomp filter to
disable execute access if the kernel supports seccomp. This is
more robust than the traditional method of using stub functions
that return an error.
As system()/popen() and wordexp() were fixed in different versions, the wordexp() case will be tracked under separate CVE-2016-7076 - see bug 1384982.
Public now via upstream advisory.
This issue has been addressed in the following products:
Red Hat Enterprise Linux 6
Red Hat Enterprise Linux 7
Via RHSA-2016:2872 https://rhn.redhat.com/errata/RHSA-2016-2872.html