Hi,

the SUSE security team has been asked [1] to review the new
`enlightenment_system` setuid-root binary for inclusion into openSUSE
Tumbleweed. Therefore I looked into the snapshot that our packager
provided me with. It seems to correspond to git [2] commit
b5fa86e7f5301452f4156ba62bc073f27280c798, at least with regard to the
`enlightenment_system` code itself.

[1]: https://bugzilla.suse.com/show_bug.cgi?id=1169238
[2]: https://git.enlightenment.org/core/enlightenment.git

# Security Issues

After reviewing this setuid-root binary I don't deem the current state of it
fit for production use. I have found the following individual issues:

## a) `_store_mount_verify()` follows symlinks in /media/$user

This function rejects relative path components in the target mount path.
It is unaware of symlinks, however. Furthermore it makes sure that
/media/$user and /media/$user/$sub are existing and are owned by the
$uid:$gid of the unprivileged user.

- by placing a symlink in /media/$user/$sub the setuid-root binary can be
  tricked into creating attacker owned directories in arbitrary locations.
  This can quite likely lead to full root access by creating user owned
  directories e.g. beneath /etc that are then used by other privileged
  programs.
- if the attacker wins a race condition he can also cause the setuid-root
  binary to pass ownership of arbitrary existing directories to him. The
  `_store_mount_verify()` function performs a single `stat()` call on the
  target mount path. The operation is only rejected if it exists and is
  not owned by the unprivileged user. Therefore if the attacker places
  a suitable symlink in the target path just after this `stat()` is
  performed by the setuid-root binary, the following `_mkdir()`
  invocation will `mkdir()` and `chown()` the path components
  nonetheless. This allows full root system access by gaining ownership
  of e.g. /etc or /root.

To fix this I suggest not to pass ownership of /media/$user or of any
sub-directories to the unprivileged user. If /media/$user is user
controlled then the mount operation should be rejected.

## b) `_store_umount_verify()` does not protect against shell
      metacharacters and relative path components

This function tries to make sure that the user can only unmount his own
mounts below /media/$user. It also rejects backslashes in the path.
However it does not reject relative path components or shell characters.

- this allows a regular user to unmount arbitrary file systems by passing
  paths like "/media/$user/../../tmp.
- since the unmount is performed by calling the `umount` utility via
  "/bin/sh", shell metacharacters will be interpreted. Passing a path
  like '/media/testuser/$(date)' will cause the setuid-root program to
  execute the `date` program as root. This leads to full code execution
  as root. The only requirement is that a directory of the same name
  exists. Spaces are also allowed in the path, therefore even complex
  commands can be executed as root.

I recommend to reject relative path components and shell metacharacters in
this function to fix the issue.

## c) `_store_device_verify()` limitations are insufficient

This function tries to make sure that the source device path argument
for block device operations is within the confines of the /dev
directory. To do so a lot of special characters are rejected as well as
relative path components "/..". It fails to consider symlinks, however:

- The /dev/fd directory on Linux is a symlink to /proc/self/fd.
  Therefore an already open file descriptor can be used as device
  argument. Open files are inherited from a potential attacker's context
  into the setuid-root context, therefore this can be used to circumvent
  the limitation. A prerequisite is that the attacker needs to have
  necessary privileges to open a file descriptor for the source file.

- The /dev/shm directory on Linux is a world-writable sticky-bit
  directory. Therefore an unprivileged user can place symlinks in this
  directory. `_store_device_verify()` will not reject such paths. Such
  a symlink attack only works if the kernel symlink protection feature
  is off, however. Or if the attacker wins a race condition, because
  `_store_device_verify()` performs a `stat()` on the path and only
  rejects the operation if the file can't be accessed. So an attacker
  could first place a regular file in there and after the `stat()` is
  performed it can replaced the file by a symlink. The setuid-root
  program will then pass the path to the symlink when invoking child
  programs e.g. an `eject /dev/shm/test` which points to /dev/sr0 worked
  for me.

To fix this, these two cases can be rejected by the function's logic.
But in general it's difficult to filter out bad paths like this. To make
it safer the path components would need to be opened step by step and
each component would need to be checked against symlinks or otherwise
user controlled content. This can be achieved by using system calls
like `openat()` using O_PATH and `fstat()` starting from the root path
component.

## d) `_cb_l2ping_ping()` performs an unbounded `sscanf()` on untrusted
      input data, allowing a stack buffer overflow

A `sscanf()` call in this function passes a `%s` format for the `params`
input parameter. The target buffer has a length of 1024 bytes. Thus if a
client passes a very long device name the setuid-root binary's stack
will be overwritten. The parsing by `sscanf()` stops at whitespace
characters thus the stack overflow data cannot be chosen arbitrarily.
Still it is a pretty dangerous security issue.

## e) `_cb_l2ping_ping()` SEGFAULTs when no parameter is passed.

This function unconditionally dereferences the `params` string in a
`sscanf()` call, which causes a SEGFAULT by dereferencing a NULL pointer
when a user is not passing any parameter data.

## f) /etc/enlightenment/sysactions.conf limitations are ineffective

There is a security mechanism implemented based on the file
/etc/enlightenment/sysactions.conf. This file defines which users/groups
are allowed to execute the `enlightenment_system` binary in the first
place.

The `_etc_enlightenment_system_conf()` function parses this file.
However there seems to be a `while` or `for` loop body missing. Instead
only the first line of the file is ever parsed, which happens to be a
comment line by default. The logic in the function defaults to "allow
everything" if nothing else was determined. Thus this security mechanism
is currently ineffective and all users in the system can use the full
functionality of the setuid-root program.

