Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/sudoers/canon_path.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2023 Todd C. Miller <[email protected]>
5
*
6
* Permission to use, copy, modify, and distribute this software for any
7
* purpose with or without fee is hereby granted, provided that the above
8
* copyright notice and this permission notice appear in all copies.
9
*
10
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
*/
18
19
#include <config.h>
20
21
#include <sys/stat.h>
22
#include <stddef.h>
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
#include <unistd.h>
27
#include <errno.h>
28
29
#include <sudoers.h>
30
#include <redblack.h>
31
32
static struct rbtree *canon_cache;
33
34
/*
35
* A cache_item includes storage for both the original path and the
36
* resolved path. The resolved path is directly embedded into the
37
* struct so that we can find the start of the struct cache_item
38
* given the value of resolved. Storage for pathname is embedded
39
* at the end with resolved.
40
*/
41
struct cache_item {
42
unsigned int refcnt;
43
char *pathname;
44
char resolved[];
45
};
46
47
/*
48
* Compare function for canon_cache.
49
* v1 is the key to find or data to insert, v2 is in-tree data.
50
*/
51
static int
52
compare(const void *v1, const void *v2)
53
{
54
const struct cache_item *ci1 = (const struct cache_item *)v1;
55
const struct cache_item *ci2 = (const struct cache_item *)v2;
56
return strcmp(ci1->pathname, ci2->pathname);
57
}
58
59
/* Convert a pointer returned by canon_path() to a struct cache_item *. */
60
#define resolved_to_item(_r) ((struct cache_item *)((_r) - offsetof(struct cache_item, resolved)))
61
62
/*
63
* Delete a ref from item and free if the refcount reaches 0.
64
*/
65
static void
66
canon_path_free_item(void *v)
67
{
68
struct cache_item *item = v;
69
debug_decl(canon_path_free_item, SUDOERS_DEBUG_UTIL);
70
71
if (--item->refcnt == 0)
72
free(item);
73
74
debug_return;
75
}
76
77
/*
78
* Delete a ref from the item containing "resolved" and free if
79
* the refcount reaches 0.
80
*/
81
void
82
canon_path_free(char *resolved)
83
{
84
debug_decl(canon_path_free, SUDOERS_DEBUG_UTIL);
85
if (resolved != NULL)
86
canon_path_free_item(resolved_to_item(resolved));
87
debug_return;
88
}
89
90
/*
91
* Free canon_cache.
92
* This only removes the reference for that the cache owns.
93
* Other references remain valid until canon_path_free() is called.
94
*/
95
void
96
canon_path_free_cache(void)
97
{
98
debug_decl(canon_path_free_cache, SUDOERS_DEBUG_UTIL);
99
100
if (canon_cache != NULL) {
101
rbdestroy(canon_cache, canon_path_free_item);
102
canon_cache = NULL;
103
}
104
105
debug_return;
106
}
107
108
/*
109
* Like realpath(3) but caches the result. Returns an entry from the
110
* cache on success (with an added reference) or NULL on failure.
111
*/
112
char *
113
canon_path(const char *inpath)
114
{
115
size_t item_size, inlen, reslen = 0;
116
char *resolved, resbuf[PATH_MAX];
117
struct cache_item key, *item;
118
struct rbnode *node = NULL;
119
debug_decl(canon_path, SUDOERS_DEBUG_UTIL);
120
121
if (canon_cache == NULL) {
122
canon_cache = rbcreate(compare);
123
if (canon_cache == NULL) {
124
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
125
debug_return_str(NULL);
126
}
127
} else {
128
/* Check cache. */
129
key.pathname = (char *)inpath;
130
if ((node = rbfind(canon_cache, &key)) != NULL) {
131
item = node->data;
132
goto done;
133
}
134
}
135
136
/*
137
* Not cached, call realpath(3).
138
* Older realpath() doesn't support passing a NULL buffer.
139
* We special-case the empty string to resolve to "/".
140
* XXX - warn on errors other than ENOENT?
141
*/
142
if (*inpath == '\0')
143
resolved = (char *)"/";
144
else
145
resolved = realpath(inpath, resbuf);
146
147
inlen = strlen(inpath);
148
/* one for NULL terminator of resolved, one for NULL terminator of pathname */
149
item_size = sizeof(*item) + inlen + 2;
150
if (resolved != NULL) {
151
reslen = strlen(resolved);
152
item_size += reslen;
153
}
154
item = malloc(item_size);
155
if (item == NULL) {
156
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
157
debug_return_str(NULL);
158
}
159
if (resolved != NULL)
160
memcpy(item->resolved, resolved, reslen);
161
item->resolved[reslen] = '\0';
162
item->pathname = item->resolved + reslen + 1;
163
memcpy(item->pathname, inpath, inlen);
164
item->pathname[inlen] = '\0';
165
item->refcnt = 1;
166
switch (rbinsert(canon_cache, item, NULL)) {
167
case 1:
168
/* should not happen */
169
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
170
"path \"%s\" already exists in the cache", inpath);
171
item->refcnt = 0;
172
break;
173
case -1:
174
/* can't cache item, just return it */
175
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
176
"can't cache path \"%s\"", inpath);
177
item->refcnt = 0;
178
break;
179
}
180
done:
181
if (item->refcnt != 0) {
182
sudo_debug_printf(SUDO_DEBUG_DEBUG,
183
"%s: path %s -> %s (%s)", __func__, inpath,
184
item->resolved[0] ? item->resolved : "NULL",
185
node ? "cache hit" : "cached");
186
}
187
if (item->resolved[0] == '\0') {
188
/* negative result, free item if not cached */
189
if (item->refcnt == 0)
190
free(item);
191
debug_return_str(NULL);
192
}
193
item->refcnt++;
194
debug_return_str(item->resolved);
195
}
196
197