github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/mergeCode/runc/libcontainer/selinux/selinux.go (about)

     1  // +build linux
     2  
     3  package selinux
     4  
     5  import (
     6  	"bufio"
     7  	"crypto/rand"
     8  	"encoding/binary"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"syscall"
    18  
    19  	"github.com/opencontainers/runc/libcontainer/system"
    20  )
    21  
    22  const (
    23  	Enforcing        = 1
    24  	Permissive       = 0
    25  	Disabled         = -1
    26  	selinuxDir       = "/etc/selinux/"
    27  	selinuxConfig    = selinuxDir + "config"
    28  	selinuxTypeTag   = "SELINUXTYPE"
    29  	selinuxTag       = "SELINUX"
    30  	selinuxPath      = "/sys/fs/selinux"
    31  	xattrNameSelinux = "security.selinux"
    32  	stRdOnly         = 0x01
    33  )
    34  
    35  var (
    36  	assignRegex           = regexp.MustCompile(`^([^=]+)=(.*)$`)
    37  	mcsList               = make(map[string]bool)
    38  	mcsLock               sync.Mutex
    39  	selinuxfs             = "unknown"
    40  	selinuxEnabled        = false // Stores whether selinux is currently enabled
    41  	selinuxEnabledChecked = false // Stores whether selinux enablement has been checked or established yet
    42  )
    43  
    44  type SELinuxContext map[string]string
    45  
    46  // SetDisabled disables selinux support for the package
    47  func SetDisabled() {
    48  	selinuxEnabled, selinuxEnabledChecked = false, true
    49  }
    50  
    51  // getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
    52  // filesystem or an empty string if no mountpoint is found.  Selinuxfs is
    53  // a proc-like pseudo-filesystem that exposes the selinux policy API to
    54  // processes.  The existence of an selinuxfs mount is used to determine
    55  // whether selinux is currently enabled or not.
    56  func getSelinuxMountPoint() string {
    57  	if selinuxfs != "unknown" {
    58  		return selinuxfs
    59  	}
    60  	selinuxfs = ""
    61  
    62  	f, err := os.Open("/proc/self/mountinfo")
    63  	if err != nil {
    64  		return selinuxfs
    65  	}
    66  	defer f.Close()
    67  
    68  	scanner := bufio.NewScanner(f)
    69  	for scanner.Scan() {
    70  		txt := scanner.Text()
    71  		// Safe as mountinfo encodes mountpoints with spaces as \040.
    72  		sepIdx := strings.Index(txt, " - ")
    73  		if sepIdx == -1 {
    74  			continue
    75  		}
    76  		if !strings.Contains(txt[sepIdx:], "selinuxfs") {
    77  			continue
    78  		}
    79  		fields := strings.Split(txt, " ")
    80  		if len(fields) < 5 {
    81  			continue
    82  		}
    83  		selinuxfs = fields[4]
    84  		break
    85  	}
    86  
    87  	if selinuxfs != "" {
    88  		var buf syscall.Statfs_t
    89  		syscall.Statfs(selinuxfs, &buf)
    90  		if (buf.Flags & stRdOnly) == 1 {
    91  			selinuxfs = ""
    92  		}
    93  	}
    94  	return selinuxfs
    95  }
    96  
    97  // SelinuxEnabled returns whether selinux is currently enabled.
    98  func SelinuxEnabled() bool {
    99  	if selinuxEnabledChecked {
   100  		return selinuxEnabled
   101  	}
   102  	selinuxEnabledChecked = true
   103  	if fs := getSelinuxMountPoint(); fs != "" {
   104  		if con, _ := Getcon(); con != "kernel" {
   105  			selinuxEnabled = true
   106  		}
   107  	}
   108  	return selinuxEnabled
   109  }
   110  
   111  func readConfig(target string) (value string) {
   112  	var (
   113  		val, key string
   114  		bufin    *bufio.Reader
   115  	)
   116  
   117  	in, err := os.Open(selinuxConfig)
   118  	if err != nil {
   119  		return ""
   120  	}
   121  	defer in.Close()
   122  
   123  	bufin = bufio.NewReader(in)
   124  
   125  	for done := false; !done; {
   126  		var line string
   127  		if line, err = bufin.ReadString('\n'); err != nil {
   128  			if err != io.EOF {
   129  				return ""
   130  			}
   131  			done = true
   132  		}
   133  		line = strings.TrimSpace(line)
   134  		if len(line) == 0 {
   135  			// Skip blank lines
   136  			continue
   137  		}
   138  		if line[0] == ';' || line[0] == '#' {
   139  			// Skip comments
   140  			continue
   141  		}
   142  		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
   143  			key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
   144  			if key == target {
   145  				return strings.Trim(val, "\"")
   146  			}
   147  		}
   148  	}
   149  	return ""
   150  }
   151  
   152  func getSELinuxPolicyRoot() string {
   153  	return selinuxDir + readConfig(selinuxTypeTag)
   154  }
   155  
   156  func readCon(name string) (string, error) {
   157  	var val string
   158  
   159  	in, err := os.Open(name)
   160  	if err != nil {
   161  		return "", err
   162  	}
   163  	defer in.Close()
   164  
   165  	_, err = fmt.Fscanf(in, "%s", &val)
   166  	return val, err
   167  }
   168  
   169  // Setfilecon sets the SELinux label for this path or returns an error.
   170  func Setfilecon(path string, scon string) error {
   171  	return system.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0)
   172  }
   173  
   174  // Getfilecon returns the SELinux label for this path or returns an error.
   175  func Getfilecon(path string) (string, error) {
   176  	con, err := system.Lgetxattr(path, xattrNameSelinux)
   177  	if err != nil {
   178  		return "", err
   179  	}
   180  	// Trim the NUL byte at the end of the byte buffer, if present.
   181  	if len(con) > 0 && con[len(con)-1] == '\x00' {
   182  		con = con[:len(con)-1]
   183  	}
   184  	return string(con), nil
   185  }
   186  
   187  func Setfscreatecon(scon string) error {
   188  	return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), scon)
   189  }
   190  
   191  func Getfscreatecon() (string, error) {
   192  	return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()))
   193  }
   194  
   195  // Getcon returns the SELinux label of the current process thread, or an error.
   196  func Getcon() (string, error) {
   197  	return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid()))
   198  }
   199  
   200  // Getpidcon returns the SELinux label of the given pid, or an error.
   201  func Getpidcon(pid int) (string, error) {
   202  	return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
   203  }
   204  
   205  func Getexeccon() (string, error) {
   206  	return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()))
   207  }
   208  
   209  func writeCon(name string, val string) error {
   210  	out, err := os.OpenFile(name, os.O_WRONLY, 0)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	defer out.Close()
   215  
   216  	if val != "" {
   217  		_, err = out.Write([]byte(val))
   218  	} else {
   219  		_, err = out.Write(nil)
   220  	}
   221  	return err
   222  }
   223  
   224  func Setexeccon(scon string) error {
   225  	return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon)
   226  }
   227  
   228  func (c SELinuxContext) Get() string {
   229  	return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
   230  }
   231  
   232  func NewContext(scon string) SELinuxContext {
   233  	c := make(SELinuxContext)
   234  
   235  	if len(scon) != 0 {
   236  		con := strings.SplitN(scon, ":", 4)
   237  		c["user"] = con[0]
   238  		c["role"] = con[1]
   239  		c["type"] = con[2]
   240  		c["level"] = con[3]
   241  	}
   242  	return c
   243  }
   244  
   245  func ReserveLabel(scon string) {
   246  	if len(scon) != 0 {
   247  		con := strings.SplitN(scon, ":", 4)
   248  		mcsAdd(con[3])
   249  	}
   250  }
   251  
   252  func selinuxEnforcePath() string {
   253  	return fmt.Sprintf("%s/enforce", selinuxPath)
   254  }
   255  
   256  func SelinuxGetEnforce() int {
   257  	var enforce int
   258  
   259  	enforceS, err := readCon(selinuxEnforcePath())
   260  	if err != nil {
   261  		return -1
   262  	}
   263  
   264  	enforce, err = strconv.Atoi(string(enforceS))
   265  	if err != nil {
   266  		return -1
   267  	}
   268  	return enforce
   269  }
   270  
   271  func SelinuxSetEnforce(mode int) error {
   272  	return writeCon(selinuxEnforcePath(), fmt.Sprintf("%d", mode))
   273  }
   274  
   275  func SelinuxGetEnforceMode() int {
   276  	switch readConfig(selinuxTag) {
   277  	case "enforcing":
   278  		return Enforcing
   279  	case "permissive":
   280  		return Permissive
   281  	}
   282  	return Disabled
   283  }
   284  
   285  func mcsAdd(mcs string) error {
   286  	mcsLock.Lock()
   287  	defer mcsLock.Unlock()
   288  	if mcsList[mcs] {
   289  		return fmt.Errorf("MCS Label already exists")
   290  	}
   291  	mcsList[mcs] = true
   292  	return nil
   293  }
   294  
   295  func mcsDelete(mcs string) {
   296  	mcsLock.Lock()
   297  	mcsList[mcs] = false
   298  	mcsLock.Unlock()
   299  }
   300  
   301  func IntToMcs(id int, catRange uint32) string {
   302  	var (
   303  		SETSIZE = int(catRange)
   304  		TIER    = SETSIZE
   305  		ORD     = id
   306  	)
   307  
   308  	if id < 1 || id > 523776 {
   309  		return ""
   310  	}
   311  
   312  	for ORD > TIER {
   313  		ORD = ORD - TIER
   314  		TIER--
   315  	}
   316  	TIER = SETSIZE - TIER
   317  	ORD = ORD + TIER
   318  	return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
   319  }
   320  
   321  func uniqMcs(catRange uint32) string {
   322  	var (
   323  		n      uint32
   324  		c1, c2 uint32
   325  		mcs    string
   326  	)
   327  
   328  	for {
   329  		binary.Read(rand.Reader, binary.LittleEndian, &n)
   330  		c1 = n % catRange
   331  		binary.Read(rand.Reader, binary.LittleEndian, &n)
   332  		c2 = n % catRange
   333  		if c1 == c2 {
   334  			continue
   335  		} else {
   336  			if c1 > c2 {
   337  				t := c1
   338  				c1 = c2
   339  				c2 = t
   340  			}
   341  		}
   342  		mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
   343  		if err := mcsAdd(mcs); err != nil {
   344  			continue
   345  		}
   346  		break
   347  	}
   348  	return mcs
   349  }
   350  
   351  func FreeLxcContexts(scon string) {
   352  	if len(scon) != 0 {
   353  		con := strings.SplitN(scon, ":", 4)
   354  		mcsDelete(con[3])
   355  	}
   356  }
   357  
   358  var roFileLabel string
   359  
   360  func GetROFileLabel() (fileLabel string) {
   361  	return roFileLabel
   362  }
   363  
   364  func GetLxcContexts() (processLabel string, fileLabel string) {
   365  	var (
   366  		val, key string
   367  		bufin    *bufio.Reader
   368  	)
   369  
   370  	if !SelinuxEnabled() {
   371  		return "", ""
   372  	}
   373  	lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot())
   374  	in, err := os.Open(lxcPath)
   375  	if err != nil {
   376  		return "", ""
   377  	}
   378  	defer in.Close()
   379  
   380  	bufin = bufio.NewReader(in)
   381  
   382  	for done := false; !done; {
   383  		var line string
   384  		if line, err = bufin.ReadString('\n'); err != nil {
   385  			if err == io.EOF {
   386  				done = true
   387  			} else {
   388  				goto exit
   389  			}
   390  		}
   391  		line = strings.TrimSpace(line)
   392  		if len(line) == 0 {
   393  			// Skip blank lines
   394  			continue
   395  		}
   396  		if line[0] == ';' || line[0] == '#' {
   397  			// Skip comments
   398  			continue
   399  		}
   400  		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
   401  			key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
   402  			if key == "process" {
   403  				processLabel = strings.Trim(val, "\"")
   404  			}
   405  			if key == "file" {
   406  				fileLabel = strings.Trim(val, "\"")
   407  			}
   408  			if key == "ro_file" {
   409  				roFileLabel = strings.Trim(val, "\"")
   410  			}
   411  		}
   412  	}
   413  
   414  	if processLabel == "" || fileLabel == "" {
   415  		return "", ""
   416  	}
   417  
   418  	if roFileLabel == "" {
   419  		roFileLabel = fileLabel
   420  	}
   421  exit:
   422  	//	mcs := IntToMcs(os.Getpid(), 1024)
   423  	mcs := uniqMcs(1024)
   424  	scon := NewContext(processLabel)
   425  	scon["level"] = mcs
   426  	processLabel = scon.Get()
   427  	scon = NewContext(fileLabel)
   428  	scon["level"] = mcs
   429  	fileLabel = scon.Get()
   430  	return processLabel, fileLabel
   431  }
   432  
   433  func SecurityCheckContext(val string) error {
   434  	return writeCon(fmt.Sprintf("%s.context", selinuxPath), val)
   435  }
   436  
   437  func CopyLevel(src, dest string) (string, error) {
   438  	if src == "" {
   439  		return "", nil
   440  	}
   441  	if err := SecurityCheckContext(src); err != nil {
   442  		return "", err
   443  	}
   444  	if err := SecurityCheckContext(dest); err != nil {
   445  		return "", err
   446  	}
   447  	scon := NewContext(src)
   448  	tcon := NewContext(dest)
   449  	mcsDelete(tcon["level"])
   450  	mcsAdd(scon["level"])
   451  	tcon["level"] = scon["level"]
   452  	return tcon.Get(), nil
   453  }
   454  
   455  // Prevent users from relabing system files
   456  func badPrefix(fpath string) error {
   457  	var badprefixes = []string{"/usr"}
   458  
   459  	for _, prefix := range badprefixes {
   460  		if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) {
   461  			return fmt.Errorf("Relabeling content in %s is not allowed.", prefix)
   462  		}
   463  	}
   464  	return nil
   465  }
   466  
   467  // Chcon changes the fpath file object to the SELinux label scon.
   468  // If the fpath is a directory and recurse is true Chcon will walk the
   469  // directory tree setting the label
   470  func Chcon(fpath string, scon string, recurse bool) error {
   471  	if scon == "" {
   472  		return nil
   473  	}
   474  	if err := badPrefix(fpath); err != nil {
   475  		return err
   476  	}
   477  	callback := func(p string, info os.FileInfo, err error) error {
   478  		return Setfilecon(p, scon)
   479  	}
   480  
   481  	if recurse {
   482  		return filepath.Walk(fpath, callback)
   483  	}
   484  
   485  	return Setfilecon(fpath, scon)
   486  }
   487  
   488  // DupSecOpt takes an SELinux process label and returns security options that
   489  // can will set the SELinux Type and Level for future container processes
   490  func DupSecOpt(src string) []string {
   491  	if src == "" {
   492  		return nil
   493  	}
   494  	con := NewContext(src)
   495  	if con["user"] == "" ||
   496  		con["role"] == "" ||
   497  		con["type"] == "" ||
   498  		con["level"] == "" {
   499  		return nil
   500  	}
   501  	return []string{"label=user:" + con["user"],
   502  		"label=role:" + con["role"],
   503  		"label=type:" + con["type"],
   504  		"label=level:" + con["level"]}
   505  }
   506  
   507  // DisableSecOpt returns a security opt that can be used to disabling SELinux
   508  // labeling support for future container processes
   509  func DisableSecOpt() []string {
   510  	return []string{"label=disable"}
   511  }