github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/pkg/selinux/selinux.go (about)

     1  // Copyright 2014,2015 Red Hat, Inc
     2  // Copyright 2014,2015 Docker, Inc
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  // +build linux
    17  
    18  package selinux
    19  
    20  import (
    21  	"bufio"
    22  	"crypto/rand"
    23  	"encoding/binary"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"os"
    28  	"path/filepath"
    29  	"regexp"
    30  	"strconv"
    31  	"strings"
    32  	"syscall"
    33  
    34  	"github.com/coreos/rkt/pkg/fileutil"
    35  	"github.com/hashicorp/errwrap"
    36  )
    37  
    38  const (
    39  	Enforcing        = 1
    40  	Permissive       = 0
    41  	Disabled         = -1
    42  	selinuxDir       = "/etc/selinux/"
    43  	selinuxConfig    = selinuxDir + "config"
    44  	selinuxTypeTag   = "SELINUXTYPE"
    45  	selinuxTag       = "SELINUX"
    46  	selinuxPath      = "/sys/fs/selinux"
    47  	xattrNameSelinux = "security.selinux"
    48  	stRdOnly         = 0x01
    49  )
    50  
    51  var (
    52  	assignRegex           = regexp.MustCompile(`^([^=]+)=(.*)$`)
    53  	spaceRegex            = regexp.MustCompile(`^([^=]+) (.*)$`)
    54  	selinuxfs             = "unknown"
    55  	selinuxEnabled        = false // Stores whether selinux is currently enabled
    56  	selinuxEnabledChecked = false // Stores whether selinux enablement has been checked or established yet
    57  	mcsdir                = ""    // Directory to use for MCS storage
    58  )
    59  
    60  type SELinuxContext map[string]string
    61  
    62  // SetDisabled disables selinux support for the package
    63  func SetDisabled() {
    64  	selinuxEnabled, selinuxEnabledChecked = false, true
    65  }
    66  
    67  // getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
    68  // filesystem or an empty string if no mountpoint is found.  Selinuxfs is
    69  // a proc-like pseudo-filesystem that exposes the selinux policy API to
    70  // processes.  The existence of an selinuxfs mount is used to determine
    71  // whether selinux is currently enabled or not.
    72  func getSelinuxMountPoint() string {
    73  	if _, err := os.Stat(selinuxPath); os.IsNotExist(err) {
    74  		return ""
    75  	}
    76  	return selinuxPath
    77  }
    78  
    79  // SelinuxEnabled returns whether selinux is currently enabled.
    80  func SelinuxEnabled() bool {
    81  	if selinuxEnabledChecked {
    82  		return selinuxEnabled
    83  	}
    84  	selinuxEnabledChecked = true
    85  	if fs := getSelinuxMountPoint(); fs != "" {
    86  		if con, _ := Getcon(); con != "kernel" {
    87  			selinuxEnabled = true
    88  		}
    89  	}
    90  	return selinuxEnabled
    91  }
    92  
    93  func readConfig(target string) (value string) {
    94  	var (
    95  		val, key string
    96  		bufin    *bufio.Reader
    97  	)
    98  
    99  	in, err := os.Open(selinuxConfig)
   100  	if err != nil {
   101  		return ""
   102  	}
   103  	defer in.Close()
   104  
   105  	bufin = bufio.NewReader(in)
   106  
   107  	for done := false; !done; {
   108  		var line string
   109  		if line, err = bufin.ReadString('\n'); err != nil {
   110  			if err != io.EOF {
   111  				return ""
   112  			}
   113  			done = true
   114  		}
   115  		line = strings.TrimSpace(line)
   116  		if len(line) == 0 {
   117  			// Skip blank lines
   118  			continue
   119  		}
   120  		if line[0] == ';' || line[0] == '#' {
   121  			// Skip comments
   122  			continue
   123  		}
   124  		if groups := assignRegex.FindStringSubmatch(line); groups != nil {
   125  			key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
   126  			if key == target {
   127  				return strings.Trim(val, "\"")
   128  			}
   129  		}
   130  	}
   131  	return ""
   132  }
   133  
   134  func getSELinuxPolicyRoot() string {
   135  	return selinuxDir + readConfig(selinuxTypeTag)
   136  }
   137  
   138  func readCon(name string) (string, error) {
   139  	var val string
   140  
   141  	in, err := os.Open(name)
   142  	if err != nil {
   143  		return "", err
   144  	}
   145  	defer in.Close()
   146  
   147  	_, err = fmt.Fscanf(in, "%s", &val)
   148  	return val, err
   149  }
   150  
   151  type SelinuxError struct {
   152  	Errno int
   153  	Prob  string
   154  }
   155  
   156  const (
   157  	InvalidContext = iota
   158  )
   159  
   160  func (e *SelinuxError) Error() string {
   161  	return fmt.Sprintf("SELinux: %s", e.Prob)
   162  }
   163  
   164  // Setfilecon sets the SELinux label for this path or returns an error.
   165  func Setfilecon(path string, scon string) error {
   166  	err := fileutil.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0)
   167  	if err != syscall.EINVAL {
   168  		return err
   169  	}
   170  	return &SelinuxError{InvalidContext, "Invalid Context"}
   171  }
   172  
   173  // Getfilecon returns the SELinux label for this path or returns an error.
   174  func Getfilecon(path string) (string, error) {
   175  	con, err := fileutil.Lgetxattr(path, xattrNameSelinux)
   176  
   177  	// Trim the NUL byte at the end of the byte buffer, if present.
   178  	if con[len(con)-1] == '\x00' {
   179  		con = con[:len(con)-1]
   180  	}
   181  	return string(con), err
   182  }
   183  
   184  func Setfscreatecon(scon string) error {
   185  	return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), scon)
   186  }
   187  
   188  func Getfscreatecon() (string, error) {
   189  	return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()))
   190  }
   191  
   192  // Getcon returns the SELinux label of the current process thread, or an error.
   193  func Getcon() (string, error) {
   194  	return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid()))
   195  }
   196  
   197  // Getpidcon returns the SELinux label of the given pid, or an error.
   198  func Getpidcon(pid int) (string, error) {
   199  	return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
   200  }
   201  
   202  func Getexeccon() (string, error) {
   203  	return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()))
   204  }
   205  
   206  func writeCon(name string, val string) error {
   207  	out, err := os.OpenFile(name, os.O_WRONLY, 0)
   208  	if err != nil {
   209  		return err
   210  	}
   211  	defer out.Close()
   212  
   213  	if val != "" {
   214  		_, err = out.Write([]byte(val))
   215  	} else {
   216  		_, err = out.Write(nil)
   217  	}
   218  	return err
   219  }
   220  
   221  func Setexeccon(scon string) error {
   222  	return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon)
   223  }
   224  
   225  func (c SELinuxContext) Get() string {
   226  	return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
   227  }
   228  
   229  func NewContext(scon string) SELinuxContext {
   230  	c := make(SELinuxContext)
   231  
   232  	if len(scon) != 0 {
   233  		con := strings.SplitN(scon, ":", 4)
   234  		c["user"] = con[0]
   235  		c["role"] = con[1]
   236  		c["type"] = con[2]
   237  		c["level"] = con[3]
   238  	}
   239  	return c
   240  }
   241  
   242  func ReserveLabel(scon string) {
   243  	if len(scon) != 0 {
   244  		con := strings.SplitN(scon, ":", 4)
   245  		mcsAdd(con[3])
   246  	}
   247  }
   248  
   249  func SelinuxGetEnforce() int {
   250  	var enforce int
   251  
   252  	enforceS, err := readCon(fmt.Sprintf("%s/enforce", selinuxPath))
   253  	if err != nil {
   254  		return -1
   255  	}
   256  
   257  	enforce, err = strconv.Atoi(string(enforceS))
   258  	if err != nil {
   259  		return -1
   260  	}
   261  	return enforce
   262  }
   263  
   264  func SelinuxGetEnforceMode() int {
   265  	switch readConfig(selinuxTag) {
   266  	case "enforcing":
   267  		return Enforcing
   268  	case "permissive":
   269  		return Permissive
   270  	}
   271  	return Disabled
   272  }
   273  
   274  func mcsPath(mcs string) string {
   275  	filename := fmt.Sprintf("%s/%s", mcsdir, mcs)
   276  	return filename
   277  }
   278  
   279  func mcsAdd(mcs string) error {
   280  	filename := mcsPath(mcs)
   281  	file, err := os.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_RDONLY, 0644)
   282  	if err != nil {
   283  		if os.IsExist(err) {
   284  			return fmt.Errorf("MCS label already exists")
   285  		} else {
   286  			return errwrap.Wrap(errors.New("unable to test MCS"), err)
   287  		}
   288  	}
   289  	file.Close()
   290  	return nil
   291  }
   292  
   293  func mcsDelete(mcs string) {
   294  	filename := mcsPath(mcs)
   295  	os.Remove(filename)
   296  }
   297  
   298  func mcsExists(mcs string) bool {
   299  	filename := mcsPath(mcs)
   300  	_, err := os.Stat(filename)
   301  	if err == nil {
   302  		return true
   303  	}
   304  	return false
   305  }
   306  
   307  func IntToMcs(id int, catRange uint32) string {
   308  	var (
   309  		SETSIZE = int(catRange)
   310  		TIER    = SETSIZE
   311  		ORD     = id
   312  	)
   313  
   314  	if id < 1 || id > 523776 {
   315  		return ""
   316  	}
   317  
   318  	for ORD > TIER {
   319  		ORD = ORD - TIER
   320  		TIER -= 1
   321  	}
   322  	TIER = SETSIZE - TIER
   323  	ORD = ORD + TIER
   324  	return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
   325  }
   326  
   327  func uniqMcs(catRange uint32) string {
   328  	var (
   329  		n      uint32
   330  		c1, c2 uint32
   331  		mcs    string
   332  	)
   333  
   334  	for {
   335  		binary.Read(rand.Reader, binary.LittleEndian, &n)
   336  		c1 = n % catRange
   337  		binary.Read(rand.Reader, binary.LittleEndian, &n)
   338  		c2 = n % catRange
   339  		if c1 == c2 {
   340  			continue
   341  		} else {
   342  			if c1 > c2 {
   343  				t := c1
   344  				c1 = c2
   345  				c2 = t
   346  			}
   347  		}
   348  		mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
   349  		if err := mcsAdd(mcs); err != nil {
   350  			continue
   351  		}
   352  		break
   353  	}
   354  	return mcs
   355  }
   356  
   357  func FreeLxcContexts(scon string) {
   358  	if len(scon) != 0 {
   359  		con := strings.SplitN(scon, ":", 4)
   360  		mcsDelete(con[3])
   361  	}
   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  		}
   409  	}
   410  
   411  	if processLabel == "" || fileLabel == "" {
   412  		return "", ""
   413  	}
   414  
   415  exit:
   416  	//	mcs := IntToMcs(os.Getpid(), 1024)
   417  	mcs := uniqMcs(1024)
   418  	scon := NewContext(processLabel)
   419  	scon["level"] = mcs
   420  	processLabel = scon.Get()
   421  	scon = NewContext(fileLabel)
   422  	scon["level"] = mcs
   423  	fileLabel = scon.Get()
   424  	return processLabel, fileLabel
   425  }
   426  
   427  func SecurityCheckContext(val string) error {
   428  	return writeCon(fmt.Sprintf("%s.context", selinuxPath), val)
   429  }
   430  
   431  func CopyLevel(src, dest string) (string, error) {
   432  	if src == "" {
   433  		return "", nil
   434  	}
   435  	if err := SecurityCheckContext(src); err != nil {
   436  		return "", err
   437  	}
   438  	if err := SecurityCheckContext(dest); err != nil {
   439  		return "", err
   440  	}
   441  	scon := NewContext(src)
   442  	tcon := NewContext(dest)
   443  	mcsDelete(tcon["level"])
   444  	mcsAdd(scon["level"])
   445  	tcon["level"] = scon["level"]
   446  	return tcon.Get(), nil
   447  }
   448  
   449  // Prevent users from relabeling system files
   450  func badPrefix(fpath string) error {
   451  	var badprefixes = []string{"/usr"}
   452  
   453  	for _, prefix := range badprefixes {
   454  		if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) {
   455  			return fmt.Errorf("Relabeling content in %s is not allowed.", prefix)
   456  		}
   457  	}
   458  	return nil
   459  }
   460  
   461  // Change the fpath file object to the SELinux label scon.
   462  // If the fpath is a directory and recurse is true Chcon will walk the
   463  // directory tree setting the label
   464  func Chcon(fpath string, scon string, recurse bool) error {
   465  	if scon == "" {
   466  		return nil
   467  	}
   468  	if err := badPrefix(fpath); err != nil {
   469  		return err
   470  	}
   471  	callback := func(p string, info os.FileInfo, err error) error {
   472  		return Setfilecon(p, scon)
   473  	}
   474  
   475  	if recurse {
   476  		return filepath.Walk(fpath, callback)
   477  	}
   478  
   479  	return Setfilecon(fpath, scon)
   480  }
   481  
   482  // DupSecOpt takes an SELinux process label and returns security options that
   483  // can will set the SELinux Type and Level for future container processes
   484  func DupSecOpt(src string) []string {
   485  	if src == "" {
   486  		return nil
   487  	}
   488  	con := NewContext(src)
   489  	if con["user"] == "" ||
   490  		con["role"] == "" ||
   491  		con["type"] == "" ||
   492  		con["level"] == "" {
   493  		return nil
   494  	}
   495  	return []string{"label:user:" + con["user"],
   496  		"label:role:" + con["role"],
   497  		"label:type:" + con["type"],
   498  		"label:level:" + con["level"]}
   499  }
   500  
   501  // DisableSecOpt returns a security opt that can be used to disabling SELinux
   502  // labeling support for future container processes
   503  func DisableSecOpt() []string {
   504  	return []string{"label:disable"}
   505  }
   506  
   507  // Set the directory used for storage of used MCS contexts
   508  func SetMCSDir(arg string) error {
   509  	mcsdir = arg
   510  	err := os.MkdirAll(mcsdir, 0755)
   511  	return err
   512  }