summaryrefslogtreecommitdiff
path: root/third_party/heimdal/lib/roken/issuid.c
blob: 0c503a0d6b2475fe389efe805511a73aacf6596f (plain)
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
/*
 * Copyright (c) 1998 - 2017 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <config.h>

#ifdef HAVE_SYS_AUXV_H
#include <sys/auxv.h>
#endif

#include <errno.h>

#include "roken.h"
#include "getauxval.h"

extern int rk_injected_auxv;

/**
 * Returns non-zero if the caller's process started as set-uid or
 * set-gid (and therefore the environment cannot be trusted).
 *
 * As much as possible this implements the same functionality and
 * semantics as OpenBSD's issetugid() (as opposed to FreeBSD's).
 *
 * Preserves errno.
 *
 * @return Non-zero if the environment is not trusted.
 */
ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL
issuid(void)
{
#ifdef WIN32
    return 0; /* No set-id programs or anything like it on Windows */
#else
    /*
     * We want to use issetugid(), but issetugid() is not the same on
     * all OSes.
     *
     * On OpenBSD (where issetugid() originated), Illumos derivatives,
     * and Solaris, issetugid() returns true IFF the program exec()ed
     * was set-uid or set-gid.
     *
     * FreeBSD departed from OpenBSD's issetugid() semantics, and other
     * BSDs (NetBSD, DragonFly) and OS X adopted FreeBSD's.
     *
     * FreeBSDs' issetugid() returns true if the program exec()ed was
     * set-uid or set-gid, or if the process has switched UIDs/GIDs or
     * otherwise changed privileges or is a descendant of such a process
     * and has not exec()ed since.
     *
     * The FreeBSD/NetBSD issetugid() does us no good because we _want_
     * to trust the environment when the process started life as
     * non-set-uid root (or otherwise privileged).  There's nothing
     * about _dropping_ privileges (without having gained them first)
     * that taints the environment.  It's not like calling system(),
     * say, might change the environment of the caller.
     *
     * We want OpenBSD's issetugid() semantics.
     *
     * Linux, meanwhile, has no issetugid() (at least glibc doesn't
     * anyways) but has an equivalent: getauxval(AT_SECURE).
     *
     * To be really specific: we want getauxval(AT_SECURE) semantics
     * because there may be ways in which a process might gain privilege
     * at exec time other than by exec'ing a set-id program.
     *
     * Where we use getauxval(), we really use our getauxval(), the one
     * that isn't broken the way glibc's used to be.  Our getauxval()
     * also works on more systems than actually provide one.
     *
     * In order to avoid FreeBSD issetugid() semantics, where available,
     * we use the ELF auxilliary vector to implement OpenBSD semantics
     * before finally falling back on issetugid().
     *
     * All of this is as of April 2017, and might become stale in the
     * future.
     */
    static int we_are_suid = -1; /* Memoize; -1 == dunno */
    int save_errno = errno;
#if defined(AT_EUID) && defined(AT_UID) && defined(AT_EGID) && defined(AT_GID)
    int seen = 0;
#endif

    if (we_are_suid >= 0 && !rk_injected_auxv)
        return we_are_suid;

#ifdef AT_SECURE
    errno = 0;
    if (rk_getauxval(AT_SECURE) != 0) {
        errno = save_errno;
        return we_are_suid = 1;
    } else if (errno == 0) {
        errno = save_errno;
        return we_are_suid = 0;
    }
    /* errno == ENOENT; AT_SECURE not found; fall through */
#endif

#if defined(AT_EUID) && defined(AT_UID) && defined(AT_EGID) && defined(AT_GID)
    {
        unsigned long euid;
        unsigned long uid;

        errno = 0;
        euid = rk_getauxval(AT_EUID);
        if (errno == 0)
            seen |= 1;
        errno = 0;
        uid = rk_getauxval(AT_UID);
        if (errno == 0)
            seen |= 2;
        if (euid != uid) {
            errno = save_errno;
            return we_are_suid = 1;
        }
    }
    /* Check GIDs */
    {
        unsigned long egid;
        unsigned long gid;

        errno = 0;
        egid = rk_getauxval(AT_EGID);
        if (errno == 0)
            seen |= 4;
        errno = 0;
        gid = rk_getauxval(AT_GID);
        if (errno == 0)
            seen |= 8;
        if (egid != gid) {
            errno = save_errno;
            return we_are_suid = 1;
        }
    }
    errno = save_errno;
    if (seen == 15)
        return we_are_suid = 0;
#endif

#if defined(HAVE_ISSETUGID)
    /* If issetugid() == 0 then we're definitely OK then */
    if (issetugid() == 0)
        return we_are_suid = 0;
    /* issetugid() == 1 might have been a false positive; fall through */
#endif /* USE_RK_GETAUXVAL */

#ifdef AT_EXECFN
    /*
     * There's an auxval by which to find the path of the program this
     * process exec'ed.
     *
     * We can stat() it.  If the program did a chroot() and the chroot
     * has a program with the same path but not set-uid/set-gid, of
     * course, we lose here.  But a) that's a bit of a stretch, b)
     * there's not much more we can do here.
     *
     * Also, this is technically a TOCTOU race, though for set-id
     * programs this is exceedingly unlikely to be an actual TOCTOU
     * race.
     */
    {
        unsigned long p = rk_getauxval(AT_EXECPATH);
        struct stat st;
        
        if (p != 0 && *(const char *)p == '/' &&
            stat((const char *)p, &st) == 0) {
            if ((st.st_mode & S_ISUID) || (st.st_mode & S_ISGID)) {
                errno = save_errno;
                return we_are_suid = 1;
            }
            errno = save_errno;
            return we_are_suid = 0;
        }
    }
    /* Fall through */
#endif

#if defined(HAVE_ISSETUGID)
    errno = save_errno;
    return we_are_suid = 1;
#else
    /*
     * Paranoia: for extra safety we ought to default to returning 1.
     *
     * But who knows what that might break where users link statically
     * (so no auxv), say.
     *
     * We'll check the actual real and effective IDs (as opposed to the
     * ones at main() start time.
     *
     * For now we stick to returning zero by default.  We've been rather
     * heroic above trying to find out if we're suid, and we're running
     * on a rather old or uncool OS if we've gotten here.
     */

#if defined(HAVE_GETRESUID)
    /*
     * If r/e/suid are all the same then chances are very good we did
     * not start as set-uid.  Though this could be a login program that
     * started out as privileged and is calling Heimdal "as the user".
     *
     * Again, such a program would have to be statically linked to get
     * here.
     */
    {
        uid_t r, e, s;
        if (getresuid(&r, &e, &s) == 0) {
            if (r != e || r != s) {
                errno = save_errno;
                return we_are_suid = 1;
            }
        }
    }
#endif
#if defined(HAVE_GETRESGID)
    {
        gid_t r, e, s;
        if (getresgid(&r, &e, &s) == 0) {
            if (r != e || r != s) {
                errno = save_errno;
                return we_are_suid = 1;
            }
        }
    }
#endif
#if defined(HAVE_GETRESUID) && defined(HAVE_GETRESGID)
    errno = save_errno;
    return we_are_suid = 0;

#else /* avoid compiler warnings about dead code */

#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
    if (getuid() != geteuid())
	return we_are_suid = 1;
#endif
#if defined(HAVE_GETGID) && defined(HAVE_GETEGID)
    if (getgid() != getegid())
	return we_are_suid = 1;
#endif

#endif /* !defined(HAVE_GETRESUID) || !defined(HAVE_GETRESGID) */

    errno = save_errno;
    return we_are_suid = 0;
#endif /* !defined(HAVE_ISSETUGID) */
#endif /* WIN32 */
}