blob: 919086c0a93e9df92476c2d223d47c1d5a614f8e [file] [log] [blame]
Willy Tarreaua547a212023-08-29 10:24:26 +02001/*
2 * Minimal handling of Linux kernel capabilities
3 *
4 * Copyright 2000-2023 Willy Tarreau <w@1wt.eu>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
13/* Depending on distros, some have capset(), others use the more complicated
14 * libcap. Let's stick to what we need and the kernel documents (capset).
15 * Note that prctl is needed here.
16 */
17#include <linux/capability.h>
18#include <sys/prctl.h>
19#include <errno.h>
20#include <unistd.h>
21#include <syscall.h>
22
23#include <haproxy/api.h>
24#include <haproxy/cfgparse.h>
25#include <haproxy/errors.h>
26#include <haproxy/tools.h>
27
28/* supported names, zero-terminated */
29static const struct {
30 int cap;
31 const char *name;
32} known_caps[] = {
33#ifdef CAP_NET_RAW
34 { CAP_NET_RAW, "cap_net_raw" },
35#endif
36#ifdef CAP_NET_ADMIN
37 { CAP_NET_ADMIN, "cap_net_admin" },
38#endif
39#ifdef CAP_NET_BIND_SERVICE
40 { CAP_NET_BIND_SERVICE, "cap_net_bind_service" },
41#endif
42 /* must be last */
43 { 0, 0 }
44};
45
46/* provided by sys/capability.h on some distros */
47static inline int capset(cap_user_header_t hdrp, const cap_user_data_t datap)
48{
49 return syscall(SYS_capset, hdrp, datap);
50}
51
52/* defaults to zero, i.e. we don't keep any cap after setuid() */
53static uint32_t caplist;
54
55/* try to apply capabilities before switching UID from <from_uid> to <to_uid>.
56 * In practice we need to do this in 4 steps:
57 * - set PR_SET_KEEPCAPS to preserve caps across the final setuid()
58 * - set the effective and permitted caps ;
59 * - switch euid to non-zero
60 * - set the effective and permitted caps again
61 * - then the caller can safely call setuid()
62 * We don't do this if the current euid is not zero or if the target uid
63 * is zero. Returns >=0 on success, negative on failure. Alerts or warnings
64 * may be emitted.
65 */
66int prepare_caps_for_setuid(int from_uid, int to_uid)
67{
68 struct __user_cap_data_struct cap_data = { };
69 struct __user_cap_header_struct cap_hdr = {
70 .pid = 0, /* current process */
71 .version = _LINUX_CAPABILITY_VERSION_1,
72 };
73
74 if (from_uid != 0)
75 return 0;
76
77 if (!to_uid)
78 return 0;
79
80 if (!caplist)
81 return 0;
82
83 if (prctl(PR_SET_KEEPCAPS, 1) == -1) {
84 ha_alert("Failed to preserve capabilities using prctl(): %s\n", strerror(errno));
85 return -1;
86 }
87
88 cap_data.effective = cap_data.permitted = caplist | (1 << CAP_SETUID);
89 if (capset(&cap_hdr, &cap_data) == -1) {
90 ha_alert("Failed to preset the capabilities to preserve using capset(): %s\n", strerror(errno));
91 return -1;
92 }
93
94 if (seteuid(to_uid) == -1) {
95 ha_alert("Failed to set effective uid to %d: %s\n", to_uid, strerror(errno));
96 return -1;
97 }
98
99 cap_data.effective = cap_data.permitted = caplist | (1 << CAP_SETUID);
100 if (capset(&cap_hdr, &cap_data) == -1) {
101 ha_alert("Failed to set the final capabilities using capset(): %s\n", strerror(errno));
102 return -1;
103 }
104 /* all's good */
105 return 0;
106}
107
108/* finalize the capabilities after setuid(). The most important is to drop the
109 * CAP_SET_SETUID capability, which would otherwise allow to switch back to any
110 * UID and recover everything.
111 */
112int finalize_caps_after_setuid(int from_uid, int to_uid)
113{
114 struct __user_cap_data_struct cap_data = { };
115 struct __user_cap_header_struct cap_hdr = {
116 .pid = 0, /* current process */
117 .version = _LINUX_CAPABILITY_VERSION_1,
118 };
119
120 if (from_uid != 0)
121 return 0;
122
123 if (!to_uid)
124 return 0;
125
126 if (!caplist)
127 return 0;
128
129 cap_data.effective = cap_data.permitted = caplist;
130 if (capset(&cap_hdr, &cap_data) == -1) {
131 ha_alert("Failed to drop the setuid capability using capset(): %s\n", strerror(errno));
132 return -1;
133 }
134 /* all's good */
135 return 0;
136}
137
138/* parse the "setcap" global keyword. Returns -1 on failure, 0 on success. */
139static int cfg_parse_global_setcap(char **args, int section_type,
140 struct proxy *curpx, const struct proxy *defpx,
141 const char *file, int line, char **err)
142{
143 char *name = args[1];
144 char *next;
145 uint32_t caps = 0;
146 int id;
147
148 if (!*name) {
149 memprintf(err, "'%s' : missing capability name(s). ", args[0]);
150 goto dump_caps;
151 }
152
153 while (name && *name) {
154 next = strchr(name, ',');
155 if (next)
156 *(next++) = '\0';
157
158 for (id = 0; known_caps[id].cap; id++) {
159 if (strcmp(name, known_caps[id].name) == 0) {
160 caps |= 1U << known_caps[id].cap;
161 break;
162 }
163 }
164
165 if (!known_caps[id].cap) {
166 memprintf(err, "'%s' : unsupported capability '%s'. ", args[0], args[1]);
167 goto dump_caps;
168 }
169 name = next;
170 }
171
172 caplist |= caps;
173 return 0;
174
175
176 dump_caps:
177 memprintf(err, "%s Supported ones are: ", *err);
178
179 for (id = 0; known_caps[id].cap; id++)
180 memprintf(err, "%s%s%s%s", *err,
181 id ? known_caps[id+1].cap ? ", " : " and " : "",
182 known_caps[id].name, known_caps[id+1].cap ? "" : ".");
183 return -1;
184}
185
186static struct cfg_kw_list cfg_kws = {ILH, {
187 { CFG_GLOBAL, "setcap", cfg_parse_global_setcap },
188 { 0, NULL, NULL }
189}};
190
191INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);