Fix: runas: supplementary groups are ignored on lttng save
[lttng-tools.git] / src / common / runas.c
index 222047cb60422023031f5a531879d166edccb154..cfc898f166d45e8f313b2e060c1518568d54fcce 100644 (file)
@@ -1,37 +1,27 @@
 /*
- * Copyright (C) 2011 David Goulet <david.goulet@polymtl.ca>
- *                      Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
- *               2019 - Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ * Copyright (C) 2011 David Goulet <david.goulet@polymtl.ca>
+ * Copyright (C) 2011 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ * Copyright (C) 2019 Jérémie Galarneau <jeremie.galarneau@efficios.com>
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2 only,
- * as published by the Free Software Foundation.
+ * SPDX-License-Identifier: GPL-2.0-only
  *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 #define _LGPL_SOURCE
-#include <errno.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <grp.h>
 #include <limits.h>
+#include <pwd.h>
+#include <sched.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/wait.h>
-#include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 #include <unistd.h>
-#include <fcntl.h>
-#include <sched.h>
-#include <signal.h>
-#include <assert.h>
-#include <signal.h>
 
 #include <common/lttng-kernel.h>
 #include <common/common.h>
@@ -46,6 +36,8 @@
 
 #include "runas.h"
 
+#define GETPW_BUFFER_FALLBACK_SIZE 4096
+
 struct run_as_data;
 struct run_as_ret;
 typedef int (*run_as_fct)(struct run_as_data *data, struct run_as_ret *ret_value);
@@ -351,22 +343,27 @@ int _mkdirat_recursive(struct run_as_data *data, struct run_as_ret *ret_value)
 {
        const char *path;
        mode_t mode;
-       struct lttng_directory_handle handle;
+       struct lttng_directory_handle *handle;
 
        path = data->u.mkdir.path;
        mode = data->u.mkdir.mode;
 
-       (void) lttng_directory_handle_init_from_dirfd(&handle,
-                       data->u.mkdir.dirfd);
+       handle = lttng_directory_handle_create_from_dirfd(data->u.mkdir.dirfd);
+       if (!handle) {
+               ret_value->_errno = errno;
+               ret_value->_error = true;
+               ret_value->u.ret = -1;
+               goto end;
+       }
        /* Ownership of dirfd is transferred to the handle. */
        data->u.mkdir.dirfd = -1;
        /* Safe to call as we have transitioned to the requested uid/gid. */
-       ret_value->u.ret =
-                       lttng_directory_handle_create_subdirectory_recursive(
-                                       &handle, path, mode);
+       ret_value->u.ret = lttng_directory_handle_create_subdirectory_recursive(
+                       handle, path, mode);
        ret_value->_errno = errno;
        ret_value->_error = (ret_value->u.ret) ? true : false;
-       lttng_directory_handle_fini(&handle);
+       lttng_directory_handle_put(handle);
+end:
        return ret_value->u.ret;
 }
 
@@ -375,22 +372,27 @@ int _mkdirat(struct run_as_data *data, struct run_as_ret *ret_value)
 {
        const char *path;
        mode_t mode;
-       struct lttng_directory_handle handle;
+       struct lttng_directory_handle *handle;
 
        path = data->u.mkdir.path;
        mode = data->u.mkdir.mode;
 
-       (void) lttng_directory_handle_init_from_dirfd(&handle,
-                       data->u.mkdir.dirfd);
+       handle = lttng_directory_handle_create_from_dirfd(data->u.mkdir.dirfd);
+       if (!handle) {
+               ret_value->u.ret = -1;
+               ret_value->_errno = errno;
+               ret_value->_error = true;
+               goto end;
+       }
        /* Ownership of dirfd is transferred to the handle. */
        data->u.mkdir.dirfd = -1;
        /* Safe to call as we have transitioned to the requested uid/gid. */
-       ret_value->u.ret =
-                       lttng_directory_handle_create_subdirectory(
-                                       &handle, path, mode);
+       ret_value->u.ret = lttng_directory_handle_create_subdirectory(
+                       handle, path, mode);
        ret_value->_errno = errno;
        ret_value->_error = (ret_value->u.ret) ? true : false;
-       lttng_directory_handle_fini(&handle);
+       lttng_directory_handle_put(handle);
+end:
        return ret_value->u.ret;
 }
 
