/src/samba/third_party/heimdal/lib/roken/issuid.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 1998 - 2017 Kungliga Tekniska Högskolan |
3 | | * (Royal Institute of Technology, Stockholm, Sweden). |
4 | | * All rights reserved. |
5 | | * |
6 | | * Redistribution and use in source and binary forms, with or without |
7 | | * modification, are permitted provided that the following conditions |
8 | | * are met: |
9 | | * |
10 | | * 1. Redistributions of source code must retain the above copyright |
11 | | * notice, this list of conditions and the following disclaimer. |
12 | | * |
13 | | * 2. Redistributions in binary form must reproduce the above copyright |
14 | | * notice, this list of conditions and the following disclaimer in the |
15 | | * documentation and/or other materials provided with the distribution. |
16 | | * |
17 | | * 3. Neither the name of the Institute nor the names of its contributors |
18 | | * may be used to endorse or promote products derived from this software |
19 | | * without specific prior written permission. |
20 | | * |
21 | | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND |
22 | | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
23 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
24 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE |
25 | | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
26 | | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
27 | | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
28 | | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
29 | | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
30 | | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
31 | | * SUCH DAMAGE. |
32 | | */ |
33 | | |
34 | | #include <config.h> |
35 | | |
36 | | #ifdef HAVE_SYS_AUXV_H |
37 | | #include <sys/auxv.h> |
38 | | #endif |
39 | | |
40 | | #include <errno.h> |
41 | | |
42 | | #include "roken.h" |
43 | | #include "getauxval.h" |
44 | | |
45 | | extern int rk_injected_auxv; |
46 | | |
47 | | /** |
48 | | * Returns non-zero if the caller's process started as set-uid or |
49 | | * set-gid (and therefore the environment cannot be trusted). |
50 | | * |
51 | | * As much as possible this implements the same functionality and |
52 | | * semantics as OpenBSD's issetugid() (as opposed to FreeBSD's). |
53 | | * |
54 | | * Preserves errno. |
55 | | * |
56 | | * @return Non-zero if the environment is not trusted. |
57 | | */ |
58 | | ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL |
59 | | issuid(void) |
60 | 0 | { |
61 | | #ifdef WIN32 |
62 | | return 0; /* No set-id programs or anything like it on Windows */ |
63 | | #else |
64 | | /* |
65 | | * We want to use issetugid(), but issetugid() is not the same on |
66 | | * all OSes. |
67 | | * |
68 | | * On OpenBSD (where issetugid() originated), Illumos derivatives, |
69 | | * and Solaris, issetugid() returns true IFF the program exec()ed |
70 | | * was set-uid or set-gid. |
71 | | * |
72 | | * FreeBSD departed from OpenBSD's issetugid() semantics, and other |
73 | | * BSDs (NetBSD, DragonFly) and OS X adopted FreeBSD's. |
74 | | * |
75 | | * FreeBSDs' issetugid() returns true if the program exec()ed was |
76 | | * set-uid or set-gid, or if the process has switched UIDs/GIDs or |
77 | | * otherwise changed privileges or is a descendant of such a process |
78 | | * and has not exec()ed since. |
79 | | * |
80 | | * The FreeBSD/NetBSD issetugid() does us no good because we _want_ |
81 | | * to trust the environment when the process started life as |
82 | | * non-set-uid root (or otherwise privileged). There's nothing |
83 | | * about _dropping_ privileges (without having gained them first) |
84 | | * that taints the environment. It's not like calling system(), |
85 | | * say, might change the environment of the caller. |
86 | | * |
87 | | * We want OpenBSD's issetugid() semantics. |
88 | | * |
89 | | * Linux, meanwhile, has no issetugid() (at least glibc doesn't |
90 | | * anyways) but has an equivalent: getauxval(AT_SECURE). |
91 | | * |
92 | | * To be really specific: we want getauxval(AT_SECURE) semantics |
93 | | * because there may be ways in which a process might gain privilege |
94 | | * at exec time other than by exec'ing a set-id program. |
95 | | * |
96 | | * Where we use getauxval(), we really use our getauxval(), the one |
97 | | * that isn't broken the way glibc's used to be. Our getauxval() |
98 | | * also works on more systems than actually provide one. |
99 | | * |
100 | | * In order to avoid FreeBSD issetugid() semantics, where available, |
101 | | * we use the ELF auxilliary vector to implement OpenBSD semantics |
102 | | * before finally falling back on issetugid(). |
103 | | * |
104 | | * All of this is as of April 2017, and might become stale in the |
105 | | * future. |
106 | | */ |
107 | 0 | static int we_are_suid = -1; /* Memoize; -1 == dunno */ |
108 | 0 | int save_errno = errno; |
109 | 0 | #if defined(AT_EUID) && defined(AT_UID) && defined(AT_EGID) && defined(AT_GID) |
110 | 0 | int seen = 0; |
111 | 0 | #endif |
112 | |
|
113 | 0 | if (we_are_suid >= 0 && !rk_injected_auxv) |
114 | 0 | return we_are_suid; |
115 | | |
116 | 0 | #ifdef AT_SECURE |
117 | 0 | errno = 0; |
118 | 0 | if (rk_getauxval(AT_SECURE) != 0) { |
119 | 0 | errno = save_errno; |
120 | 0 | return we_are_suid = 1; |
121 | 0 | } else if (errno == 0) { |
122 | 0 | errno = save_errno; |
123 | 0 | return we_are_suid = 0; |
124 | 0 | } |
125 | | /* errno == ENOENT; AT_SECURE not found; fall through */ |
126 | 0 | #endif |
127 | | |
128 | 0 | #if defined(AT_EUID) && defined(AT_UID) && defined(AT_EGID) && defined(AT_GID) |
129 | 0 | { |
130 | 0 | unsigned long euid; |
131 | 0 | unsigned long uid; |
132 | |
|
133 | 0 | errno = 0; |
134 | 0 | euid = rk_getauxval(AT_EUID); |
135 | 0 | if (errno == 0) |
136 | 0 | seen |= 1; |
137 | 0 | errno = 0; |
138 | 0 | uid = rk_getauxval(AT_UID); |
139 | 0 | if (errno == 0) |
140 | 0 | seen |= 2; |
141 | 0 | if (euid != uid) { |
142 | 0 | errno = save_errno; |
143 | 0 | return we_are_suid = 1; |
144 | 0 | } |
145 | 0 | } |
146 | | /* Check GIDs */ |
147 | 0 | { |
148 | 0 | unsigned long egid; |
149 | 0 | unsigned long gid; |
150 | |
|
151 | 0 | errno = 0; |
152 | 0 | egid = rk_getauxval(AT_EGID); |
153 | 0 | if (errno == 0) |
154 | 0 | seen |= 4; |
155 | 0 | errno = 0; |
156 | 0 | gid = rk_getauxval(AT_GID); |
157 | 0 | if (errno == 0) |
158 | 0 | seen |= 8; |
159 | 0 | if (egid != gid) { |
160 | 0 | errno = save_errno; |
161 | 0 | return we_are_suid = 1; |
162 | 0 | } |
163 | 0 | } |
164 | 0 | errno = save_errno; |
165 | 0 | if (seen == 15) |
166 | 0 | return we_are_suid = 0; |
167 | 0 | #endif |
168 | | |
169 | | #if defined(HAVE_ISSETUGID) |
170 | | /* If issetugid() == 0 then we're definitely OK then */ |
171 | | if (issetugid() == 0) |
172 | | return we_are_suid = 0; |
173 | | /* issetugid() == 1 might have been a false positive; fall through */ |
174 | | #endif |
175 | | |
176 | 0 | #ifdef AT_EXECFN |
177 | | /* |
178 | | * There's an auxval by which to find the path of the program this |
179 | | * process exec'ed. |
180 | | * |
181 | | * We can stat() it. If the program did a chroot() and the chroot |
182 | | * has a program with the same path but not set-uid/set-gid, of |
183 | | * course, we lose here. But a) that's a bit of a stretch, b) |
184 | | * there's not much more we can do here. |
185 | | * |
186 | | * Also, this is technically a TOCTOU race, though for set-id |
187 | | * programs this is exceedingly unlikely to be an actual TOCTOU |
188 | | * race. |
189 | | * |
190 | | * TODO We should really make sure that none of the path components of the |
191 | | * execpath are symlinks. |
192 | | */ |
193 | 0 | { |
194 | 0 | unsigned long p = rk_getauxval(AT_EXECPATH); |
195 | 0 | struct stat st; |
196 | | |
197 | 0 | if (p != 0 && *(const char *)p == '/' && |
198 | 0 | stat((const char *)p, &st) == 0) { |
199 | 0 | if ((st.st_mode & S_ISUID) || (st.st_mode & S_ISGID)) { |
200 | 0 | errno = save_errno; |
201 | 0 | return we_are_suid = 1; |
202 | 0 | } |
203 | 0 | errno = save_errno; |
204 | 0 | return we_are_suid = 0; |
205 | 0 | } |
206 | 0 | } |
207 | | /* Fall through */ |
208 | 0 | #endif |
209 | | |
210 | | #if defined(HAVE_ISSETUGID) |
211 | | errno = save_errno; |
212 | | return we_are_suid = 1; |
213 | | #else |
214 | | /* |
215 | | * Paranoia: for extra safety we ought to default to returning 1. |
216 | | * |
217 | | * But who knows what that might break where users link statically |
218 | | * (so no auxv), say. |
219 | | * |
220 | | * We'll check the actual real and effective IDs (as opposed to the |
221 | | * ones at main() start time. |
222 | | * |
223 | | * For now we stick to returning zero by default. We've been rather |
224 | | * heroic above trying to find out if we're suid, and we're running |
225 | | * on a rather old or uncool OS if we've gotten here. |
226 | | */ |
227 | | |
228 | 0 | #if defined(HAVE_GETRESUID) |
229 | | /* |
230 | | * If r/e/suid are all the same then chances are very good we did |
231 | | * not start as set-uid. Though this could be a login program that |
232 | | * started out as privileged and is calling Heimdal "as the user". |
233 | | * |
234 | | * Again, such a program would have to be statically linked to get |
235 | | * here. |
236 | | */ |
237 | 0 | { |
238 | 0 | uid_t r, e, s; |
239 | 0 | if (getresuid(&r, &e, &s) == 0) { |
240 | 0 | if (r != e || r != s) { |
241 | 0 | errno = save_errno; |
242 | 0 | return we_are_suid = 1; |
243 | 0 | } |
244 | 0 | } |
245 | 0 | } |
246 | 0 | #endif |
247 | 0 | #if defined(HAVE_GETRESGID) |
248 | 0 | { |
249 | 0 | gid_t r, e, s; |
250 | 0 | if (getresgid(&r, &e, &s) == 0) { |
251 | 0 | if (r != e || r != s) { |
252 | 0 | errno = save_errno; |
253 | 0 | return we_are_suid = 1; |
254 | 0 | } |
255 | 0 | } |
256 | 0 | } |
257 | 0 | #endif |
258 | 0 | #if defined(HAVE_GETRESUID) && defined(HAVE_GETRESGID) |
259 | 0 | errno = save_errno; |
260 | 0 | return we_are_suid = 0; |
261 | |
|
262 | | #else /* avoid compiler warnings about dead code */ |
263 | | |
264 | | #if defined(HAVE_GETUID) && defined(HAVE_GETEUID) |
265 | | if (getuid() != geteuid()) |
266 | | return we_are_suid = 1; |
267 | | #endif |
268 | | #if defined(HAVE_GETGID) && defined(HAVE_GETEGID) |
269 | | if (getgid() != getegid()) |
270 | | return we_are_suid = 1; |
271 | | #endif |
272 | | |
273 | | errno = save_errno; |
274 | | return we_are_suid = 0; |
275 | | #endif /* !defined(HAVE_GETRESUID) || !defined(HAVE_GETRESGID) */ |
276 | 0 | #endif /* !defined(HAVE_ISSETUGID) */ |
277 | 0 | #endif /* WIN32 */ |
278 | 0 | } |