1 package selinux
2
3 import (
4 "bufio"
5 "bytes"
6 "crypto/rand"
7 "encoding/binary"
8 "errors"
9 "fmt"
10 "io"
11 "io/fs"
12 "math/big"
13 "os"
14 "os/user"
15 "path/filepath"
16 "strconv"
17 "strings"
18 "sync"
19
20 "github.com/opencontainers/selinux/pkg/pwalkdir"
21 "golang.org/x/sys/unix"
22 )
23
24 const (
25 minSensLen = 2
26 contextFile = "/usr/share/containers/selinux/contexts"
27 selinuxDir = "/etc/selinux/"
28 selinuxUsersDir = "contexts/users"
29 defaultContexts = "contexts/default_contexts"
30 selinuxConfig = selinuxDir + "config"
31 selinuxfsMount = "/sys/fs/selinux"
32 selinuxTypeTag = "SELINUXTYPE"
33 selinuxTag = "SELINUX"
34 xattrNameSelinux = "security.selinux"
35 )
36
37 type selinuxState struct {
38 mcsList map[string]bool
39 selinuxfs string
40 selinuxfsOnce sync.Once
41 enabledSet bool
42 enabled bool
43 sync.Mutex
44 }
45
46 type level struct {
47 cats *big.Int
48 sens uint
49 }
50
51 type mlsRange struct {
52 low *level
53 high *level
54 }
55
56 type defaultSECtx struct {
57 userRdr io.Reader
58 verifier func(string) error
59 defaultRdr io.Reader
60 user, level, scon string
61 }
62
63 type levelItem byte
64
65 const (
66 sensitivity levelItem = 's'
67 category levelItem = 'c'
68 )
69
70 var (
71 readOnlyFileLabel string
72 state = selinuxState{
73 mcsList: make(map[string]bool),
74 }
75
76
77 attrPathOnce sync.Once
78 haveThreadSelf bool
79
80
81 policyRootOnce sync.Once
82 policyRootVal string
83
84
85 loadLabelsOnce sync.Once
86 labels map[string]string
87 )
88
89 func policyRoot() string {
90 policyRootOnce.Do(func() {
91 policyRootVal = filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
92 })
93
94 return policyRootVal
95 }
96
97 func (s *selinuxState) setEnable(enabled bool) bool {
98 s.Lock()
99 defer s.Unlock()
100 s.enabledSet = true
101 s.enabled = enabled
102 return s.enabled
103 }
104
105 func (s *selinuxState) getEnabled() bool {
106 s.Lock()
107 enabled := s.enabled
108 enabledSet := s.enabledSet
109 s.Unlock()
110 if enabledSet {
111 return enabled
112 }
113
114 enabled = false
115 if fs := getSelinuxMountPoint(); fs != "" {
116 if con, _ := CurrentLabel(); con != "kernel" {
117 enabled = true
118 }
119 }
120 return s.setEnable(enabled)
121 }
122
123
124 func setDisabled() {
125 state.setEnable(false)
126 }
127
128 func verifySELinuxfsMount(mnt string) bool {
129 var buf unix.Statfs_t
130 for {
131 err := unix.Statfs(mnt, &buf)
132 if err == nil {
133 break
134 }
135 if err == unix.EAGAIN || err == unix.EINTR {
136 continue
137 }
138 return false
139 }
140
141 if uint32(buf.Type) != uint32(unix.SELINUX_MAGIC) {
142 return false
143 }
144 if (buf.Flags & unix.ST_RDONLY) != 0 {
145 return false
146 }
147
148 return true
149 }
150
151 func findSELinuxfs() string {
152
153 if verifySELinuxfsMount(selinuxfsMount) {
154 return selinuxfsMount
155 }
156
157
158 fs, err := os.ReadFile("/proc/filesystems")
159 if err != nil {
160 return ""
161 }
162 if !bytes.Contains(fs, []byte("\tselinuxfs\n")) {
163 return ""
164 }
165
166
167 f, err := os.Open("/proc/self/mountinfo")
168 if err != nil {
169 return ""
170 }
171 defer f.Close()
172
173 scanner := bufio.NewScanner(f)
174 for {
175 mnt := findSELinuxfsMount(scanner)
176 if mnt == "" {
177 return ""
178 }
179 if verifySELinuxfsMount(mnt) {
180 return mnt
181 }
182 }
183 }
184
185
186
187 func findSELinuxfsMount(s *bufio.Scanner) string {
188 for s.Scan() {
189 txt := s.Bytes()
190
191
192 if !bytes.Contains(txt, []byte(" - selinuxfs ")) {
193 continue
194 }
195 const mPos = 5
196 fields := bytes.SplitN(txt, []byte(" "), mPos+1)
197 if len(fields) < mPos+1 {
198 continue
199 }
200 return string(fields[mPos-1])
201 }
202
203 return ""
204 }
205
206 func (s *selinuxState) getSELinuxfs() string {
207 s.selinuxfsOnce.Do(func() {
208 s.selinuxfs = findSELinuxfs()
209 })
210
211 return s.selinuxfs
212 }
213
214
215
216
217
218
219 func getSelinuxMountPoint() string {
220 return state.getSELinuxfs()
221 }
222
223
224 func getEnabled() bool {
225 return state.getEnabled()
226 }
227
228 func readConfig(target string) string {
229 in, err := os.Open(selinuxConfig)
230 if err != nil {
231 return ""
232 }
233 defer in.Close()
234
235 scanner := bufio.NewScanner(in)
236
237 for scanner.Scan() {
238 line := bytes.TrimSpace(scanner.Bytes())
239 if len(line) == 0 {
240
241 continue
242 }
243 if line[0] == ';' || line[0] == '#' {
244
245 continue
246 }
247 fields := bytes.SplitN(line, []byte{'='}, 2)
248 if len(fields) != 2 {
249 continue
250 }
251 if bytes.Equal(fields[0], []byte(target)) {
252 return string(bytes.Trim(fields[1], `"`))
253 }
254 }
255 return ""
256 }
257
258 func isProcHandle(fh *os.File) error {
259 var buf unix.Statfs_t
260
261 for {
262 err := unix.Fstatfs(int(fh.Fd()), &buf)
263 if err == nil {
264 break
265 }
266 if err != unix.EINTR {
267 return &os.PathError{Op: "fstatfs", Path: fh.Name(), Err: err}
268 }
269 }
270 if buf.Type != unix.PROC_SUPER_MAGIC {
271 return fmt.Errorf("file %q is not on procfs", fh.Name())
272 }
273
274 return nil
275 }
276
277 func readCon(fpath string) (string, error) {
278 if fpath == "" {
279 return "", ErrEmptyPath
280 }
281
282 in, err := os.Open(fpath)
283 if err != nil {
284 return "", err
285 }
286 defer in.Close()
287
288 if err := isProcHandle(in); err != nil {
289 return "", err
290 }
291 return readConFd(in)
292 }
293
294 func readConFd(in *os.File) (string, error) {
295 data, err := io.ReadAll(in)
296 if err != nil {
297 return "", err
298 }
299 return string(bytes.TrimSuffix(data, []byte{0})), nil
300 }
301
302
303
304 func classIndex(class string) (int, error) {
305 permpath := fmt.Sprintf("class/%s/index", class)
306 indexpath := filepath.Join(getSelinuxMountPoint(), permpath)
307
308 indexB, err := os.ReadFile(indexpath)
309 if err != nil {
310 return -1, err
311 }
312 index, err := strconv.Atoi(string(indexB))
313 if err != nil {
314 return -1, err
315 }
316
317 return index, nil
318 }
319
320
321
322 func lSetFileLabel(fpath string, label string) error {
323 if fpath == "" {
324 return ErrEmptyPath
325 }
326 for {
327 err := unix.Lsetxattr(fpath, xattrNameSelinux, []byte(label), 0)
328 if err == nil {
329 break
330 }
331 if err != unix.EINTR {
332 return &os.PathError{Op: "lsetxattr", Path: fpath, Err: err}
333 }
334 }
335
336 return nil
337 }
338
339
340
341 func setFileLabel(fpath string, label string) error {
342 if fpath == "" {
343 return ErrEmptyPath
344 }
345 for {
346 err := unix.Setxattr(fpath, xattrNameSelinux, []byte(label), 0)
347 if err == nil {
348 break
349 }
350 if err != unix.EINTR {
351 return &os.PathError{Op: "setxattr", Path: fpath, Err: err}
352 }
353 }
354
355 return nil
356 }
357
358
359
360 func fileLabel(fpath string) (string, error) {
361 if fpath == "" {
362 return "", ErrEmptyPath
363 }
364
365 label, err := getxattr(fpath, xattrNameSelinux)
366 if err != nil {
367 return "", &os.PathError{Op: "getxattr", Path: fpath, Err: err}
368 }
369
370 if len(label) > 0 && label[len(label)-1] == '\x00' {
371 label = label[:len(label)-1]
372 }
373 return string(label), nil
374 }
375
376
377
378 func lFileLabel(fpath string) (string, error) {
379 if fpath == "" {
380 return "", ErrEmptyPath
381 }
382
383 label, err := lgetxattr(fpath, xattrNameSelinux)
384 if err != nil {
385 return "", &os.PathError{Op: "lgetxattr", Path: fpath, Err: err}
386 }
387
388 if len(label) > 0 && label[len(label)-1] == '\x00' {
389 label = label[:len(label)-1]
390 }
391 return string(label), nil
392 }
393
394 func setFSCreateLabel(label string) error {
395 return writeCon(attrPath("fscreate"), label)
396 }
397
398
399
400 func fsCreateLabel() (string, error) {
401 return readCon(attrPath("fscreate"))
402 }
403
404
405 func currentLabel() (string, error) {
406 return readCon(attrPath("current"))
407 }
408
409
410 func pidLabel(pid int) (string, error) {
411 return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
412 }
413
414
415
416 func execLabel() (string, error) {
417 return readCon(attrPath("exec"))
418 }
419
420 func writeCon(fpath, val string) error {
421 if fpath == "" {
422 return ErrEmptyPath
423 }
424 if val == "" {
425 if !getEnabled() {
426 return nil
427 }
428 }
429
430 out, err := os.OpenFile(fpath, os.O_WRONLY, 0)
431 if err != nil {
432 return err
433 }
434 defer out.Close()
435
436 if err := isProcHandle(out); err != nil {
437 return err
438 }
439
440 if val != "" {
441 _, err = out.Write([]byte(val))
442 } else {
443 _, err = out.Write(nil)
444 }
445 if err != nil {
446 return err
447 }
448 return nil
449 }
450
451 func attrPath(attr string) string {
452
453 const threadSelfPrefix = "/proc/thread-self/attr"
454
455 attrPathOnce.Do(func() {
456 st, err := os.Stat(threadSelfPrefix)
457 if err == nil && st.Mode().IsDir() {
458 haveThreadSelf = true
459 }
460 })
461
462 if haveThreadSelf {
463 return filepath.Join(threadSelfPrefix, attr)
464 }
465
466 return filepath.Join("/proc/self/task", strconv.Itoa(unix.Gettid()), "attr", attr)
467 }
468
469
470
471
472 func canonicalizeContext(val string) (string, error) {
473 return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val)
474 }
475
476
477
478 func computeCreateContext(source string, target string, class string) (string, error) {
479 classidx, err := classIndex(class)
480 if err != nil {
481 return "", err
482 }
483
484 return readWriteCon(filepath.Join(getSelinuxMountPoint(), "create"), fmt.Sprintf("%s %s %d", source, target, classidx))
485 }
486
487
488 func catsToBitset(cats string) (*big.Int, error) {
489 bitset := new(big.Int)
490
491 catlist := strings.Split(cats, ",")
492 for _, r := range catlist {
493 ranges := strings.SplitN(r, ".", 2)
494 if len(ranges) > 1 {
495 catstart, err := parseLevelItem(ranges[0], category)
496 if err != nil {
497 return nil, err
498 }
499 catend, err := parseLevelItem(ranges[1], category)
500 if err != nil {
501 return nil, err
502 }
503 for i := catstart; i <= catend; i++ {
504 bitset.SetBit(bitset, int(i), 1)
505 }
506 } else {
507 cat, err := parseLevelItem(ranges[0], category)
508 if err != nil {
509 return nil, err
510 }
511 bitset.SetBit(bitset, int(cat), 1)
512 }
513 }
514
515 return bitset, nil
516 }
517
518
519 func parseLevelItem(s string, sep levelItem) (uint, error) {
520 if len(s) < minSensLen || levelItem(s[0]) != sep {
521 return 0, ErrLevelSyntax
522 }
523 val, err := strconv.ParseUint(s[1:], 10, 32)
524 if err != nil {
525 return 0, err
526 }
527
528 return uint(val), nil
529 }
530
531
532
533 func (l *level) parseLevel(levelStr string) error {
534 lvl := strings.SplitN(levelStr, ":", 2)
535 sens, err := parseLevelItem(lvl[0], sensitivity)
536 if err != nil {
537 return fmt.Errorf("failed to parse sensitivity: %w", err)
538 }
539 l.sens = sens
540 if len(lvl) > 1 {
541 cats, err := catsToBitset(lvl[1])
542 if err != nil {
543 return fmt.Errorf("failed to parse categories: %w", err)
544 }
545 l.cats = cats
546 }
547
548 return nil
549 }
550
551
552 func rangeStrToMLSRange(rangeStr string) (*mlsRange, error) {
553 r := &mlsRange{}
554 l := strings.SplitN(rangeStr, "-", 2)
555
556 switch len(l) {
557
558 case 2:
559 r.high = &level{}
560 if err := r.high.parseLevel(l[1]); err != nil {
561 return nil, fmt.Errorf("failed to parse high level %q: %w", l[1], err)
562 }
563 fallthrough
564
565 case 1:
566 r.low = &level{}
567 if err := r.low.parseLevel(l[0]); err != nil {
568 return nil, fmt.Errorf("failed to parse low level %q: %w", l[0], err)
569 }
570 }
571
572 if r.high == nil {
573 r.high = r.low
574 }
575
576 return r, nil
577 }
578
579
580
581 func bitsetToStr(c *big.Int) string {
582 var str string
583
584 length := 0
585 for i := int(c.TrailingZeroBits()); i < c.BitLen(); i++ {
586 if c.Bit(i) == 0 {
587 continue
588 }
589 if length == 0 {
590 if str != "" {
591 str += ","
592 }
593 str += "c" + strconv.Itoa(i)
594 }
595 if c.Bit(i+1) == 1 {
596 length++
597 continue
598 }
599 if length == 1 {
600 str += ",c" + strconv.Itoa(i)
601 } else if length > 1 {
602 str += ".c" + strconv.Itoa(i)
603 }
604 length = 0
605 }
606
607 return str
608 }
609
610 func (l *level) equal(l2 *level) bool {
611 if l2 == nil || l == nil {
612 return l == l2
613 }
614 if l2.sens != l.sens {
615 return false
616 }
617 if l2.cats == nil || l.cats == nil {
618 return l2.cats == l.cats
619 }
620 return l.cats.Cmp(l2.cats) == 0
621 }
622
623
624 func (m mlsRange) String() string {
625 low := "s" + strconv.Itoa(int(m.low.sens))
626 if m.low.cats != nil && m.low.cats.BitLen() > 0 {
627 low += ":" + bitsetToStr(m.low.cats)
628 }
629
630 if m.low.equal(m.high) {
631 return low
632 }
633
634 high := "s" + strconv.Itoa(int(m.high.sens))
635 if m.high.cats != nil && m.high.cats.BitLen() > 0 {
636 high += ":" + bitsetToStr(m.high.cats)
637 }
638
639 return low + "-" + high
640 }
641
642 func max(a, b uint) uint {
643 if a > b {
644 return a
645 }
646 return b
647 }
648
649 func min(a, b uint) uint {
650 if a < b {
651 return a
652 }
653 return b
654 }
655
656
657
658
659
660 func calculateGlbLub(sourceRange, targetRange string) (string, error) {
661 s, err := rangeStrToMLSRange(sourceRange)
662 if err != nil {
663 return "", err
664 }
665 t, err := rangeStrToMLSRange(targetRange)
666 if err != nil {
667 return "", err
668 }
669
670 if s.high.sens < t.low.sens || t.high.sens < s.low.sens {
671
672 return "", ErrIncomparable
673 }
674
675 outrange := &mlsRange{low: &level{}, high: &level{}}
676
677
678 outrange.low.sens = max(s.low.sens, t.low.sens)
679
680
681 outrange.high.sens = min(s.high.sens, t.high.sens)
682
683
684 if s.low.cats != nil && t.low.cats != nil {
685 outrange.low.cats = new(big.Int)
686 outrange.low.cats.And(s.low.cats, t.low.cats)
687 }
688 if s.high.cats != nil && t.high.cats != nil {
689 outrange.high.cats = new(big.Int)
690 outrange.high.cats.And(s.high.cats, t.high.cats)
691 }
692
693 return outrange.String(), nil
694 }
695
696 func readWriteCon(fpath string, val string) (string, error) {
697 if fpath == "" {
698 return "", ErrEmptyPath
699 }
700 f, err := os.OpenFile(fpath, os.O_RDWR, 0)
701 if err != nil {
702 return "", err
703 }
704 defer f.Close()
705
706 _, err = f.Write([]byte(val))
707 if err != nil {
708 return "", err
709 }
710
711 return readConFd(f)
712 }
713
714
715 func peerLabel(fd uintptr) (string, error) {
716 l, err := unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_PEERSEC)
717 if err != nil {
718 return "", &os.PathError{Op: "getsockopt", Path: "fd " + strconv.Itoa(int(fd)), Err: err}
719 }
720 return l, nil
721 }
722
723
724
725 func setKeyLabel(label string) error {
726 err := writeCon("/proc/self/attr/keycreate", label)
727 if errors.Is(err, os.ErrNotExist) {
728 return nil
729 }
730 if label == "" && errors.Is(err, os.ErrPermission) {
731 return nil
732 }
733 return err
734 }
735
736
737 func (c Context) get() string {
738 if l := c["level"]; l != "" {
739 return c["user"] + ":" + c["role"] + ":" + c["type"] + ":" + l
740 }
741 return c["user"] + ":" + c["role"] + ":" + c["type"]
742 }
743
744
745 func newContext(label string) (Context, error) {
746 c := make(Context)
747
748 if len(label) != 0 {
749 con := strings.SplitN(label, ":", 4)
750 if len(con) < 3 {
751 return c, ErrInvalidLabel
752 }
753 c["user"] = con[0]
754 c["role"] = con[1]
755 c["type"] = con[2]
756 if len(con) > 3 {
757 c["level"] = con[3]
758 }
759 }
760 return c, nil
761 }
762
763
764 func clearLabels() {
765 state.Lock()
766 state.mcsList = make(map[string]bool)
767 state.Unlock()
768 }
769
770
771 func reserveLabel(label string) {
772 if len(label) != 0 {
773 con := strings.SplitN(label, ":", 4)
774 if len(con) > 3 {
775 _ = mcsAdd(con[3])
776 }
777 }
778 }
779
780 func selinuxEnforcePath() string {
781 return filepath.Join(getSelinuxMountPoint(), "enforce")
782 }
783
784
785 func isMLSEnabled() bool {
786 enabledB, err := os.ReadFile(filepath.Join(getSelinuxMountPoint(), "mls"))
787 if err != nil {
788 return false
789 }
790 return bytes.Equal(enabledB, []byte{'1'})
791 }
792
793
794 func enforceMode() int {
795 var enforce int
796
797 enforceB, err := os.ReadFile(selinuxEnforcePath())
798 if err != nil {
799 return -1
800 }
801 enforce, err = strconv.Atoi(string(enforceB))
802 if err != nil {
803 return -1
804 }
805 return enforce
806 }
807
808
809
810 func setEnforceMode(mode int) error {
811
812 return os.WriteFile(selinuxEnforcePath(), []byte(strconv.Itoa(mode)), 0o644)
813 }
814
815
816
817
818 func defaultEnforceMode() int {
819 switch readConfig(selinuxTag) {
820 case "enforcing":
821 return Enforcing
822 case "permissive":
823 return Permissive
824 }
825 return Disabled
826 }
827
828 func mcsAdd(mcs string) error {
829 if mcs == "" {
830 return nil
831 }
832 state.Lock()
833 defer state.Unlock()
834 if state.mcsList[mcs] {
835 return ErrMCSAlreadyExists
836 }
837 state.mcsList[mcs] = true
838 return nil
839 }
840
841 func mcsDelete(mcs string) {
842 if mcs == "" {
843 return
844 }
845 state.Lock()
846 defer state.Unlock()
847 state.mcsList[mcs] = false
848 }
849
850 func intToMcs(id int, catRange uint32) string {
851 var (
852 SETSIZE = int(catRange)
853 TIER = SETSIZE
854 ORD = id
855 )
856
857 if id < 1 || id > 523776 {
858 return ""
859 }
860
861 for ORD > TIER {
862 ORD -= TIER
863 TIER--
864 }
865 TIER = SETSIZE - TIER
866 ORD += TIER
867 return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
868 }
869
870 func uniqMcs(catRange uint32) string {
871 var (
872 n uint32
873 c1, c2 uint32
874 mcs string
875 )
876
877 for {
878 _ = binary.Read(rand.Reader, binary.LittleEndian, &n)
879 c1 = n % catRange
880 _ = binary.Read(rand.Reader, binary.LittleEndian, &n)
881 c2 = n % catRange
882 if c1 == c2 {
883 continue
884 } else if c1 > c2 {
885 c1, c2 = c2, c1
886 }
887 mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
888 if err := mcsAdd(mcs); err != nil {
889 continue
890 }
891 break
892 }
893 return mcs
894 }
895
896
897
898 func releaseLabel(label string) {
899 if len(label) != 0 {
900 con := strings.SplitN(label, ":", 4)
901 if len(con) > 3 {
902 mcsDelete(con[3])
903 }
904 }
905 }
906
907
908 func roFileLabel() string {
909 return readOnlyFileLabel
910 }
911
912 func openContextFile() (*os.File, error) {
913 if f, err := os.Open(contextFile); err == nil {
914 return f, nil
915 }
916 return os.Open(filepath.Join(policyRoot(), "contexts", "lxc_contexts"))
917 }
918
919 func loadLabels() {
920 labels = make(map[string]string)
921 in, err := openContextFile()
922 if err != nil {
923 return
924 }
925 defer in.Close()
926
927 scanner := bufio.NewScanner(in)
928
929 for scanner.Scan() {
930 line := bytes.TrimSpace(scanner.Bytes())
931 if len(line) == 0 {
932
933 continue
934 }
935 if line[0] == ';' || line[0] == '#' {
936
937 continue
938 }
939 fields := bytes.SplitN(line, []byte{'='}, 2)
940 if len(fields) != 2 {
941 continue
942 }
943 key, val := bytes.TrimSpace(fields[0]), bytes.TrimSpace(fields[1])
944 labels[string(key)] = string(bytes.Trim(val, `"`))
945 }
946
947 con, _ := NewContext(labels["file"])
948 con["level"] = fmt.Sprintf("s0:c%d,c%d", maxCategory-2, maxCategory-1)
949 privContainerMountLabel = con.get()
950 reserveLabel(privContainerMountLabel)
951 }
952
953 func label(key string) string {
954 loadLabelsOnce.Do(func() {
955 loadLabels()
956 })
957 return labels[key]
958 }
959
960
961
962 func kvmContainerLabels() (string, string) {
963 processLabel := label("kvm_process")
964 if processLabel == "" {
965 processLabel = label("process")
966 }
967
968 return addMcs(processLabel, label("file"))
969 }
970
971
972
973 func initContainerLabels() (string, string) {
974 processLabel := label("init_process")
975 if processLabel == "" {
976 processLabel = label("process")
977 }
978
979 return addMcs(processLabel, label("file"))
980 }
981
982
983
984 func containerLabels() (processLabel string, fileLabel string) {
985 if !getEnabled() {
986 return "", ""
987 }
988
989 processLabel = label("process")
990 fileLabel = label("file")
991 readOnlyFileLabel = label("ro_file")
992
993 if processLabel == "" || fileLabel == "" {
994 return "", fileLabel
995 }
996
997 if readOnlyFileLabel == "" {
998 readOnlyFileLabel = fileLabel
999 }
1000
1001 return addMcs(processLabel, fileLabel)
1002 }
1003
1004 func addMcs(processLabel, fileLabel string) (string, string) {
1005 scon, _ := NewContext(processLabel)
1006 if scon["level"] != "" {
1007 mcs := uniqMcs(CategoryRange)
1008 scon["level"] = mcs
1009 processLabel = scon.Get()
1010 scon, _ = NewContext(fileLabel)
1011 scon["level"] = mcs
1012 fileLabel = scon.Get()
1013 }
1014 return processLabel, fileLabel
1015 }
1016
1017
1018 func securityCheckContext(val string) error {
1019
1020 return os.WriteFile(filepath.Join(getSelinuxMountPoint(), "context"), []byte(val), 0o644)
1021 }
1022
1023
1024
1025 func copyLevel(src, dest string) (string, error) {
1026 if src == "" {
1027 return "", nil
1028 }
1029 if err := SecurityCheckContext(src); err != nil {
1030 return "", err
1031 }
1032 if err := SecurityCheckContext(dest); err != nil {
1033 return "", err
1034 }
1035 scon, err := NewContext(src)
1036 if err != nil {
1037 return "", err
1038 }
1039 tcon, err := NewContext(dest)
1040 if err != nil {
1041 return "", err
1042 }
1043 mcsDelete(tcon["level"])
1044 _ = mcsAdd(scon["level"])
1045 tcon["level"] = scon["level"]
1046 return tcon.Get(), nil
1047 }
1048
1049
1050
1051
1052 func chcon(fpath string, label string, recurse bool) error {
1053 if fpath == "" {
1054 return ErrEmptyPath
1055 }
1056 if label == "" {
1057 return nil
1058 }
1059
1060 excludePaths := map[string]bool{
1061 "/": true,
1062 "/bin": true,
1063 "/boot": true,
1064 "/dev": true,
1065 "/etc": true,
1066 "/etc/passwd": true,
1067 "/etc/pki": true,
1068 "/etc/shadow": true,
1069 "/home": true,
1070 "/lib": true,
1071 "/lib64": true,
1072 "/media": true,
1073 "/opt": true,
1074 "/proc": true,
1075 "/root": true,
1076 "/run": true,
1077 "/sbin": true,
1078 "/srv": true,
1079 "/sys": true,
1080 "/tmp": true,
1081 "/usr": true,
1082 "/var": true,
1083 "/var/lib": true,
1084 "/var/log": true,
1085 }
1086
1087 if home := os.Getenv("HOME"); home != "" {
1088 excludePaths[home] = true
1089 }
1090
1091 if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" {
1092 if usr, err := user.Lookup(sudoUser); err == nil {
1093 excludePaths[usr.HomeDir] = true
1094 }
1095 }
1096
1097 if fpath != "/" {
1098 fpath = strings.TrimSuffix(fpath, "/")
1099 }
1100 if excludePaths[fpath] {
1101 return fmt.Errorf("SELinux relabeling of %s is not allowed", fpath)
1102 }
1103
1104 if !recurse {
1105 err := lSetFileLabel(fpath, label)
1106 if err != nil {
1107
1108 if errors.Is(err, os.ErrNotExist) {
1109 return nil
1110 }
1111
1112 flabel, nerr := lFileLabel(fpath)
1113 if nerr == nil && flabel == label {
1114 return nil
1115 }
1116
1117 if errors.Is(nerr, os.ErrNotExist) {
1118 return nil
1119 }
1120 return err
1121 }
1122 return nil
1123 }
1124
1125 return rchcon(fpath, label)
1126 }
1127
1128 func rchcon(fpath, label string) error {
1129 fastMode := false
1130
1131
1132 if cLabel, err := lFileLabel(fpath); err == nil && cLabel == label {
1133 fastMode = true
1134 }
1135 return pwalkdir.Walk(fpath, func(p string, _ fs.DirEntry, _ error) error {
1136 if fastMode {
1137 if cLabel, err := lFileLabel(fpath); err == nil && cLabel == label {
1138 return nil
1139 }
1140 }
1141 err := lSetFileLabel(p, label)
1142
1143 if errors.Is(err, os.ErrNotExist) {
1144 return nil
1145 }
1146 return err
1147 })
1148 }
1149
1150
1151
1152 func dupSecOpt(src string) ([]string, error) {
1153 if src == "" {
1154 return nil, nil
1155 }
1156 con, err := NewContext(src)
1157 if err != nil {
1158 return nil, err
1159 }
1160 if con["user"] == "" ||
1161 con["role"] == "" ||
1162 con["type"] == "" {
1163 return nil, nil
1164 }
1165 dup := []string{
1166 "user:" + con["user"],
1167 "role:" + con["role"],
1168 "type:" + con["type"],
1169 }
1170
1171 if con["level"] != "" {
1172 dup = append(dup, "level:"+con["level"])
1173 }
1174
1175 return dup, nil
1176 }
1177
1178
1179
1180
1181
1182 func findUserInContext(context Context, r io.Reader, verifier func(string) error) (string, error) {
1183 fromRole := context["role"]
1184 fromType := context["type"]
1185 scanner := bufio.NewScanner(r)
1186
1187 for scanner.Scan() {
1188 fromConns := strings.Fields(scanner.Text())
1189 if len(fromConns) == 0 {
1190
1191 continue
1192 }
1193
1194 line := fromConns[0]
1195
1196 if line[0] == ';' || line[0] == '#' {
1197
1198 continue
1199 }
1200
1201
1202
1203 lineArr := strings.SplitN(line, ":", 4)
1204
1205 if len(lineArr) != 3 ||
1206 lineArr[0] != fromRole ||
1207 lineArr[1] != fromType {
1208 continue
1209 }
1210
1211 for _, cc := range fromConns[1:] {
1212 toConns := strings.SplitN(cc, ":", 4)
1213 if len(toConns) != 3 {
1214 continue
1215 }
1216
1217 context["role"] = toConns[0]
1218 context["type"] = toConns[1]
1219
1220 outConn := context.get()
1221 if err := verifier(outConn); err != nil {
1222 continue
1223 }
1224
1225 return outConn, nil
1226 }
1227 }
1228 if err := scanner.Err(); err != nil {
1229 return "", fmt.Errorf("failed to scan for context: %w", err)
1230 }
1231
1232 return "", nil
1233 }
1234
1235 func getDefaultContextFromReaders(c *defaultSECtx) (string, error) {
1236 if c.verifier == nil {
1237 return "", ErrVerifierNil
1238 }
1239
1240 context, err := newContext(c.scon)
1241 if err != nil {
1242 return "", fmt.Errorf("failed to create label for %s: %w", c.scon, err)
1243 }
1244
1245
1246 context["user"] = c.user
1247 context["level"] = c.level
1248
1249 conn, err := findUserInContext(context, c.userRdr, c.verifier)
1250 if err != nil {
1251 return "", err
1252 }
1253
1254 if conn != "" {
1255 return conn, nil
1256 }
1257
1258 conn, err = findUserInContext(context, c.defaultRdr, c.verifier)
1259 if err != nil {
1260 return "", err
1261 }
1262
1263 if conn != "" {
1264 return conn, nil
1265 }
1266
1267 return "", fmt.Errorf("context %q not found: %w", c.scon, ErrContextMissing)
1268 }
1269
1270 func getDefaultContextWithLevel(user, level, scon string) (string, error) {
1271 userPath := filepath.Join(policyRoot(), selinuxUsersDir, user)
1272 fu, err := os.Open(userPath)
1273 if err != nil {
1274 return "", err
1275 }
1276 defer fu.Close()
1277
1278 defaultPath := filepath.Join(policyRoot(), defaultContexts)
1279 fd, err := os.Open(defaultPath)
1280 if err != nil {
1281 return "", err
1282 }
1283 defer fd.Close()
1284
1285 c := defaultSECtx{
1286 user: user,
1287 level: level,
1288 scon: scon,
1289 userRdr: fu,
1290 defaultRdr: fd,
1291 verifier: securityCheckContext,
1292 }
1293
1294 return getDefaultContextFromReaders(&c)
1295 }
1296
View as plain text