matchpathcon.c revision 569ce5498553b87dc7af343b2efb4da8d3ecdb4f
1#include <sys/stat.h> 2#include <string.h> 3#include <errno.h> 4#include <stdio.h> 5#include "selinux_internal.h" 6#include "label_internal.h" 7#include "callbacks.h" 8 9static __thread struct selabel_handle *hnd; 10 11/* 12 * An array for mapping integers to contexts 13 */ 14static __thread char **con_array; 15static __thread int con_array_size; 16static __thread int con_array_used; 17 18static pthread_once_t once = PTHREAD_ONCE_INIT; 19static pthread_key_t destructor_key; 20 21static int add_array_elt(char *con) 22{ 23 if (con_array_size) { 24 while (con_array_used >= con_array_size) { 25 con_array_size *= 2; 26 con_array = (char **)realloc(con_array, sizeof(char*) * 27 con_array_size); 28 if (!con_array) { 29 con_array_size = con_array_used = 0; 30 return -1; 31 } 32 } 33 } else { 34 con_array_size = 1000; 35 con_array = (char **)malloc(sizeof(char*) * con_array_size); 36 if (!con_array) { 37 con_array_size = con_array_used = 0; 38 return -1; 39 } 40 } 41 42 con_array[con_array_used] = strdup(con); 43 if (!con_array[con_array_used]) 44 return -1; 45 return con_array_used++; 46} 47 48static void free_array_elts(void) 49{ 50 con_array_size = con_array_used = 0; 51 free(con_array); 52 con_array = NULL; 53} 54 55static void 56#ifdef __GNUC__ 57 __attribute__ ((format(printf, 1, 2))) 58#endif 59 default_printf(const char *fmt, ...) 60{ 61 va_list ap; 62 va_start(ap, fmt); 63 vfprintf(stderr, fmt, ap); 64 va_end(ap); 65} 66 67void 68#ifdef __GNUC__ 69 __attribute__ ((format(printf, 1, 2))) 70#endif 71 (*myprintf) (const char *fmt,...) = &default_printf; 72int myprintf_compat = 0; 73 74void set_matchpathcon_printf(void (*f) (const char *fmt, ...)) 75{ 76 myprintf = f ? f : &default_printf; 77 myprintf_compat = 1; 78} 79 80static int (*myinvalidcon) (const char *p, unsigned l, char *c) = NULL; 81 82void set_matchpathcon_invalidcon(int (*f) (const char *p, unsigned l, char *c)) 83{ 84 myinvalidcon = f; 85} 86 87static int default_canoncon(const char *path, unsigned lineno, char **context) 88{ 89 char *tmpcon; 90 if (security_canonicalize_context_raw(*context, &tmpcon) < 0) { 91 if (errno == ENOENT) 92 return 0; 93 if (lineno) 94 myprintf("%s: line %u has invalid context %s\n", path, 95 lineno, *context); 96 else 97 myprintf("%s: invalid context %s\n", path, *context); 98 return 1; 99 } 100 free(*context); 101 *context = tmpcon; 102 return 0; 103} 104 105static int (*mycanoncon) (const char *p, unsigned l, char **c) = 106 NULL; 107 108void set_matchpathcon_canoncon(int (*f) (const char *p, unsigned l, char **c)) 109{ 110 if (f) 111 mycanoncon = f; 112 else 113 mycanoncon = &default_canoncon; 114} 115 116static __thread struct selinux_opt options[SELABEL_NOPT]; 117static __thread int notrans; 118 119void set_matchpathcon_flags(unsigned int flags) 120{ 121 int i; 122 memset(options, 0, sizeof(options)); 123 i = SELABEL_OPT_BASEONLY; 124 options[i].type = i; 125 options[i].value = (flags & MATCHPATHCON_BASEONLY) ? (char*)1 : NULL; 126 i = SELABEL_OPT_VALIDATE; 127 options[i].type = i; 128 options[i].value = (flags & MATCHPATHCON_VALIDATE) ? (char*)1 : NULL; 129 notrans = flags & MATCHPATHCON_NOTRANS; 130} 131 132/* 133 * An association between an inode and a 134 * specification. 135 */ 136typedef struct file_spec { 137 ino_t ino; /* inode number */ 138 int specind; /* index of specification in spec */ 139 char *file; /* full pathname for diagnostic messages about conflicts */ 140 struct file_spec *next; /* next association in hash bucket chain */ 141} file_spec_t; 142 143/* 144 * The hash table of associations, hashed by inode number. 145 * Chaining is used for collisions, with elements ordered 146 * by inode number in each bucket. Each hash bucket has a dummy 147 * header. 148 */ 149#define HASH_BITS 16 150#define HASH_BUCKETS (1 << HASH_BITS) 151#define HASH_MASK (HASH_BUCKETS-1) 152static file_spec_t *fl_head; 153 154/* 155 * Try to add an association between an inode and 156 * a specification. If there is already an association 157 * for the inode and it conflicts with this specification, 158 * then use the specification that occurs later in the 159 * specification array. 160 */ 161int matchpathcon_filespec_add(ino_t ino, int specind, const char *file) 162{ 163 file_spec_t *prevfl, *fl; 164 int h, ret; 165 struct stat sb; 166 167 if (!fl_head) { 168 fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS); 169 if (!fl_head) 170 goto oom; 171 memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS); 172 } 173 174 h = (ino + (ino >> HASH_BITS)) & HASH_MASK; 175 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl; 176 prevfl = fl, fl = fl->next) { 177 if (ino == fl->ino) { 178 ret = lstat(fl->file, &sb); 179 if (ret < 0 || sb.st_ino != ino) { 180 fl->specind = specind; 181 free(fl->file); 182 fl->file = malloc(strlen(file) + 1); 183 if (!fl->file) 184 goto oom; 185 strcpy(fl->file, file); 186 return fl->specind; 187 188 } 189 190 if (!strcmp(con_array[fl->specind], 191 con_array[specind])) 192 return fl->specind; 193 194 myprintf 195 ("%s: conflicting specifications for %s and %s, using %s.\n", 196 __FUNCTION__, file, fl->file, 197 con_array[fl->specind]); 198 free(fl->file); 199 fl->file = malloc(strlen(file) + 1); 200 if (!fl->file) 201 goto oom; 202 strcpy(fl->file, file); 203 return fl->specind; 204 } 205 206 if (ino > fl->ino) 207 break; 208 } 209 210 fl = malloc(sizeof(file_spec_t)); 211 if (!fl) 212 goto oom; 213 fl->ino = ino; 214 fl->specind = specind; 215 fl->file = malloc(strlen(file) + 1); 216 if (!fl->file) 217 goto oom_freefl; 218 strcpy(fl->file, file); 219 fl->next = prevfl->next; 220 prevfl->next = fl; 221 return fl->specind; 222 oom_freefl: 223 free(fl); 224 oom: 225 myprintf("%s: insufficient memory for file label entry for %s\n", 226 __FUNCTION__, file); 227 return -1; 228} 229 230/* 231 * Evaluate the association hash table distribution. 232 */ 233void matchpathcon_filespec_eval(void) 234{ 235 file_spec_t *fl; 236 int h, used, nel, len, longest; 237 238 if (!fl_head) 239 return; 240 241 used = 0; 242 longest = 0; 243 nel = 0; 244 for (h = 0; h < HASH_BUCKETS; h++) { 245 len = 0; 246 for (fl = fl_head[h].next; fl; fl = fl->next) { 247 len++; 248 } 249 if (len) 250 used++; 251 if (len > longest) 252 longest = len; 253 nel += len; 254 } 255 256 myprintf 257 ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n", 258 __FUNCTION__, nel, used, HASH_BUCKETS, longest); 259} 260 261/* 262 * Destroy the association hash table. 263 */ 264void matchpathcon_filespec_destroy(void) 265{ 266 file_spec_t *fl, *tmp; 267 int h; 268 269 free_array_elts(); 270 271 if (!fl_head) 272 return; 273 274 for (h = 0; h < HASH_BUCKETS; h++) { 275 fl = fl_head[h].next; 276 while (fl) { 277 tmp = fl; 278 fl = fl->next; 279 free(tmp->file); 280 free(tmp); 281 } 282 fl_head[h].next = NULL; 283 } 284 free(fl_head); 285 fl_head = NULL; 286} 287 288static void matchpathcon_thread_destructor(void __attribute__((unused)) *ptr) 289{ 290 matchpathcon_fini(); 291} 292 293static void matchpathcon_init_once(void) 294{ 295 __selinux_key_create(&destructor_key, matchpathcon_thread_destructor); 296} 297 298int matchpathcon_init_prefix(const char *path, const char *subset) 299{ 300 if (!mycanoncon) 301 mycanoncon = default_canoncon; 302 303 __selinux_once(once, matchpathcon_init_once); 304 __selinux_setspecific(destructor_key, (void *)1); 305 306 options[SELABEL_OPT_SUBSET].type = SELABEL_OPT_SUBSET; 307 options[SELABEL_OPT_SUBSET].value = subset; 308 options[SELABEL_OPT_PATH].type = SELABEL_OPT_PATH; 309 options[SELABEL_OPT_PATH].value = path; 310 311 hnd = selabel_open(SELABEL_CTX_FILE, options, SELABEL_NOPT); 312 return hnd ? 0 : -1; 313} 314 315hidden_def(matchpathcon_init_prefix) 316 317int matchpathcon_init(const char *path) 318{ 319 return matchpathcon_init_prefix(path, NULL); 320} 321 322void matchpathcon_fini(void) 323{ 324 free_array_elts(); 325 326 if (hnd) { 327 selabel_close(hnd); 328 hnd = NULL; 329 } 330} 331 332int matchpathcon(const char *name, mode_t mode, security_context_t * con) 333{ 334 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) 335 return -1; 336 337 return notrans ? 338 selabel_lookup_raw(hnd, con, name, mode) : 339 selabel_lookup(hnd, con, name, mode); 340} 341 342int matchpathcon_index(const char *name, mode_t mode, security_context_t * con) 343{ 344 int i = matchpathcon(name, mode, con); 345 346 if (i < 0) 347 return -1; 348 349 return add_array_elt(*con); 350} 351 352void matchpathcon_checkmatches(char *str __attribute__((unused))) 353{ 354 selabel_stats(hnd); 355} 356 357/* Compare two contexts to see if their differences are "significant", 358 * or whether the only difference is in the user. */ 359int selinux_file_context_cmp(const security_context_t a, 360 const security_context_t b) 361{ 362 char *rest_a, *rest_b; /* Rest of the context after the user */ 363 if (!a && !b) 364 return 0; 365 if (!a) 366 return -1; 367 if (!b) 368 return 1; 369 rest_a = strchr((char *)a, ':'); 370 rest_b = strchr((char *)b, ':'); 371 if (!rest_a && !rest_b) 372 return 0; 373 if (!rest_a) 374 return -1; 375 if (!rest_b) 376 return 1; 377 return strcmp(rest_a, rest_b); 378} 379 380int selinux_file_context_verify(const char *path, mode_t mode) 381{ 382 security_context_t con = NULL; 383 security_context_t fcontext = NULL; 384 int rc = 0; 385 386 rc = lgetfilecon_raw(path, &con); 387 if (rc == -1) { 388 if (errno != ENOTSUP) 389 return 1; 390 else 391 return 0; 392 } 393 394 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) 395 return -1; 396 397 if (selabel_lookup_raw(hnd, &fcontext, path, mode) != 0) { 398 if (errno != ENOENT) 399 rc = 1; 400 else 401 rc = 0; 402 } else 403 rc = (selinux_file_context_cmp(fcontext, con) == 0); 404 405 freecon(con); 406 freecon(fcontext); 407 return rc; 408} 409 410int selinux_lsetfilecon_default(const char *path) 411{ 412 struct stat st; 413 int rc = -1; 414 security_context_t scontext = NULL; 415 if (lstat(path, &st) != 0) 416 return rc; 417 418 if (!hnd && (matchpathcon_init_prefix(NULL, NULL) < 0)) 419 return -1; 420 421 /* If there's an error determining the context, or it has none, 422 return to allow default context */ 423 if (selabel_lookup_raw(hnd, &scontext, path, st.st_mode)) { 424 if (errno == ENOENT) 425 rc = 0; 426 } else { 427 rc = lsetfilecon_raw(path, scontext); 428 freecon(scontext); 429 } 430 return rc; 431} 432 433int compat_validate(struct selabel_handle *rec, 434 struct selabel_lookup_rec *contexts, 435 const char *path, unsigned lineno) 436{ 437 int rc; 438 char **ctx = &contexts->ctx_raw; 439 440 if (myinvalidcon) 441 rc = myinvalidcon(path, lineno, *ctx); 442 else if (mycanoncon) 443 rc = mycanoncon(path, lineno, ctx); 444 else { 445 rc = selabel_validate(rec, contexts); 446 if (rc < 0) { 447 COMPAT_LOG(SELINUX_WARNING, 448 "%s: line %d has invalid context %s\n", 449 path, lineno, *ctx); 450 } 451 } 452 453 return rc ? -1 : 0; 454} 455