@@ -398,14 +400,19 @@ static
 int _open(struct run_as_data *data, struct run_as_ret *ret_value)
 {
        int fd;
-       struct lttng_directory_handle handle;
+       struct lttng_directory_handle *handle;
 
-       (void) lttng_directory_handle_init_from_dirfd(&handle,
-                       data->u.open.dirfd);
+       handle = lttng_directory_handle_create_from_dirfd(data->u.open.dirfd);
+       if (!handle) {
+               ret_value->_errno = errno;
+               ret_value->_error = true;
+               ret_value->u.ret = -1;
+               goto end;
+       }
        /* Ownership of dirfd is transferred to the handle. */
        data->u.open.dirfd = -1;
 
-       fd = lttng_directory_handle_open_file(&handle,
+       fd = lttng_directory_handle_open_file(handle,
                        data->u.open.path, data->u.open.flags,
                        data->u.open.mode);
        if (fd < 0) {
@@ -418,64 +425,83 @@ int _open(struct run_as_data *data, struct run_as_ret *ret_value)
 
        ret_value->_errno = errno;
        ret_value->_error = fd < 0;
-       lttng_directory_handle_fini(&handle);
+       lttng_directory_handle_put(handle);
+end:
        return ret_value->u.ret;
 }
 
 static
 int _unlink(struct run_as_data *data, struct run_as_ret *ret_value)
 {
-       struct lttng_directory_handle handle;
+       struct lttng_directory_handle *handle;
 
-       (void) lttng_directory_handle_init_from_dirfd(&handle,
-                       data->u.unlink.dirfd);
+       handle = lttng_directory_handle_create_from_dirfd(data->u.unlink.dirfd);
+       if (!handle) {
+               ret_value->u.ret = -1;
+               ret_value->_errno = errno;
+               ret_value->_error = true;
+               goto end;
+       }
 
        /* Ownership of dirfd is transferred to the handle. */
        data->u.unlink.dirfd = -1;
 
-       ret_value->u.ret = lttng_directory_handle_unlink_file(&handle,
+       ret_value->u.ret = lttng_directory_handle_unlink_file(handle,
                        data->u.unlink.path);
        ret_value->_errno = errno;
        ret_value->_error = (ret_value->u.ret) ? true : false;
-       lttng_directory_handle_fini(&handle);
+       lttng_directory_handle_put(handle);
+end:
        return ret_value->u.ret;
 }
 
 static
 int _rmdir(struct run_as_data *data, struct run_as_ret *ret_value)
 {
-       struct lttng_directory_handle handle;
+       struct lttng_directory_handle *handle;
 
-       (void) lttng_directory_handle_init_from_dirfd(&handle,
-                       data->u.rmdir.dirfd);
+       handle = lttng_directory_handle_create_from_dirfd(data->u.rmdir.dirfd);
+       if (!handle) {
+               ret_value->u.ret = -1;
+               ret_value->_errno = errno;
+               ret_value->_error = true;
+               goto end;
+       }
 
        /* Ownership of dirfd is transferred to the handle. */
        data->u.rmdir.dirfd = -1;
 
        ret_value->u.ret = lttng_directory_handle_remove_subdirectory(
-                       &handle, data->u.rmdir.path);
+                       handle, data->u.rmdir.path);
        ret_value->_errno = errno;
        ret_value->_error = (ret_value->u.ret) ? true : false;
-       lttng_directory_handle_fini(&handle);
+       lttng_directory_handle_put(handle);
+end:
        return ret_value->u.ret;
 }
 
 static
 int _rmdir_recursive(struct run_as_data *data, struct run_as_ret *ret_value)
 {
-       struct lttng_directory_handle handle;
+       struct lttng_directory_handle *handle;
 
-       (void) lttng_directory_handle_init_from_dirfd(&handle,
-                       data->u.rmdir.dirfd);
+       handle = lttng_directory_handle_create_from_dirfd(data->u.rmdir.dirfd);
+       if (!handle) {
+               ret_value->u.ret = -1;
+               ret_value->_errno = errno;
+               ret_value->_error = true;
+               goto end;
+       }
 
        /* Ownership of dirfd is transferred to the handle. */
        data->u.rmdir.dirfd = -1;
 
        ret_value->u.ret = lttng_directory_handle_remove_subdirectory_recursive(
-                       &handle, data->u.rmdir.path, data->u.rmdir.flags);
+                       handle, data->u.rmdir.path, data->u.rmdir.flags);
        ret_value->_errno = errno;
        ret_value->_error = (ret_value->u.ret) ? true : false;
-       lttng_directory_handle_fini(&handle);
+       lttng_directory_handle_put(handle);
+end:
        return ret_value->u.ret;
 }
 
@@ -483,26 +509,35 @@ static
 int _rename(struct run_as_data *data, struct run_as_ret *ret_value)
 {
        const char *old_path, *new_path;
-       struct lttng_directory_handle old_handle, new_handle;
+       struct lttng_directory_handle *old_handle = NULL, *new_handle = NULL;
 
        old_path = data->u.rename.old_path;
        new_path = data->u.rename.new_path;
 
-       (void) lttng_directory_handle_init_from_dirfd(&old_handle,
+       old_handle = lttng_directory_handle_create_from_dirfd(
                        data->u.rename.dirfds[0]);
-       (void) lttng_directory_handle_init_from_dirfd(&new_handle,
+       if (!old_handle) {
+               ret_value->u.ret = -1;
+               goto end;
+       }
+       new_handle = lttng_directory_handle_create_from_dirfd(
                        data->u.rename.dirfds[1]);
+       if (!new_handle) {
+               ret_value->u.ret = -1;
+               goto end;
+       }
 
        /* Ownership of dirfds are transferred to the handles. */
        data->u.rename.dirfds[0] = data->u.rename.dirfds[1] = -1;
 
        /* Safe to call as we have transitioned to the requested uid/gid. */
        ret_value->u.ret = lttng_directory_handle_rename(
-                       &old_handle, old_path, &new_handle, new_path);
+                       old_handle, old_path, new_handle, new_path);
+end:
+       lttng_directory_handle_put(old_handle);
+       lttng_directory_handle_put(new_handle);
        ret_value->_errno = errno;
        ret_value->_error = (ret_value->u.ret) ? true : false;
-       lttng_directory_handle_fini(&old_handle);
-       lttng_directory_handle_fini(&new_handle);
        return ret_value->u.ret;
 }
 
@@ -790,18 +825,207 @@ end:
        return ret;
 }
 
+static int get_user_infos_from_uid(
+               uid_t uid, char **username, gid_t *primary_gid)
+{
+       int ret;
+       char *buf = NULL;
+       size_t buf_size;
+       struct passwd pwd;
+       struct passwd *result = NULL;
+
+       /* Fetch the max size for the temporary buffer. */
+       errno = 0;
+       buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
+       if (buf_size < 0) {
+               if (errno != 0) {
+                       PERROR("Failed to query _SC_GETPW_R_SIZE_MAX");
+                       goto error;
+               }
+
+               /* Limit is indeterminate. */
+               WARN("Failed to query _SC_GETPW_R_SIZE_MAX as it is "
+                       "indeterminate; falling back to default buffer size");
+               buf_size = GETPW_BUFFER_FALLBACK_SIZE;
+       }
+
+       buf = zmalloc(buf_size);
+       if (buf == NULL) {
+               PERROR("Failed to allocate buffer to get password file entries");
+               goto error;
+       }
+
+       ret = getpwuid_r(uid, &pwd, buf, buf_size, &result);
+       if (ret < 0) {
+               PERROR("Failed to get user information for user:  uid = %d",
+                               (int) uid);
+               goto error;
+       }
+
+       if (result == NULL) {
+               ERR("Failed to find user information in password entries: uid = %d",
+                               (int) uid);
+               ret = -1;
+               goto error;
+       }
+
+       *username = strdup(result->pw_name);
+       if (*username == NULL) {
+               PERROR("Failed to copy user name");
+               goto error;
+       }
+
+       *primary_gid = result->pw_gid;
+
+end:
+       free(buf);
+       return ret;
+error:
+       *username = NULL;
+       *primary_gid = -1;
+       ret = -1;
+       goto end;
+}
+
+static int demote_creds(
+               uid_t prev_uid, gid_t prev_gid, uid_t new_uid, gid_t new_gid)
+{
+       int ret = 0;
+       gid_t primary_gid;
+       char *username = NULL;
+
+       /* Change the group id. */
+       if (prev_gid != new_gid) {
+               ret = setegid(new_gid);
+               if (ret < 0) {
+                       PERROR("Failed to set effective group id: new_gid = %d",
+                                       (int) new_gid);
+                       goto end;
+               }
+       }
+
+       /* Change the user id. */
+       if (prev_uid != new_uid) {
+               ret = get_user_infos_from_uid(new_uid, &username, &primary_gid);
+               if (ret < 0) {
+                       goto end;
+               }
+
+               /*
+                * Initialize the supplementary group access list.
+                *
+                * This is needed to handle cases where the supplementary groups
+                * of the user the process is demoting-to would give it access
+                * to a given file/folder, but not it's primary group.
+                *
+                * e.g
+                *   username: User1
+                *   Primary Group: User1
+                *   Secondary group: Disk, Network
+                *
+                *   mkdir inside the following directory must work since User1
+                *   is part of the Network group.
+                *
+                *   drwxrwx--- 2 root Network 4096 Jul 23 17:17 /tmp/my_folder/
+                *
+                *
+                * The order of the following initgroups and seteuid calls is
+                * important here;
+                * Only a root process or one with CAP_SETGID capability can
+                * call the the initgroups() function. We must initialize the
+                * supplementary groups before we change the effective
+                * UID to a less-privileged user.
+                */
+               ret = initgroups(username, primary_gid);
+               if (ret < 0) {
+                       PERROR("Failed to init the supplementary group access list: "
+                                       "username = `%s`, primary gid = %d", username,
+                                       (int) primary_gid);
+                       goto end;
+               }
+
+               ret = seteuid(new_uid);
+               if (ret < 0) {
+                       PERROR("Failed to set effective user id: new_uid = %d",
+                                       (int) new_uid);
+                       goto end;
+               }
+       }
+end:
+       free(username);
+       return ret;
+}
+
+static int promote_creds(
+               uid_t prev_uid, gid_t prev_gid, uid_t new_uid, gid_t new_gid)
+{
+       int ret = 0;
+       gid_t primary_gid;
+       char *username = NULL;
+
+       /* Change the group id. */
+       if (prev_gid != new_gid) {
+               ret = setegid(new_gid);
+               if (ret < 0) {
+                       PERROR("Failed to set effective group id: new_gid = %d",
+                                       (int) new_gid);
+                       goto end;
+               }
+       }
+
+       /* Change the user id. */
+       if (prev_uid != new_uid) {
+               ret = get_user_infos_from_uid(new_uid, &username, &primary_gid);
+               if (ret < 0) {
+                       goto end;
+               }
+
+               /*
+                * seteuid call must be done before the initgroups call because
+                * we need to be privileged (CAP_SETGID) to call initgroups().
+                */
+               ret = seteuid(new_uid);
+               if (ret < 0) {
+                       PERROR("Failed to set effective user id: new_uid = %d",
+                                       (int) new_uid);
+                       goto end;
+               }
+
+               /*
+                * Initialize the supplementary group access list.
+                *
+                * There is a possibility the groups we set in the following
+                * initgroups() call are not exactly the same as the ones we
+                * had when we originally demoted. This can happen if the
+                * /etc/group file is modified after the runas process is
+                * forked. This is very unlikely.
+                */
+               ret = initgroups(username, primary_gid);
+               if (ret < 0) {
+                       PERROR("Failed to init the supplementary group access "
+                                       "list: username = `%s`, primary gid = %d",
+                                       username, (int) primary_gid)
+                       goto end;
+               }
+       }
+end:
+       free(username);
+       return ret;
+}
+
 /*
  * Return < 0 on error, 0 if OK, 1 on hangup.
  */
 static
 int handle_one_cmd(struct run_as_worker *worker)
 {
-       int ret = 0;
-        struct run_as_data data = {};
-        ssize_t readlen, writelen;
-        struct run_as_ret sendret = {};
-        run_as_fct cmd;
-       uid_t prev_euid;
+       int ret = 0, promote_ret;
+       struct run_as_data data = {};
+       ssize_t readlen, writelen;
+       struct run_as_ret sendret = {};
+       run_as_fct cmd;
+       uid_t prev_ruid;
+       gid_t prev_rgid;
 
        /*
         * Stage 1: Receive run_as_data struct from the master.
@@ -839,24 +1063,12 @@ int handle_one_cmd(struct run_as_worker *worker)
                goto end;
        }
 
-       prev_euid = getuid();
-       if (data.gid != getegid()) {
-               ret = setegid(data.gid);
-               if (ret < 0) {
-                       sendret._error = true;
-                       sendret._errno = errno;
-                       PERROR("setegid");
-                       goto write_return;
-               }
-       }
-       if (data.uid != prev_euid) {
-               ret = seteuid(data.uid);
-               if (ret < 0) {
-                       sendret._error = true;
-                       sendret._errno = errno;
-                       PERROR("seteuid");
-                       goto write_return;
-               }
+       prev_ruid = getuid();
+       prev_rgid = getgid();
+
+       ret = demote_creds(prev_ruid, prev_rgid, data.uid, data.gid);
+       if (ret < 0) {
+               goto write_return;
        }
 
        /*
@@ -876,7 +1088,7 @@ write_return:
        ret = cleanup_received_fds(&data);
        if (ret < 0) {
                ERR("Error cleaning up FD");
-               goto end;
+               goto promote_back;
        }
 
        /*
@@ -888,7 +1100,7 @@ write_return:
        if (writelen < sizeof(sendret)) {
                PERROR("lttcomm_send_unix_sock error");
                ret = -1;
-               goto end;
+               goto promote_back;
        }
 
        /*
@@ -897,15 +1109,17 @@ write_return:
        ret = send_fds_to_master(worker, data.cmd, &sendret);
        if (ret < 0) {
                DBG("Sending FD to master returned an error");
-               goto end;
        }
 
-       if (seteuid(prev_euid) < 0) {
-               PERROR("seteuid");
-               ret = -1;
-               goto end;
-       }
        ret = 0;
+
+promote_back:
+       /* Return to previous uid/gid. */
+       promote_ret = promote_creds(data.uid, data.gid, prev_ruid, prev_rgid);
+       if (promote_ret < 0) {
+               ERR("Failed to promote back to the initial credentials");
+       }
+
 end:
        return ret;
 }
This page took 0.029831 seconds and 4 git commands to generate.