LCOV - code coverage report
Current view: top level - lib - lwan-straitjacket.c (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 21 118 17.8 %
Date: 2023-04-18 16:19:03 Functions: 2 5 40.0 %

          Line data    Source code
       1             : /*
       2             :  * lwan - web server
       3             :  * Copyright (c) 2015 L. A. F. Pereira <l@tia.mat.br>
       4             :  *
       5             :  * This program is free software; you can redistribute it and/or
       6             :  * modify it under the terms of the GNU General Public License
       7             :  * as published by the Free Software Foundation; either version 2
       8             :  * of the License, or any later version.
       9             :  *
      10             :  * This program is distributed in the hope that it will be useful,
      11             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :  * GNU General Public License for more details.
      14             :  *
      15             :  * You should have received a copy of the GNU General Public License
      16             :  * along with this program; if not, write to the Free Software
      17             :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
      18             :  * USA.
      19             :  */
      20             : 
      21             : #define _GNU_SOURCE
      22             : #include <dirent.h>
      23             : #include <errno.h>
      24             : #include <grp.h>
      25             : #include <limits.h>
      26             : #include <linux/capability.h>
      27             : #include <pwd.h>
      28             : #include <stdlib.h>
      29             : #include <string.h>
      30             : #include <sys/stat.h>
      31             : #include <sys/types.h>
      32             : #include <unistd.h>
      33             : 
      34             : #include "lwan-private.h"
      35             : 
      36             : #include "lwan-config.h"
      37             : #include "lwan-status.h"
      38             : 
      39           0 : static bool get_user_uid_gid(const char *user, uid_t *uid, gid_t *gid)
      40             : {
      41           0 :     struct passwd pwd = {};
      42             :     struct passwd *result;
      43             :     char *buf;
      44           0 :     long pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX);
      45             :     int r;
      46             : 
      47           0 :     if (pw_size_max < 0) {
      48             :         /* This constant is returned for sysconf(_SC_GETPW_R_SIZE_MAX) in glibc,
      49             :          * and it seems to be a reasonable size (1024).  Use it as a fallback in
      50             :          * the (very unlikely) case where sysconf() fails. */
      51           0 :         pw_size_max = NSS_BUFLEN_PASSWD;
      52             :     }
      53             : 
      54           0 :     buf = malloc((size_t)pw_size_max);
      55           0 :     if (!buf) {
      56           0 :         lwan_status_error("Could not allocate buffer for passwd struct");
      57           0 :         return false;
      58             :     }
      59             : 
      60           0 :     r = getpwnam_r(user, &pwd, buf, (size_t)pw_size_max, &result);
      61           0 :     *uid = pwd.pw_uid;
      62           0 :     *gid = pwd.pw_gid;
      63           0 :     free(buf);
      64             : 
      65           0 :     if (result)
      66           0 :         return true;
      67             : 
      68           0 :     if (!r) {
      69           0 :         lwan_status_error("Username not found: %s", user);
      70             :     } else {
      71           0 :         errno = r;
      72           0 :         lwan_status_perror("Could not obtain uid/gid for user %s", user);
      73             :     }
      74             : 
      75           0 :     return false;
      76             : }
      77             : 
      78           0 : static bool switch_to_user(uid_t uid, gid_t gid, const char *username)
      79             : {
      80             :     uid_t ruid, euid, suid;
      81             :     gid_t rgid, egid, sgid;
      82             : 
      83           0 :     lwan_status_info("Dropping privileges to UID %d, GID %d (%s)", uid, gid,
      84             :                      username);
      85             : 
      86           0 :     if (setresgid(gid, gid, gid) < 0)
      87           0 :         return false;
      88             : #if defined(__APPLE__)
      89             :     if (initgroups(username, (int)gid) < 0)
      90             :         return false;
      91             : #else
      92           0 :     if (initgroups(username, gid) < 0)
      93           0 :         return false;
      94             : #endif
      95           0 :     if (setresuid(uid, uid, uid) < 0)
      96           0 :         return false;
      97             : 
      98           0 :     if (getresuid(&ruid, &euid, &suid) < 0)
      99           0 :         return false;
     100           0 :     if (ruid != euid || euid != suid || suid != uid)
     101           0 :         return false;
     102             : 
     103           0 :     if (getresgid(&rgid, &egid, &sgid) < 0)
     104           0 :         return false;
     105           0 :     if (rgid != egid || egid != sgid || sgid != gid)
     106           0 :         return false;
     107             : 
     108           0 :     return true;
     109             : }
     110             : 
     111             : #ifdef __linux__
     112           0 : static void abort_on_open_directories(void)
     113             : {
     114             :     /* This is racy, but is a way to detect misconfiguration.  Since it's
     115             :      * called just once during boot time, before threads are created, this
     116             :      * should be fine (maybe not if Lwan is used as a library.)
     117             :      */
     118           0 :     DIR *dir = opendir("/proc/self/fd");
     119             :     struct dirent *ent;
     120             :     char own_fd[3 * sizeof(int)];
     121             :     int ret;
     122             : 
     123           0 :     if (!dir) {
     124           0 :         lwan_status_critical_perror(
     125             :             "Could not determine if there are open directory fds");
     126             :     }
     127             : 
     128           0 :     ret = snprintf(own_fd, sizeof(own_fd), "%d", dirfd(dir));
     129           0 :     if (ret < 0 || ret >= (int)sizeof(own_fd)) {
     130           0 :         lwan_status_critical("Could not get descriptor of /proc/self/fd");
     131             :     }
     132             : 
     133           0 :     while ((ent = readdir(dir))) {
     134             :         char path[PATH_MAX];
     135             :         struct stat st;
     136             :         ssize_t len;
     137             : 
     138           0 :         if (streq(ent->d_name, own_fd))
     139           0 :             continue;
     140           0 :         if (streq(ent->d_name, ".") || streq(ent->d_name, ".."))
     141           0 :             continue;
     142             : 
     143           0 :         len = readlinkat(dirfd(dir), ent->d_name, path, sizeof(path));
     144           0 :         if (len < 0) {
     145           0 :             lwan_status_critical_perror("Could not get information about fd %s",
     146             :                                         ent->d_name);
     147             :         }
     148           0 :         path[len] = '\0';
     149             : 
     150           0 :         if (path[0] != '/') {
     151             :             /* readlink() there will point to the realpath() of a file, so
     152             :              * if it's on a filesystem, it starts with '/'.  Sockets, for
     153             :              * instance, begin with "socket:" instead...  so no need for
     154             :              * stat().  */
     155           0 :             continue;
     156             :         }
     157             : 
     158           0 :         if (stat(path, &st) < 0) {
     159           0 :             lwan_status_critical_perror(
     160             :                 "Could not get information about open file: %s", path);
     161             :         }
     162             : 
     163           0 :         if (S_ISDIR(st.st_mode)) {
     164           0 :             closedir(dir);
     165             : 
     166           0 :             lwan_status_critical(
     167             :                 "The directory '%s' is open (fd %s), can't chroot", path,
     168             :                 ent->d_name);
     169             :             return;
     170             :         }
     171             :     }
     172             : 
     173           0 :     closedir(dir);
     174             : }
     175             : #else
     176             : static void abort_on_open_directories(void) {}
     177             : #endif
     178             : 
     179          87 : void lwan_straitjacket_enforce(const struct lwan_straitjacket *sj)
     180             : {
     181          87 :     uid_t uid = 0;
     182          87 :     gid_t gid = 0;
     183          87 :     bool got_uid_gid = false;
     184             : 
     185          87 :     if (!sj->user_name && !sj->chroot_path)
     186          87 :         goto out;
     187             : 
     188           0 :     if (geteuid() != 0)
     189           0 :         lwan_status_critical("Straitjacket requires root privileges");
     190             : 
     191           0 :     if (sj->user_name && *sj->user_name) {
     192           0 :         if (!get_user_uid_gid(sj->user_name, &uid, &gid))
     193           0 :             lwan_status_critical("Unknown user: %s", sj->user_name);
     194           0 :         got_uid_gid = true;
     195             :     }
     196             : 
     197           0 :     if (sj->chroot_path) {
     198           0 :         abort_on_open_directories();
     199             : 
     200           0 :         if (chroot(sj->chroot_path) < 0) {
     201           0 :             lwan_status_critical_perror("Could not chroot() to %s",
     202             :                                         sj->chroot_path);
     203             :         }
     204             : 
     205           0 :         if (chdir("/") < 0)
     206           0 :             lwan_status_critical_perror("Could not chdir() to /");
     207             : 
     208           0 :         lwan_status_info("Jailed to %s", sj->chroot_path);
     209             :     }
     210             : 
     211           0 :     if (got_uid_gid && !switch_to_user(uid, gid, sj->user_name)) {
     212           0 :         lwan_status_critical("Could not drop privileges to %s, aborting",
     213             :                              sj->user_name);
     214             :     }
     215             : 
     216           0 : out:
     217          87 :     if (sj->drop_capabilities) {
     218          87 :         struct __user_cap_header_struct header = {
     219             :             .version = _LINUX_CAPABILITY_VERSION_1,
     220             :         };
     221          87 :         struct __user_cap_data_struct data = {};
     222             : 
     223          87 :         if (capset(&header, &data) < 0)
     224           0 :             lwan_status_critical_perror("Could not drop capabilities");
     225             :     }
     226          87 : }
     227             : 
     228          87 : void lwan_straitjacket_enforce_from_config(struct config *c)
     229             : {
     230             :     const struct config_line *l;
     231          87 :     char *user_name = NULL;
     232          87 :     char *chroot_path = NULL;
     233          87 :     bool drop_capabilities = true;
     234             : 
     235         174 :     while ((l = config_read_line(c))) {
     236          87 :         switch (l->type) {
     237           0 :         case CONFIG_LINE_TYPE_LINE:
     238             :             /* TODO: limit_syscalls */
     239           0 :             if (streq(l->key, "user")) {
     240           0 :                 if (user_name) {
     241           0 :                     config_error(c, "`user' already specified");
     242          87 :                     return;
     243             :                 }
     244           0 :                 user_name = strdupa(l->value);
     245           0 :             } else if (streq(l->key, "chroot")) {
     246           0 :                 if (chroot_path) {
     247           0 :                     config_error(c, "`chroot' already specified");
     248           0 :                     return;
     249             :                 }
     250           0 :                 chroot_path = strdupa(l->value);
     251           0 :             } else if (streq(l->key, "drop_capabilities")) {
     252           0 :                 drop_capabilities = parse_bool(l->value, true);
     253             :             } else {
     254           0 :                 config_error(c, "Invalid key: %s", l->key);
     255           0 :                 return;
     256             :             }
     257           0 :             break;
     258           0 :         case CONFIG_LINE_TYPE_SECTION:
     259           0 :             config_error(c, "Straitjacket accepts no sections");
     260           0 :             return;
     261          87 :         case CONFIG_LINE_TYPE_SECTION_END:
     262          87 :             lwan_straitjacket_enforce(&(struct lwan_straitjacket){
     263             :                 .user_name = user_name,
     264             :                 .chroot_path = chroot_path,
     265             :                 .drop_capabilities = drop_capabilities,
     266             :             });
     267             : 
     268          87 :             return;
     269             :         }
     270             :     }
     271             : 
     272           0 :     config_error(c, "Expecting section end while parsing straitjacket");
     273             : }

Generated by: LCOV version 1.15-2-gb9d6727