Skip to content

Commit 46afc66

Browse files
libselinux: stringrep: serialize access to the discover_class_cache
The discover_class_cache list is accessed without locking by the string_to_*() and *_to_string() functions in libselinux for translating between string names and class/perm values, and returns pointers to strings within this list to callers. selinux_flush_class_cache() frees the list upon a policy reload, called by avc_process_policyload() from selinux_status_updated() and avc_netlink_process(). Make this safe for multi-threaded users by introducing and taking a mutex around the list accesses, and by moving flushed nodes to a retired list rather than freeing them so that returned string pointers remain valid. Given the infrequency of policy reloads, the relative stability of the class/perm mapping even across policy reloads, and the small amount of memory required, this seems a worthwhile tradeoff. If it becomes an issue, there are several options: 1. Only load/refresh new/modified class/permission names and values upon policy reload rather than flushing and reloading them all, 2. Provide a way to drain the retired list safely when the application knows it is no longer using any returned strings. Fixes: SELinuxProject#287 SELinuxProject#335 SELinuxProject#336 Link: https://lore.kernel.org/selinux/CAJsHiNx1E7x1jaBkS0i4L1nBWXp8YXLHRWcCaDaH4LOn=zm+Zw@mail.gmail.com/ Link: https://lore.kernel.org/selinux/20220120073329.15234-1-purushottamchoudhary29@gmail.com/ Reported-by: Seth Moore <sethmo@google.com> Reported-by: Purushottam Choudhary <purushottamchoudhary29@gmail.com> Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
1 parent 6d855c0 commit 46afc66

1 file changed

Lines changed: 87 additions & 33 deletions

File tree

libselinux/src/stringrep.c

Lines changed: 87 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ struct discover_class_node {
2727
struct discover_class_node *next;
2828
};
2929

30+
static pthread_mutex_t discover_class_lock = PTHREAD_MUTEX_INITIALIZER;
3031
static struct discover_class_node *discover_class_cache = NULL;
32+
static struct discover_class_node *discover_class_retired = NULL;
3133

3234
static struct discover_class_node *get_class_cache_entry_name(const char *s)
3335
{
@@ -156,9 +158,6 @@ static struct discover_class_node *discover_class(const char *s)
156158
}
157159
closedir(dir);
158160

159-
node->next = discover_class_cache;
160-
discover_class_cache = node;
161-
162161
return node;
163162

164163
err4:
@@ -176,41 +175,84 @@ static struct discover_class_node *discover_class(const char *s)
176175

