1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
|
/*
* Minimal handling of Linux kernel capabilities
*
* Copyright 2000-2023 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
/* Depending on distros, some have capset(), others use the more complicated
* libcap. Let's stick to what we need and the kernel documents (capset).
* Note that prctl is needed here.
*/
#include <sys/prctl.h>
#include <errno.h>
#include <unistd.h>
#include <syscall.h>
#include <haproxy/api.h>
#include <haproxy/cfgparse.h>
#include <haproxy/errors.h>
#include <haproxy/global.h>
#include <haproxy/linuxcap.h>
#include <haproxy/tools.h>
struct __user_cap_header_struct cap_hdr_haproxy = {
.pid = 0, /* current process */
.version = _LINUX_CAPABILITY_VERSION_3,
};
/* supported names, zero-terminated */
static const struct {
int cap;
const char *name;
} known_caps[] = {
#ifdef CAP_NET_RAW
{ CAP_NET_RAW, "cap_net_raw" },
#endif
#ifdef CAP_NET_ADMIN
{ CAP_NET_ADMIN, "cap_net_admin" },
#endif
#ifdef CAP_NET_BIND_SERVICE
{ CAP_NET_BIND_SERVICE, "cap_net_bind_service" },
#endif
#ifdef CAP_SYS_ADMIN
{ CAP_SYS_ADMIN, "cap_sys_admin" },
#endif
/* must be last */
{ 0, 0 }
};
/* provided by sys/capability.h on some distros */
static inline int capset(cap_user_header_t hdrp, const cap_user_data_t datap)
{
return syscall(SYS_capset, hdrp, datap);
}
/* defaults to zero, i.e. we don't keep any cap after setuid() */
static uint32_t caplist;
/* try to check if CAP_NET_ADMIN, CAP_NET_RAW or CAP_SYS_ADMIN are in the
* process Effective set in the case when euid is non-root. If there is a
* match, LSTCHK_NETADM or LSTCHK_SYSADM is unset respectively from
* global.last_checks to avoid warning due to global.last_checks verifications
* later at the process init stage.
* If there is no any supported by haproxy capability in the process Effective
* set, try to check the process Permitted set. In this case we promote from
* Permitted set to Effective only the capabilities, that were marked by user
* via 'capset' keyword in the global section (caplist). If there is match with
* caplist and CAP_NET_ADMIN/CAP_NET_RAW or CAP_SYS_ADMIN are in this list,
* LSTCHK_NETADM or/and LSTCHK_SYSADM will be unset by the same reason.
* We do this only if the current euid is non-root and there is no global.uid.
* Otherwise, the process will continue either to run under root, or it will do
* a transition to unprivileged user later in prepare_caps_for_setuid(),
* which specially manages its capabilities in that case.
* Always returns 0. Diagnostic warnings will be emitted only, if
* LSTCHK_NETADM/LSTCHK_SYSADM is presented in global.last_checks and some
* failures are encountered.
*/
int prepare_caps_from_permitted_set(int from_uid, int to_uid)
{
/* _LINUX_CAPABILITY_U32S_1 = 1 and corresponds to version 1, which is three
* 32-bit integers set. So kernel in capset()/capget() will copy_from/to_user
* only _LINUX_CAPABILITY_U32S_1 * (sizeof(struct __user_cap_data_struct)),
* i.e. only the __user_cap_data_struct[0].
*/
struct __user_cap_data_struct start_cap_data[_LINUX_CAPABILITY_U32S_3] = { };
/* started as root */
if (!from_uid)
return 0;
/* will change ruid and euid later in set_identity() */
if (to_uid > 0)
return 0;
/* first, let's check if CAP_NET_ADMIN or CAP_NET_RAW is already in
* the process effective set. This may happen, when administrator sets
* these capabilities and the file effective bit on haproxy binary via
* setcap, see capabilities man page for details.
*/
if (capget(&cap_hdr_haproxy, start_cap_data) == -1) {
if (global.last_checks & (LSTCHK_NETADM | LSTCHK_SYSADM))
ha_diag_warning("Failed to get process capabilities using capget(): %s. "
"Can't use capabilities that might be set on %s binary "
"by administrator.\n", strerror(errno), progname);
return 0;
}
if (start_cap_data[0].effective & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW))) {
global.last_checks &= ~LSTCHK_NETADM;
return 0;
}
if (start_cap_data[0].effective & ((1 << CAP_SYS_ADMIN))) {
global.last_checks &= ~LSTCHK_SYSADM;
return 0;
}
/* second, try to check process permitted set, in this case caplist is
* necessary. Allows to put cap_net_bind_service in process effective
* set, if it is in the caplist and also presented in the binary
* permitted set.
*/
if (caplist && start_cap_data[0].permitted & caplist) {
start_cap_data[0].effective |= start_cap_data[0].permitted & caplist;
if (capset(&cap_hdr_haproxy, start_cap_data) == 0) {
if (caplist & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW)))
global.last_checks &= ~LSTCHK_NETADM;
if (caplist & (1 << CAP_SYS_ADMIN))
global.last_checks &= ~LSTCHK_SYSADM;
} else if (global.last_checks & (LSTCHK_NETADM|LSTCHK_SYSADM)) {
ha_diag_warning("Failed to put capabilities from caplist in %s "
"process Effective capabilities set using capset(): %s\n",
progname, strerror(errno));
}
}
return 0;
}
/* try to apply capabilities before switching UID from <from_uid> to <to_uid>.
* In practice we need to do this in 4 steps:
* - set PR_SET_KEEPCAPS to preserve caps across the final setuid()
* - set the effective and permitted caps ;
* - switch euid to non-zero
* - set the effective and permitted caps again
* - then the caller can safely call setuid()
* On success LSTCHK_NETADM is unset from global.last_checks, if CAP_NET_ADMIN
* or CAP_NET_RAW was found in the caplist from config. Same for
* LSTCHK_SYSADM, if CAP_SYS_ADMIN was found in the caplist from config.
* We don't do this if the current euid is not zero or if the target uid
* is zero. Returns 0 on success, negative on failure. Alerts may be emitted.
*/
int prepare_caps_for_setuid(int from_uid, int to_uid)
{
/* _LINUX_CAPABILITY_U32S_1 = 1 and corresponds to version 1, which is three
* 32-bit integers set. So kernel in capset()/capget() will copy_from/to_user
* only _LINUX_CAPABILITY_U32S_1 * (sizeof(struct __user_cap_data_struct)),
* i.e. only the __user_cap_data_struct[0].
*/
struct __user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3] = { };
if (from_uid != 0)
return 0;
if (to_uid <= 0)
return 0;
if (!caplist)
return 0;
if (prctl(PR_SET_KEEPCAPS, 1) == -1) {
ha_alert("Failed to preserve capabilities using prctl(): %s\n", strerror(errno));
return -1;
}
cap_data[0].effective = cap_data[0].permitted = caplist | (1 << CAP_SETUID);
if (capset(&cap_hdr_haproxy, cap_data) == -1) {
ha_alert("Failed to preset the capabilities to preserve using capset(): %s\n", strerror(errno));
return -1;
}
if (seteuid(to_uid) == -1) {
ha_alert("Failed to set effective uid to %d: %s\n", to_uid, strerror(errno));
return -1;
}
cap_data[0].effective = cap_data[0].permitted = caplist | (1 << CAP_SETUID);
if (capset(&cap_hdr_haproxy, cap_data) == -1) {
ha_alert("Failed to set the final capabilities using capset(): %s\n", strerror(errno));
return -1;
}
if (caplist & ((1 << CAP_NET_ADMIN)|(1 << CAP_NET_RAW)))
global.last_checks &= ~LSTCHK_NETADM;
if (caplist & (1 << CAP_SYS_ADMIN))
global.last_checks &= ~LSTCHK_SYSADM;
/* all's good */
return 0;
}
/* finalize the capabilities after setuid(). The most important is to drop the
* CAP_SET_SETUID capability, which would otherwise allow to switch back to any
* UID and recover everything.
*/
int finalize_caps_after_setuid(int from_uid, int to_uid)
{
struct __user_cap_data_struct cap_data[_LINUX_CAPABILITY_U32S_3] = { };
if (from_uid != 0)
return 0;
if (to_uid <= 0)
return 0;
if (!caplist)
return 0;
cap_data[0].effective = cap_data[0].permitted = caplist;
if (capset(&cap_hdr_haproxy, cap_data) == -1) {
ha_alert("Failed to drop the setuid capability using capset(): %s\n", strerror(errno));
return -1;
}
/* all's good */
return 0;
}
/* parse the "setcap" global keyword. Returns -1 on failure, 0 on success. */
static int cfg_parse_global_setcap(char **args, int section_type,
struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err)
{
char *name = args[1];
char *next;
uint32_t caps = 0;
int id;
if (!*name) {
memprintf(err, "'%s' : missing capability name(s). ", args[0]);
goto dump_caps;
}
while (name && *name) {
next = strchr(name, ',');
if (next)
*(next++) = '\0';
for (id = 0; known_caps[id].cap; id++) {
if (strcmp(name, known_caps[id].name) == 0) {
caps |= 1U << known_caps[id].cap;
break;
}
}
if (!known_caps[id].cap) {
memprintf(err, "'%s' : unsupported capability '%s'. ", args[0], args[1]);
goto dump_caps;
}
name = next;
}
caplist |= caps;
return 0;
dump_caps:
memprintf(err, "%s Supported ones are: ", *err);
for (id = 0; known_caps[id].cap; id++)
memprintf(err, "%s%s%s%s", *err,
id ? known_caps[id+1].cap ? ", " : " and " : "",
known_caps[id].name, known_caps[id+1].cap ? "" : ".");
return -1;
}
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_GLOBAL, "setcap", cfg_parse_global_setcap },
{ 0, NULL, NULL }
}};
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
|