I suggest to deny access by default and correct the algorithm to
correctly parse the configuration file.

## g) setcwd() is missing and a check for "libcurl.so.5" allows arbitrary
      file existence tests

The setuid-root program does not reset the current working directory to
a safe path like "/". Therefore whichever directory the unprivileged
user sits in when invoking the setuid-root program will affect relative
path lookups performed in the setuid-root context.

One such instance is found in the context of the call to
`ecore_file_download_init()´ which later down the path tries to load the
shared library "libcurl.so.5" via `eina_module_load()`. If the
`dlopen()` fails the latter function performs a `stat()` on the library
basename "to determine" whether the library existed or not. If it thinks
the library existed but couldn't be loaded then it prints out an error
message.

An attacker can place a symlink named "libcurl.so.5" in the current
working directory and enlightenment_system will follow the link and
behave differently depending on whether the link target exists or not.
Therefore an attacker can test for existence of arbitrary files also in
paths usually not accessible to him. Example:

```
# symlink pointing to non-existing file
user /home/user $ ln -s /root/.notexisting libcurl.so.5
user /home/user $ /usr/lib64/enlightenment/utils/enlightenment_system 
ddc-list^C

# symlink pointing to existing file
user /home/user $ ln -s /root/.bashrc libcurl.so.5
user /home/user $ /usr/lib64/enlightenment/utils/enlightenment_system
ERR<4165>:eina_module ../src/lib/eina/eina_module.c:319 eina_module_load() 
could not dlopen("libcurl.so.5", libcurl.so.5: cannot open shared object file: 
No such file or directory): RTLD_NOW
[...]
```

I suggest to set the CWD to a defined and safe default like "/".

Furthermore I'd adjust the code in `eina_module_load()` to use
`dlerror()` instead of guessing reasons for failed library loads.

## h) `_cb_stdio_in_read()`: potentially large memory allocation based
      on untrusted user data

The line `buf = malloc(head.size)` in this function uses the untrusted
size specification provided by the unprivileged user to allocate a
potentially large chunk of memory on the heap. On Linux this is mostly
uncritical, because the kernel by default overcommits memory and only
really allocates memory once it's accessed. On other OSs this could be
used to hog memory in a root process, however.

I suggest to implement a reasonable maximum message size and reject
everything else.

## i) `ecore_file_app_installed()` can be tricked into returning bogus results

Various calls to `ecore_file_app_installed()` are performed in the
context of the setuid-root binary. This function performs a direct check
for the existence of the given filename before checking the directories
found in the PATH environment variable.

Since the CWD is controlled by a potential attacker (see g)), the
attacker can place arbitrary files named like the searched binaries in
the CWD. As a result the `ecore_file_app_installed()` will returns bogus
results. I couldn't find any way to exploit this fact in the context of
the setuid-root binary, however.

Like in g) I suggest to set the CWD to a safe default value.
Furthermore I suggest *not* to check the CWD in
`ecore_file_app_installed()` installed. If the CWD should be checked
then the PATH environment variable should contain an entry for "."
instead.

# Security Vulnerability Process

I'm posting these findings here publicly since the Enlightenment project
does not document any preferred vulnerability report procedure and does
not offer a means of coordinated disclosure. I asked on the
Enlightenment freenode IRC channel about the best way to report security
issues and I was pointed towards the mailing lists and the issue
tracker.

From my point of view at least items a), b) and d) deserve a CVE
assignment due to the severity of the issues. Even if to my knowledge
the code in question wasn't yet part of an official release yet it might
help the community to identify risks in their systems. Please tell me
whether you want to assign CVEs on your end or whether I should do this.

# The general design of the `enlightenment_system` setuid-root program

I would like to share my thoughts on the general design of this
setuid-root program:

- the fact that all of the child processes are created through "/bin/sh"
  is difficult for security and complicates the code a lot. By using a
  mechanism based on the `execve()` system call all worries about shell
  metacharacters would be gone in the first place.
- the various "filter" logic that tries to detect bad paths and alike is
  error prone in nature. It is very difficult to operate as root on user
  controlled paths. Lower level system calls like `openat()` are a must
  to avoid symlink attacks and other surprises. Child programs like
  `mount` can then be pointed to the already open files in
  /proc/self/fd/, for example. This is valid at least for Linux.
- allowing to mount arbitrary block devices as is the case with the
  "store-mount" command seems a bad idea to me. This should be limited
  to removable block devices at best (this property can be determined
  via sysfs on Linux).
- even though the setuid-root specific code is "only" about 3.000 lines
  of code in size, the plethora of framework libraries pulled in make
  this a surprisingly complex program for a setuid-root utility. These
  libraries are largely unaware that they're operating in a security
  sensitive context which can create additional security issues (like
  issue g) and i)).
- the design of the program is already asynchronous in nature based on
  the main loop. Furthermore there is a rather complex communication
  channel established between the caller and the program based on a
  special purpose binary protocol. Given this I wonder why a setuid-root
  program has been chosen to realize these features in the first place.
  With this level of complexity and feature set a D-Bus service with
  polkit authentication on top would be the cleaner solution.

Cheers

Matthias

-- 
Matthias Gerstner <matthias.gerst...@suse.de>
Dipl.-Wirtsch.-Inf. (FH), Security Engineer
https://www.suse.com/security
Phone: +49 911 740 53 290
GPG Key ID: 0x14C405C971923553

SUSE Software Solutions Germany GmbH
HRB 36809, AG Nürnberg
Geschäftsführer: Felix Imendörffer

Attachment: signature.asc
Description: PGP signature

_______________________________________________
enlightenment-devel mailing list
enlightenment-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/enlightenment-devel

Reply via email to