1package selinux 2 3import ( 4 "bufio" 5 "bytes" 6 "crypto/rand" 7 "encoding/binary" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "strings" 17 "sync" 18 19 "github.com/bits-and-blooms/bitset" 20 "github.com/opencontainers/selinux/pkg/pwalk" 21 "github.com/pkg/errors" 22 "golang.org/x/sys/unix" 23) 24 25const ( 26 minSensLen = 2 27 contextFile = "/usr/share/containers/selinux/contexts" 28 selinuxDir = "/etc/selinux/" 29 selinuxUsersDir = "contexts/users" 30 defaultContexts = "contexts/default_contexts" 31 selinuxConfig = selinuxDir + "config" 32 selinuxfsMount = "/sys/fs/selinux" 33 selinuxTypeTag = "SELINUXTYPE" 34 selinuxTag = "SELINUX" 35 xattrNameSelinux = "security.selinux" 36) 37 38var policyRoot = filepath.Join(selinuxDir, readConfig(selinuxTypeTag)) 39 40type selinuxState struct { 41 enabledSet bool 42 enabled bool 43 selinuxfsOnce sync.Once 44 selinuxfs string 45 mcsList map[string]bool 46 sync.Mutex 47} 48 49type level struct { 50 sens uint 51 cats *bitset.BitSet 52} 53 54type mlsRange struct { 55 low *level 56 high *level 57} 58 59type defaultSECtx struct { 60 user, level, scon string 61 userRdr, defaultRdr io.Reader 62 63 verifier func(string) error 64} 65 66type levelItem byte 67 68const ( 69 sensitivity levelItem = 's' 70 category levelItem = 'c' 71) 72 73var ( 74 assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) 75 readOnlyFileLabel string 76 state = selinuxState{ 77 mcsList: make(map[string]bool), 78 } 79 80 // for attrPath() 81 attrPathOnce sync.Once 82 haveThreadSelf bool 83) 84 85func (s *selinuxState) setEnable(enabled bool) bool { 86 s.Lock() 87 defer s.Unlock() 88 s.enabledSet = true 89 s.enabled = enabled 90 return s.enabled 91} 92 93func (s *selinuxState) getEnabled() bool { 94 s.Lock() 95 enabled := s.enabled 96 enabledSet := s.enabledSet 97 s.Unlock() 98 if enabledSet { 99 return enabled 100 } 101 102 enabled = false 103 if fs := getSelinuxMountPoint(); fs != "" { 104 if con, _ := CurrentLabel(); con != "kernel" { 105 enabled = true 106 } 107 } 108 return s.setEnable(enabled) 109} 110 111// setDisabled disables SELinux support for the package 112func setDisabled() { 113 state.setEnable(false) 114} 115 116func verifySELinuxfsMount(mnt string) bool { 117 var buf unix.Statfs_t 118 for { 119 err := unix.Statfs(mnt, &buf) 120 if err == nil { 121 break 122 } 123 if err == unix.EAGAIN || err == unix.EINTR { 124 continue 125 } 126 return false 127 } 128 129 if uint32(buf.Type) != uint32(unix.SELINUX_MAGIC) { 130 return false 131 } 132 if (buf.Flags & unix.ST_RDONLY) != 0 { 133 return false 134 } 135 136 return true 137} 138 139func findSELinuxfs() string { 140 // fast path: check the default mount first 141 if verifySELinuxfsMount(selinuxfsMount) { 142 return selinuxfsMount 143 } 144 145 // check if selinuxfs is available before going the slow path 146 fs, err := ioutil.ReadFile("/proc/filesystems") 147 if err != nil { 148 return "" 149 } 150 if !bytes.Contains(fs, []byte("\tselinuxfs\n")) { 151 return "" 152 } 153 154 // slow path: try to find among the mounts 155 f, err := os.Open("/proc/self/mountinfo") 156 if err != nil { 157 return "" 158 } 159 defer f.Close() 160 161 scanner := bufio.NewScanner(f) 162 for { 163 mnt := findSELinuxfsMount(scanner) 164 if mnt == "" { // error or not found 165 return "" 166 } 167 if verifySELinuxfsMount(mnt) { 168 return mnt 169 } 170 } 171} 172 173// findSELinuxfsMount returns a next selinuxfs mount point found, 174// if there is one, or an empty string in case of EOF or error. 175func findSELinuxfsMount(s *bufio.Scanner) string { 176 for s.Scan() { 177 txt := s.Bytes() 178 // The first field after - is fs type. 179 // Safe as spaces in mountpoints are encoded as \040 180 if !bytes.Contains(txt, []byte(" - selinuxfs ")) { 181 continue 182 } 183 const mPos = 5 // mount point is 5th field 184 fields := bytes.SplitN(txt, []byte(" "), mPos+1) 185 if len(fields) < mPos+1 { 186 continue 187 } 188 return string(fields[mPos-1]) 189 } 190 191 return "" 192} 193 194func (s *selinuxState) getSELinuxfs() string { 195 s.selinuxfsOnce.Do(func() { 196 s.selinuxfs = findSELinuxfs() 197 }) 198 199 return s.selinuxfs 200} 201 202// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs 203// filesystem or an empty string if no mountpoint is found. Selinuxfs is 204// a proc-like pseudo-filesystem that exposes the SELinux policy API to 205// processes. The existence of an selinuxfs mount is used to determine 206// whether SELinux is currently enabled or not. 207func getSelinuxMountPoint() string { 208 return state.getSELinuxfs() 209} 210 211// getEnabled returns whether SELinux is currently enabled. 212func getEnabled() bool { 213 return state.getEnabled() 214} 215 216func readConfig(target string) string { 217 in, err := os.Open(selinuxConfig) 218 if err != nil { 219 return "" 220 } 221 defer in.Close() 222 223 scanner := bufio.NewScanner(in) 224 225 for scanner.Scan() { 226 line := strings.TrimSpace(scanner.Text()) 227 if len(line) == 0 { 228 // Skip blank lines 229 continue 230 } 231 if line[0] == ';' || line[0] == '#' { 232 // Skip comments 233 continue 234 } 235 if groups := assignRegex.FindStringSubmatch(line); groups != nil { 236 key, val := strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) 237 if key == target { 238 return strings.Trim(val, "\"") 239 } 240 } 241 } 242 return "" 243} 244 245func isProcHandle(fh *os.File) error { 246 var buf unix.Statfs_t 247 248 for { 249 err := unix.Fstatfs(int(fh.Fd()), &buf) 250 if err == nil { 251 break 252 } 253 if err != unix.EINTR { 254 return errors.Wrapf(err, "statfs(%q) failed", fh.Name()) 255 } 256 } 257 if buf.Type != unix.PROC_SUPER_MAGIC { 258 return errors.Errorf("file %q is not on procfs", fh.Name()) 259 } 260 261 return nil 262} 263 264func readCon(fpath string) (string, error) { 265 if fpath == "" { 266 return "", ErrEmptyPath 267 } 268 269 in, err := os.Open(fpath) 270 if err != nil { 271 return "", err 272 } 273 defer in.Close() 274 275 if err := isProcHandle(in); err != nil { 276 return "", err 277 } 278 279 var retval string 280 if _, err := fmt.Fscanf(in, "%s", &retval); err != nil { 281 return "", err 282 } 283 return strings.Trim(retval, "\x00"), nil 284} 285 286// classIndex returns the int index for an object class in the loaded policy, 287// or -1 and an error 288func classIndex(class string) (int, error) { 289 permpath := fmt.Sprintf("class/%s/index", class) 290 indexpath := filepath.Join(getSelinuxMountPoint(), permpath) 291 292 indexB, err := ioutil.ReadFile(indexpath) 293 if err != nil { 294 return -1, err 295 } 296 index, err := strconv.Atoi(string(indexB)) 297 if err != nil { 298 return -1, err 299 } 300 301 return index, nil 302} 303 304// setFileLabel sets the SELinux label for this path or returns an error. 305func setFileLabel(fpath string, label string) error { 306 if fpath == "" { 307 return ErrEmptyPath 308 } 309 for { 310 err := unix.Lsetxattr(fpath, xattrNameSelinux, []byte(label), 0) 311 if err == nil { 312 break 313 } 314 if err != unix.EINTR { 315 return errors.Wrapf(err, "failed to set file label on %s", fpath) 316 } 317 } 318 319 return nil 320} 321 322// fileLabel returns the SELinux label for this path or returns an error. 323func fileLabel(fpath string) (string, error) { 324 if fpath == "" { 325 return "", ErrEmptyPath 326 } 327 328 label, err := lgetxattr(fpath, xattrNameSelinux) 329 if err != nil { 330 return "", err 331 } 332 // Trim the NUL byte at the end of the byte buffer, if present. 333 if len(label) > 0 && label[len(label)-1] == '\x00' { 334 label = label[:len(label)-1] 335 } 336 return string(label), nil 337} 338 339// setFSCreateLabel tells kernel the label to create all file system objects 340// created by this task. Setting label="" to return to default. 341func setFSCreateLabel(label string) error { 342 return writeAttr("fscreate", label) 343} 344 345// fsCreateLabel returns the default label the kernel which the kernel is using 346// for file system objects created by this task. "" indicates default. 347func fsCreateLabel() (string, error) { 348 return readAttr("fscreate") 349} 350 351// currentLabel returns the SELinux label of the current process thread, or an error. 352func currentLabel() (string, error) { 353 return readAttr("current") 354} 355 356// pidLabel returns the SELinux label of the given pid, or an error. 357func pidLabel(pid int) (string, error) { 358 return readCon(fmt.Sprintf("/proc/%d/attr/current", pid)) 359} 360 361// ExecLabel returns the SELinux label that the kernel will use for any programs 362// that are executed by the current process thread, or an error. 363func execLabel() (string, error) { 364 return readAttr("exec") 365} 366 367func writeCon(fpath, val string) error { 368 if fpath == "" { 369 return ErrEmptyPath 370 } 371 if val == "" { 372 if !getEnabled() { 373 return nil 374 } 375 } 376 377 out, err := os.OpenFile(fpath, os.O_WRONLY, 0) 378 if err != nil { 379 return err 380 } 381 defer out.Close() 382 383 if err := isProcHandle(out); err != nil { 384 return err 385 } 386 387 if val != "" { 388 _, err = out.Write([]byte(val)) 389 } else { 390 _, err = out.Write(nil) 391 } 392 if err != nil { 393 return errors.Wrapf(err, "failed to set %s on procfs", fpath) 394 } 395 return nil 396} 397 398func attrPath(attr string) string { 399 // Linux >= 3.17 provides this 400 const threadSelfPrefix = "/proc/thread-self/attr" 401 402 attrPathOnce.Do(func() { 403 st, err := os.Stat(threadSelfPrefix) 404 if err == nil && st.Mode().IsDir() { 405 haveThreadSelf = true 406 } 407 }) 408 409 if haveThreadSelf { 410 return path.Join(threadSelfPrefix, attr) 411 } 412 413 return path.Join("/proc/self/task/", strconv.Itoa(unix.Gettid()), "/attr/", attr) 414} 415 416func readAttr(attr string) (string, error) { 417 return readCon(attrPath(attr)) 418} 419 420func writeAttr(attr, val string) error { 421 return writeCon(attrPath(attr), val) 422} 423 424// canonicalizeContext takes a context string and writes it to the kernel 425// the function then returns the context that the kernel will use. Use this 426// function to check if two contexts are equivalent 427func canonicalizeContext(val string) (string, error) { 428 return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val) 429} 430 431// computeCreateContext requests the type transition from source to target for 432// class from the kernel. 433func computeCreateContext(source string, target string, class string) (string, error) { 434 classidx, err := classIndex(class) 435 if err != nil { 436 return "", err 437 } 438 439 return readWriteCon(filepath.Join(getSelinuxMountPoint(), "create"), fmt.Sprintf("%s %s %d", source, target, classidx)) 440} 441 442// catsToBitset stores categories in a bitset. 443func catsToBitset(cats string) (*bitset.BitSet, error) { 444 bitset := &bitset.BitSet{} 445 446 catlist := strings.Split(cats, ",") 447 for _, r := range catlist { 448 ranges := strings.SplitN(r, ".", 2) 449 if len(ranges) > 1 { 450 catstart, err := parseLevelItem(ranges[0], category) 451 if err != nil { 452 return nil, err 453 } 454 catend, err := parseLevelItem(ranges[1], category) 455 if err != nil { 456 return nil, err 457 } 458 for i := catstart; i <= catend; i++ { 459 bitset.Set(i) 460 } 461 } else { 462 cat, err := parseLevelItem(ranges[0], category) 463 if err != nil { 464 return nil, err 465 } 466 bitset.Set(cat) 467 } 468 } 469 470 return bitset, nil 471} 472 473// parseLevelItem parses and verifies that a sensitivity or category are valid 474func parseLevelItem(s string, sep levelItem) (uint, error) { 475 if len(s) < minSensLen || levelItem(s[0]) != sep { 476 return 0, ErrLevelSyntax 477 } 478 val, err := strconv.ParseUint(s[1:], 10, 32) 479 if err != nil { 480 return 0, err 481 } 482 483 return uint(val), nil 484} 485 486// parseLevel fills a level from a string that contains 487// a sensitivity and categories 488func (l *level) parseLevel(levelStr string) error { 489 lvl := strings.SplitN(levelStr, ":", 2) 490 sens, err := parseLevelItem(lvl[0], sensitivity) 491 if err != nil { 492 return errors.Wrap(err, "failed to parse sensitivity") 493 } 494 l.sens = sens 495 if len(lvl) > 1 { 496 cats, err := catsToBitset(lvl[1]) 497 if err != nil { 498 return errors.Wrap(err, "failed to parse categories") 499 } 500 l.cats = cats 501 } 502 503 return nil 504} 505 506// rangeStrToMLSRange marshals a string representation of a range. 507func rangeStrToMLSRange(rangeStr string) (*mlsRange, error) { 508 mlsRange := &mlsRange{} 509 levelSlice := strings.SplitN(rangeStr, "-", 2) 510 511 switch len(levelSlice) { 512 // rangeStr that has a low and a high level, e.g. s4:c0.c1023-s6:c0.c1023 513 case 2: 514 mlsRange.high = &level{} 515 if err := mlsRange.high.parseLevel(levelSlice[1]); err != nil { 516 return nil, errors.Wrapf(err, "failed to parse high level %q", levelSlice[1]) 517 } 518 fallthrough 519 // rangeStr that is single level, e.g. s6:c0,c3,c5,c30.c1023 520 case 1: 521 mlsRange.low = &level{} 522 if err := mlsRange.low.parseLevel(levelSlice[0]); err != nil { 523 return nil, errors.Wrapf(err, "failed to parse low level %q", levelSlice[0]) 524 } 525 } 526 527 if mlsRange.high == nil { 528 mlsRange.high = mlsRange.low 529 } 530 531 return mlsRange, nil 532} 533 534// bitsetToStr takes a category bitset and returns it in the 535// canonical selinux syntax 536func bitsetToStr(c *bitset.BitSet) string { 537 var str string 538 i, e := c.NextSet(0) 539 len := 0 540 for e { 541 if len == 0 { 542 if str != "" { 543 str += "," 544 } 545 str += "c" + strconv.Itoa(int(i)) 546 } 547 548 next, e := c.NextSet(i + 1) 549 if e { 550 // consecutive cats 551 if next == i+1 { 552 len++ 553 i = next 554 continue 555 } 556 } 557 if len == 1 { 558 str += ",c" + strconv.Itoa(int(i)) 559 } else if len > 1 { 560 str += ".c" + strconv.Itoa(int(i)) 561 } 562 if !e { 563 break 564 } 565 len = 0 566 i = next 567 } 568 569 return str 570} 571 572func (l1 *level) equal(l2 *level) bool { 573 if l2 == nil || l1 == nil { 574 return l1 == l2 575 } 576 if l1.sens != l2.sens { 577 return false 578 } 579 return l1.cats.Equal(l2.cats) 580} 581 582// String returns an mlsRange as a string. 583func (m mlsRange) String() string { 584 low := "s" + strconv.Itoa(int(m.low.sens)) 585 if m.low.cats != nil && m.low.cats.Count() > 0 { 586 low += ":" + bitsetToStr(m.low.cats) 587 } 588 589 if m.low.equal(m.high) { 590 return low 591 } 592 593 high := "s" + strconv.Itoa(int(m.high.sens)) 594 if m.high.cats != nil && m.high.cats.Count() > 0 { 595 high += ":" + bitsetToStr(m.high.cats) 596 } 597 598 return low + "-" + high 599} 600 601func max(a, b uint) uint { 602 if a > b { 603 return a 604 } 605 return b 606} 607 608func min(a, b uint) uint { 609 if a < b { 610 return a 611 } 612 return b 613} 614 615// calculateGlbLub computes the glb (greatest lower bound) and lub (least upper bound) 616// of a source and target range. 617// The glblub is calculated as the greater of the low sensitivities and 618// the lower of the high sensitivities and the and of each category bitset. 619func calculateGlbLub(sourceRange, targetRange string) (string, error) { 620 s, err := rangeStrToMLSRange(sourceRange) 621 if err != nil { 622 return "", err 623 } 624 t, err := rangeStrToMLSRange(targetRange) 625 if err != nil { 626 return "", err 627 } 628 629 if s.high.sens < t.low.sens || t.high.sens < s.low.sens { 630 /* these ranges have no common sensitivities */ 631 return "", ErrIncomparable 632 } 633 634 outrange := &mlsRange{low: &level{}, high: &level{}} 635 636 /* take the greatest of the low */ 637 outrange.low.sens = max(s.low.sens, t.low.sens) 638 639 /* take the least of the high */ 640 outrange.high.sens = min(s.high.sens, t.high.sens) 641 642 /* find the intersecting categories */ 643 if s.low.cats != nil && t.low.cats != nil { 644 outrange.low.cats = s.low.cats.Intersection(t.low.cats) 645 } 646 if s.high.cats != nil && t.high.cats != nil { 647 outrange.high.cats = s.high.cats.Intersection(t.high.cats) 648 } 649 650 return outrange.String(), nil 651} 652 653func readWriteCon(fpath string, val string) (string, error) { 654 if fpath == "" { 655 return "", ErrEmptyPath 656 } 657 f, err := os.OpenFile(fpath, os.O_RDWR, 0) 658 if err != nil { 659 return "", err 660 } 661 defer f.Close() 662 663 _, err = f.Write([]byte(val)) 664 if err != nil { 665 return "", err 666 } 667 668 var retval string 669 if _, err := fmt.Fscanf(f, "%s", &retval); err != nil { 670 return "", err 671 } 672 return strings.Trim(retval, "\x00"), nil 673} 674 675// setExecLabel sets the SELinux label that the kernel will use for any programs 676// that are executed by the current process thread, or an error. 677func setExecLabel(label string) error { 678 return writeAttr("exec", label) 679} 680 681// setTaskLabel sets the SELinux label for the current thread, or an error. 682// This requires the dyntransition permission. 683func setTaskLabel(label string) error { 684 return writeAttr("current", label) 685} 686 687// setSocketLabel takes a process label and tells the kernel to assign the 688// label to the next socket that gets created 689func setSocketLabel(label string) error { 690 return writeAttr("sockcreate", label) 691} 692 693// socketLabel retrieves the current socket label setting 694func socketLabel() (string, error) { 695 return readAttr("sockcreate") 696} 697 698// peerLabel retrieves the label of the client on the other side of a socket 699func peerLabel(fd uintptr) (string, error) { 700 return unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC) 701} 702 703// setKeyLabel takes a process label and tells the kernel to assign the 704// label to the next kernel keyring that gets created 705func setKeyLabel(label string) error { 706 err := writeCon("/proc/self/attr/keycreate", label) 707 if os.IsNotExist(errors.Cause(err)) { 708 return nil 709 } 710 if label == "" && os.IsPermission(errors.Cause(err)) { 711 return nil 712 } 713 return err 714} 715 716// keyLabel retrieves the current kernel keyring label setting 717func keyLabel() (string, error) { 718 return readCon("/proc/self/attr/keycreate") 719} 720 721// get returns the Context as a string 722func (c Context) get() string { 723 if c["level"] != "" { 724 return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"]) 725 } 726 return fmt.Sprintf("%s:%s:%s", c["user"], c["role"], c["type"]) 727} 728 729// newContext creates a new Context struct from the specified label 730func newContext(label string) (Context, error) { 731 c := make(Context) 732 733 if len(label) != 0 { 734 con := strings.SplitN(label, ":", 4) 735 if len(con) < 3 { 736 return c, InvalidLabel 737 } 738 c["user"] = con[0] 739 c["role"] = con[1] 740 c["type"] = con[2] 741 if len(con) > 3 { 742 c["level"] = con[3] 743 } 744 } 745 return c, nil 746} 747 748// clearLabels clears all reserved labels 749func clearLabels() { 750 state.Lock() 751 state.mcsList = make(map[string]bool) 752 state.Unlock() 753} 754 755// reserveLabel reserves the MLS/MCS level component of the specified label 756func reserveLabel(label string) { 757 if len(label) != 0 { 758 con := strings.SplitN(label, ":", 4) 759 if len(con) > 3 { 760 _ = mcsAdd(con[3]) 761 } 762 } 763} 764 765func selinuxEnforcePath() string { 766 return path.Join(getSelinuxMountPoint(), "enforce") 767} 768 769// enforceMode returns the current SELinux mode Enforcing, Permissive, Disabled 770func enforceMode() int { 771 var enforce int 772 773 enforceB, err := ioutil.ReadFile(selinuxEnforcePath()) 774 if err != nil { 775 return -1 776 } 777 enforce, err = strconv.Atoi(string(enforceB)) 778 if err != nil { 779 return -1 780 } 781 return enforce 782} 783 784// setEnforceMode sets the current SELinux mode Enforcing, Permissive. 785// Disabled is not valid, since this needs to be set at boot time. 786func setEnforceMode(mode int) error { 787 return ioutil.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0644) 788} 789 790// defaultEnforceMode returns the systems default SELinux mode Enforcing, 791// Permissive or Disabled. Note this is is just the default at boot time. 792// EnforceMode tells you the systems current mode. 793func defaultEnforceMode() int { 794 switch readConfig(selinuxTag) { 795 case "enforcing": 796 return Enforcing 797 case "permissive": 798 return Permissive 799 } 800 return Disabled 801} 802 803func mcsAdd(mcs string) error { 804 if mcs == "" { 805 return nil 806 } 807 state.Lock() 808 defer state.Unlock() 809 if state.mcsList[mcs] { 810 return ErrMCSAlreadyExists 811 } 812 state.mcsList[mcs] = true 813 return nil 814} 815 816func mcsDelete(mcs string) { 817 if mcs == "" { 818 return 819 } 820 state.Lock() 821 defer state.Unlock() 822 state.mcsList[mcs] = false 823} 824 825func intToMcs(id int, catRange uint32) string { 826 var ( 827 SETSIZE = int(catRange) 828 TIER = SETSIZE 829 ORD = id 830 ) 831 832 if id < 1 || id > 523776 { 833 return "" 834 } 835 836 for ORD > TIER { 837 ORD -= TIER 838 TIER-- 839 } 840 TIER = SETSIZE - TIER 841 ORD += TIER 842 return fmt.Sprintf("s0:c%d,c%d", TIER, ORD) 843} 844 845func uniqMcs(catRange uint32) string { 846 var ( 847 n uint32 848 c1, c2 uint32 849 mcs string 850 ) 851 852 for { 853 _ = binary.Read(rand.Reader, binary.LittleEndian, &n) 854 c1 = n % catRange 855 _ = binary.Read(rand.Reader, binary.LittleEndian, &n) 856 c2 = n % catRange 857 if c1 == c2 { 858 continue 859 } else if c1 > c2 { 860 c1, c2 = c2, c1 861 } 862 mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2) 863 if err := mcsAdd(mcs); err != nil { 864 continue 865 } 866 break 867 } 868 return mcs 869} 870 871// releaseLabel un-reserves the MLS/MCS Level field of the specified label, 872// allowing it to be used by another process. 873func releaseLabel(label string) { 874 if len(label) != 0 { 875 con := strings.SplitN(label, ":", 4) 876 if len(con) > 3 { 877 mcsDelete(con[3]) 878 } 879 } 880} 881 882// roFileLabel returns the specified SELinux readonly file label 883func roFileLabel() string { 884 return readOnlyFileLabel 885} 886 887func openContextFile() (*os.File, error) { 888 if f, err := os.Open(contextFile); err == nil { 889 return f, nil 890 } 891 lxcPath := filepath.Join(policyRoot, "/contexts/lxc_contexts") 892 return os.Open(lxcPath) 893} 894 895var labels, privContainerMountLabel = loadLabels() 896 897func loadLabels() (map[string]string, string) { 898 labels := make(map[string]string) 899 in, err := openContextFile() 900 if err != nil { 901 return labels, "" 902 } 903 defer in.Close() 904 905 scanner := bufio.NewScanner(in) 906 907 for scanner.Scan() { 908 line := strings.TrimSpace(scanner.Text()) 909 if len(line) == 0 { 910 // Skip blank lines 911 continue 912 } 913 if line[0] == ';' || line[0] == '#' { 914 // Skip comments 915 continue 916 } 917 if groups := assignRegex.FindStringSubmatch(line); groups != nil { 918 key, val := strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) 919 labels[key] = strings.Trim(val, "\"") 920 } 921 } 922 923 con, _ := NewContext(labels["file"]) 924 con["level"] = fmt.Sprintf("s0:c%d,c%d", maxCategory-2, maxCategory-1) 925 reserveLabel(con.get()) 926 return labels, con.get() 927} 928 929// kvmContainerLabels returns the default processLabel and mountLabel to be used 930// for kvm containers by the calling process. 931func kvmContainerLabels() (string, string) { 932 processLabel := labels["kvm_process"] 933 if processLabel == "" { 934 processLabel = labels["process"] 935 } 936 937 return addMcs(processLabel, labels["file"]) 938} 939 940// initContainerLabels returns the default processLabel and file labels to be 941// used for containers running an init system like systemd by the calling process. 942func initContainerLabels() (string, string) { 943 processLabel := labels["init_process"] 944 if processLabel == "" { 945 processLabel = labels["process"] 946 } 947 948 return addMcs(processLabel, labels["file"]) 949} 950 951// containerLabels returns an allocated processLabel and fileLabel to be used for 952// container labeling by the calling process. 953func containerLabels() (processLabel string, fileLabel string) { 954 if !getEnabled() { 955 return "", "" 956 } 957 958 processLabel = labels["process"] 959 fileLabel = labels["file"] 960 readOnlyFileLabel = labels["ro_file"] 961 962 if processLabel == "" || fileLabel == "" { 963 return "", fileLabel 964 } 965 966 if readOnlyFileLabel == "" { 967 readOnlyFileLabel = fileLabel 968 } 969 970 return addMcs(processLabel, fileLabel) 971} 972 973func addMcs(processLabel, fileLabel string) (string, string) { 974 scon, _ := NewContext(processLabel) 975 if scon["level"] != "" { 976 mcs := uniqMcs(CategoryRange) 977 scon["level"] = mcs 978 processLabel = scon.Get() 979 scon, _ = NewContext(fileLabel) 980 scon["level"] = mcs 981 fileLabel = scon.Get() 982 } 983 return processLabel, fileLabel 984} 985 986// securityCheckContext validates that the SELinux label is understood by the kernel 987func securityCheckContext(val string) error { 988 return ioutil.WriteFile(path.Join(getSelinuxMountPoint(), "context"), []byte(val), 0644) 989} 990 991// copyLevel returns a label with the MLS/MCS level from src label replaced on 992// the dest label. 993func copyLevel(src, dest string) (string, error) { 994 if src == "" { 995 return "", nil 996 } 997 if err := SecurityCheckContext(src); err != nil { 998 return "", err 999 } 1000 if err := SecurityCheckContext(dest); err != nil { 1001 return "", err 1002 } 1003 scon, err := NewContext(src) 1004 if err != nil { 1005 return "", err 1006 } 1007 tcon, err := NewContext(dest) 1008 if err != nil { 1009 return "", err 1010 } 1011 mcsDelete(tcon["level"]) 1012 _ = mcsAdd(scon["level"]) 1013 tcon["level"] = scon["level"] 1014 return tcon.Get(), nil 1015} 1016 1017// Prevent users from relabeling system files 1018func badPrefix(fpath string) error { 1019 if fpath == "" { 1020 return ErrEmptyPath 1021 } 1022 1023 badPrefixes := []string{"/usr"} 1024 for _, prefix := range badPrefixes { 1025 if strings.HasPrefix(fpath, prefix) { 1026 return errors.Errorf("relabeling content in %s is not allowed", prefix) 1027 } 1028 } 1029 return nil 1030} 1031 1032// chcon changes the fpath file object to the SELinux label label. 1033// If fpath is a directory and recurse is true, then chcon walks the 1034// directory tree setting the label. 1035func chcon(fpath string, label string, recurse bool) error { 1036 if fpath == "" { 1037 return ErrEmptyPath 1038 } 1039 if label == "" { 1040 return nil 1041 } 1042 if err := badPrefix(fpath); err != nil { 1043 return err 1044 } 1045 1046 if !recurse { 1047 return SetFileLabel(fpath, label) 1048 } 1049 1050 return pwalk.Walk(fpath, func(p string, info os.FileInfo, err error) error { 1051 e := SetFileLabel(p, label) 1052 // Walk a file tree can race with removal, so ignore ENOENT 1053 if os.IsNotExist(errors.Cause(e)) { 1054 return nil 1055 } 1056 return e 1057 }) 1058} 1059 1060// dupSecOpt takes an SELinux process label and returns security options that 1061// can be used to set the SELinux Type and Level for future container processes. 1062func dupSecOpt(src string) ([]string, error) { 1063 if src == "" { 1064 return nil, nil 1065 } 1066 con, err := NewContext(src) 1067 if err != nil { 1068 return nil, err 1069 } 1070 if con["user"] == "" || 1071 con["role"] == "" || 1072 con["type"] == "" { 1073 return nil, nil 1074 } 1075 dup := []string{"user:" + con["user"], 1076 "role:" + con["role"], 1077 "type:" + con["type"], 1078 } 1079 1080 if con["level"] != "" { 1081 dup = append(dup, "level:"+con["level"]) 1082 } 1083 1084 return dup, nil 1085} 1086 1087// disableSecOpt returns a security opt that can be used to disable SELinux 1088// labeling support for future container processes. 1089func disableSecOpt() []string { 1090 return []string{"disable"} 1091} 1092 1093// findUserInContext scans the reader for a valid SELinux context 1094// match that is verified with the verifier. Invalid contexts are 1095// skipped. It returns a matched context or an empty string if no 1096// match is found. If a scanner error occurs, it is returned. 1097func findUserInContext(context Context, r io.Reader, verifier func(string) error) (string, error) { 1098 fromRole := context["role"] 1099 fromType := context["type"] 1100 scanner := bufio.NewScanner(r) 1101 1102 for scanner.Scan() { 1103 fromConns := strings.Fields(scanner.Text()) 1104 if len(fromConns) == 0 { 1105 // Skip blank lines 1106 continue 1107 } 1108 1109 line := fromConns[0] 1110 1111 if line[0] == ';' || line[0] == '#' { 1112 // Skip comments 1113 continue 1114 } 1115 1116 // user context files contexts are formatted as 1117 // role_r:type_t:s0 where the user is missing. 1118 lineArr := strings.SplitN(line, ":", 4) 1119 // skip context with typo, or role and type do not match 1120 if len(lineArr) != 3 || 1121 lineArr[0] != fromRole || 1122 lineArr[1] != fromType { 1123 continue 1124 } 1125 1126 for _, cc := range fromConns[1:] { 1127 toConns := strings.SplitN(cc, ":", 4) 1128 if len(toConns) != 3 { 1129 continue 1130 } 1131 1132 context["role"] = toConns[0] 1133 context["type"] = toConns[1] 1134 1135 outConn := context.get() 1136 if err := verifier(outConn); err != nil { 1137 continue 1138 } 1139 1140 return outConn, nil 1141 } 1142 } 1143 1144 if err := scanner.Err(); err != nil { 1145 return "", errors.Wrap(err, "failed to scan for context") 1146 } 1147 1148 return "", nil 1149} 1150 1151func getDefaultContextFromReaders(c *defaultSECtx) (string, error) { 1152 if c.verifier == nil { 1153 return "", ErrVerifierNil 1154 } 1155 1156 context, err := newContext(c.scon) 1157 if err != nil { 1158 return "", errors.Wrapf(err, "failed to create label for %s", c.scon) 1159 } 1160 1161 // set so the verifier validates the matched context with the provided user and level. 1162 context["user"] = c.user 1163 context["level"] = c.level 1164 1165 conn, err := findUserInContext(context, c.userRdr, c.verifier) 1166 if err != nil { 1167 return "", err 1168 } 1169 1170 if conn != "" { 1171 return conn, nil 1172 } 1173 1174 conn, err = findUserInContext(context, c.defaultRdr, c.verifier) 1175 if err != nil { 1176 return "", err 1177 } 1178 1179 if conn != "" { 1180 return conn, nil 1181 } 1182 1183 return "", errors.Wrapf(ErrContextMissing, "context not found: %q", c.scon) 1184} 1185 1186func getDefaultContextWithLevel(user, level, scon string) (string, error) { 1187 userPath := filepath.Join(policyRoot, selinuxUsersDir, user) 1188 defaultPath := filepath.Join(policyRoot, defaultContexts) 1189 1190 fu, err := os.Open(userPath) 1191 if err != nil { 1192 return "", err 1193 } 1194 defer fu.Close() 1195 1196 fd, err := os.Open(defaultPath) 1197 if err != nil { 1198 return "", err 1199 } 1200 defer fd.Close() 1201 1202 c := defaultSECtx{ 1203 user: user, 1204 level: level, 1205 scon: scon, 1206 userRdr: fu, 1207 defaultRdr: fd, 1208 verifier: securityCheckContext, 1209 } 1210 1211 return getDefaultContextFromReaders(&c) 1212} 1213