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