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