177176
void selinux_flush_class_cache(void)
178177
{
179-
struct discover_class_node *cur = discover_class_cache, *prev = NULL;
180-
size_t i;
181-
182-
while (cur != NULL) {
183-
free(cur->name);
184-
185-
for (i = 0; i < MAXVECTORS; i++)
186-
free(cur->perms[i]);
187-
188-
free(cur->perms);
178+
struct discover_class_node *head, *tail;
189179

190-
prev = cur;
191-
cur = cur->next;
180+
__pthread_mutex_lock(&discover_class_lock);
181+
head = discover_class_cache;
182+
discover_class_cache = NULL;
192183

193-
free(prev);
184+
/*
185+
* Move old list to the retired chain rather than
186+
* freeing it; other threads may hold pointers to
187+
* class/perm strings within it returned by
188+
* security_class_to_string() and
189+
* security_av_perm_to_string(). This incurs a small
190+
* memory cost but given the infrequency of policy
191+
* reloads, it should never be significant. If
192+
* required, a new interface could be introduced to
193+
* allow the application to safely drain the retired
194+
* list when it knows it is safe to do so.
195+
*/
196+
if (head) {
197+
tail = head;
198+
while (tail->next)
199+
tail = tail->next;
200+
tail->next = discover_class_retired;
201+
discover_class_retired = head;
194202
}
195-
196-
discover_class_cache = NULL;
203+
__pthread_mutex_unlock(&discover_class_lock);
197204
}
198205

199206
security_class_t string_to_security_class(const char *s)
200207
{
201-
struct discover_class_node *node;
208+
struct discover_class_node *node, *retry;
209+
security_class_t value;
210+
size_t i;
202211

212+
__pthread_mutex_lock(&discover_class_lock);
203213
node = get_class_cache_entry_name(s);
204-
if (node == NULL) {
205-
node = discover_class(s);
214+
if (node) {
215+
value = node->value;
216+
__pthread_mutex_unlock(&discover_class_lock);
217+
return map_class(value);
218+
}
219+
__pthread_mutex_unlock(&discover_class_lock);
206220

207-
if (node == NULL) {
208-
errno = EINVAL;
209-
return 0;
210-
}
221+
/* Discover outside of lock; reads selinuxfs */
222+
node = discover_class(s);
223+
if (node == NULL) {
224+
errno = EINVAL;
225+
return 0;
211226
}
227+
value = node->value;
228+
229+
/*
230+
* Retry cache lookup with lock held in case
231+
* another thread raced with us. If we don't
232+
* find it, add the new one; else use
233+
* the old one and free the new one.
234+
*/
235+
__pthread_mutex_lock(&discover_class_lock);
236+
retry = get_class_cache_entry_name(s);
237+
if (!retry) {
238+
node->next = discover_class_cache;
239+
discover_class_cache = node;
240+
} else {
241+
value = retry->value;
242+
/*
243+
* We can only safely free node here
244+
* because it wasn't yet added to the cache.
245+
*/
246+
free(node->name);
247+
for (i = 0; i < MAXVECTORS; i++)
248+
free(node->perms[i]);
249+
free(node->perms);
250+
free(node);
251+
node = NULL;
252+
}
253+
__pthread_mutex_unlock(&discover_class_lock);
212254

213-
return map_class(node->value);
255+
return map_class(value);
214256
}
215257

216258
security_class_t mode_to_security_class(mode_t m)
@@ -239,13 +281,17 @@ access_vector_t string_to_av_perm(security_class_t tclass, const char *s)
239281
struct discover_class_node *node;
240282
security_class_t kclass = unmap_class(tclass);
241283

284+
__pthread_mutex_lock(&discover_class_lock);
242285
node = get_class_cache_entry_value(kclass);
243286
if (node != NULL) {
244287
size_t i;
245288
for (i = 0; i < MAXVECTORS && node->perms[i] != NULL; i++)
246-
if (strcmp(node->perms[i], s) == 0)
289+
if (strcmp(node->perms[i], s) == 0) {
290+
__pthread_mutex_unlock(&discover_class_lock);
247291
return map_perm(tclass, UINT32_C(1) << i);
292+
}
248293
}
294+
__pthread_mutex_unlock(&discover_class_lock);
249295

250296
errno = EINVAL;
251297
return 0;
@@ -254,31 +300,39 @@ access_vector_t string_to_av_perm(security_class_t tclass, const char *s)
254300
const char *security_class_to_string(security_class_t tclass)
255301
{
256302
struct discover_class_node *node;
303+
const char *name;
257304

258305
tclass = unmap_class(tclass);
259306

307+
__pthread_mutex_lock(&discover_class_lock);
260308
node = get_class_cache_entry_value(tclass);
261-
if (node == NULL)
262-
return NULL;
263-
else
264-
return node->name;
309+
name = node ? node->name : NULL;
310+
__pthread_mutex_unlock(&discover_class_lock);
311+
312+
return name;
265313
}
266314

267315
const char *security_av_perm_to_string(security_class_t tclass,
268316
access_vector_t av)
269317
{
270318
struct discover_class_node *node;
271319
size_t i;
320+
const char *name;
272321

273322
av = unmap_perm(tclass, av);
274323
tclass = unmap_class(tclass);
275324

325+
__pthread_mutex_lock(&discover_class_lock);
276326
node = get_class_cache_entry_value(tclass);
277327
if (av && node)
278328
for (i = 0; i < MAXVECTORS; i++)
279-
if ((UINT32_C(1) << i) & av)
280-
return node->perms[i];
329+
if ((UINT32_C(1) << i) & av) {
330+
name = node->perms[i];
331+
__pthread_mutex_unlock(&discover_class_lock);
332+
return name;
333+
}
281334

335+
__pthread_mutex_unlock(&discover_class_lock);
282336
return NULL;
283337
}
284338

0 commit comments

Comments
 (0)