|
@@ -28,6 +28,8 @@ const (
|
|
|
minSensLen = 2
|
|
|
contextFile = "/usr/share/containers/selinux/contexts"
|
|
|
selinuxDir = "/etc/selinux/"
|
|
|
+ selinuxUsersDir = "contexts/users"
|
|
|
+ defaultContexts = "contexts/default_contexts"
|
|
|
selinuxConfig = selinuxDir + "config"
|
|
|
selinuxfsMount = "/sys/fs/selinux"
|
|
|
selinuxTypeTag = "SELINUXTYPE"
|
|
@@ -35,6 +37,8 @@ const (
|
|
|
xattrNameSelinux = "security.selinux"
|
|
|
)
|
|
|
|
|
|
+var policyRoot = filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
|
|
|
+
|
|
|
type selinuxState struct {
|
|
|
enabledSet bool
|
|
|
enabled bool
|
|
@@ -54,6 +58,13 @@ type mlsRange struct {
|
|
|
high *level
|
|
|
}
|
|
|
|
|
|
+type defaultSECtx struct {
|
|
|
+ user, level, scon string
|
|
|
+ userRdr, defaultRdr io.Reader
|
|
|
+
|
|
|
+ verifier func(string) error
|
|
|
+}
|
|
|
+
|
|
|
type levelItem byte
|
|
|
|
|
|
const (
|
|
@@ -111,7 +122,7 @@ func verifySELinuxfsMount(mnt string) bool {
|
|
|
if err == nil {
|
|
|
break
|
|
|
}
|
|
|
- if err == unix.EAGAIN {
|
|
|
+ if err == unix.EAGAIN || err == unix.EINTR {
|
|
|
continue
|
|
|
}
|
|
|
return false
|
|
@@ -205,28 +216,16 @@ func getEnabled() bool {
|
|
|
}
|
|
|
|
|
|
func readConfig(target string) string {
|
|
|
- var (
|
|
|
- val, key string
|
|
|
- bufin *bufio.Reader
|
|
|
- )
|
|
|
-
|
|
|
in, err := os.Open(selinuxConfig)
|
|
|
if err != nil {
|
|
|
return ""
|
|
|
}
|
|
|
defer in.Close()
|
|
|
|
|
|
- bufin = bufio.NewReader(in)
|
|
|
+ scanner := bufio.NewScanner(in)
|
|
|
|
|
|
- for done := false; !done; {
|
|
|
- var line string
|
|
|
- if line, err = bufin.ReadString('\n'); err != nil {
|
|
|
- if err != io.EOF {
|
|
|
- return ""
|
|
|
- }
|
|
|
- done = true
|
|
|
- }
|
|
|
- line = strings.TrimSpace(line)
|
|
|
+ for scanner.Scan() {
|
|
|
+ line := strings.TrimSpace(scanner.Text())
|
|
|
if len(line) == 0 {
|
|
|
// Skip blank lines
|
|
|
continue
|
|
@@ -236,7 +235,7 @@ func readConfig(target string) string {
|
|
|
continue
|
|
|
}
|
|
|
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
|
|
|
- key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
|
|
|
+ key, val := strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
|
|
|
if key == target {
|
|
|
return strings.Trim(val, "\"")
|
|
|
}
|
|
@@ -245,15 +244,17 @@ func readConfig(target string) string {
|
|
|
return ""
|
|
|
}
|
|
|
|
|
|
-func getSELinuxPolicyRoot() string {
|
|
|
- return filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
|
|
|
-}
|
|
|
-
|
|
|
func isProcHandle(fh *os.File) error {
|
|
|
var buf unix.Statfs_t
|
|
|
- err := unix.Fstatfs(int(fh.Fd()), &buf)
|
|
|
- if err != nil {
|
|
|
- return errors.Wrapf(err, "statfs(%q) failed", fh.Name())
|
|
|
+
|
|
|
+ for {
|
|
|
+ err := unix.Fstatfs(int(fh.Fd()), &buf)
|
|
|
+ if err == nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if err != unix.EINTR {
|
|
|
+ return errors.Wrapf(err, "statfs(%q) failed", fh.Name())
|
|
|
+ }
|
|
|
}
|
|
|
if buf.Type != unix.PROC_SUPER_MAGIC {
|
|
|
return errors.Errorf("file %q is not on procfs", fh.Name())
|
|
@@ -307,9 +308,16 @@ func setFileLabel(fpath string, label string) error {
|
|
|
if fpath == "" {
|
|
|
return ErrEmptyPath
|
|
|
}
|
|
|
- if err := unix.Lsetxattr(fpath, xattrNameSelinux, []byte(label), 0); err != nil {
|
|
|
- return errors.Wrapf(err, "failed to set file label on %s", fpath)
|
|
|
+ for {
|
|
|
+ err := unix.Lsetxattr(fpath, xattrNameSelinux, []byte(label), 0)
|
|
|
+ if err == nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if err != unix.EINTR {
|
|
|
+ return errors.Wrapf(err, "failed to set file label on %s", fpath)
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
@@ -751,7 +759,7 @@ func reserveLabel(label string) {
|
|
|
if len(label) != 0 {
|
|
|
con := strings.SplitN(label, ":", 4)
|
|
|
if len(con) > 3 {
|
|
|
- mcsAdd(con[3])
|
|
|
+ _ = mcsAdd(con[3])
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -828,11 +836,11 @@ func intToMcs(id int, catRange uint32) string {
|
|
|
}
|
|
|
|
|
|
for ORD > TIER {
|
|
|
- ORD = ORD - TIER
|
|
|
+ ORD -= TIER
|
|
|
TIER--
|
|
|
}
|
|
|
TIER = SETSIZE - TIER
|
|
|
- ORD = ORD + TIER
|
|
|
+ ORD += TIER
|
|
|
return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
|
|
|
}
|
|
|
|
|
@@ -844,16 +852,14 @@ func uniqMcs(catRange uint32) string {
|
|
|
)
|
|
|
|
|
|
for {
|
|
|
- binary.Read(rand.Reader, binary.LittleEndian, &n)
|
|
|
+ _ = binary.Read(rand.Reader, binary.LittleEndian, &n)
|
|
|
c1 = n % catRange
|
|
|
- binary.Read(rand.Reader, binary.LittleEndian, &n)
|
|
|
+ _ = binary.Read(rand.Reader, binary.LittleEndian, &n)
|
|
|
c2 = n % catRange
|
|
|
if c1 == c2 {
|
|
|
continue
|
|
|
- } else {
|
|
|
- if c1 > c2 {
|
|
|
- c1, c2 = c2, c1
|
|
|
- }
|
|
|
+ } else if c1 > c2 {
|
|
|
+ c1, c2 = c2, c1
|
|
|
}
|
|
|
mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
|
|
|
if err := mcsAdd(mcs); err != nil {
|
|
@@ -884,18 +890,13 @@ func openContextFile() (*os.File, error) {
|
|
|
if f, err := os.Open(contextFile); err == nil {
|
|
|
return f, nil
|
|
|
}
|
|
|
- lxcPath := filepath.Join(getSELinuxPolicyRoot(), "/contexts/lxc_contexts")
|
|
|
+ lxcPath := filepath.Join(policyRoot, "/contexts/lxc_contexts")
|
|
|
return os.Open(lxcPath)
|
|
|
}
|
|
|
|
|
|
var labels = loadLabels()
|
|
|
|
|
|
func loadLabels() map[string]string {
|
|
|
- var (
|
|
|
- val, key string
|
|
|
- bufin *bufio.Reader
|
|
|
- )
|
|
|
-
|
|
|
labels := make(map[string]string)
|
|
|
in, err := openContextFile()
|
|
|
if err != nil {
|
|
@@ -903,18 +904,10 @@ func loadLabels() map[string]string {
|
|
|
}
|
|
|
defer in.Close()
|
|
|
|
|
|
- bufin = bufio.NewReader(in)
|
|
|
+ scanner := bufio.NewScanner(in)
|
|
|
|
|
|
- for done := false; !done; {
|
|
|
- var line string
|
|
|
- if line, err = bufin.ReadString('\n'); err != nil {
|
|
|
- if err == io.EOF {
|
|
|
- done = true
|
|
|
- } else {
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- line = strings.TrimSpace(line)
|
|
|
+ for scanner.Scan() {
|
|
|
+ line := strings.TrimSpace(scanner.Text())
|
|
|
if len(line) == 0 {
|
|
|
// Skip blank lines
|
|
|
continue
|
|
@@ -924,7 +917,7 @@ func loadLabels() map[string]string {
|
|
|
continue
|
|
|
}
|
|
|
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
|
|
|
- key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
|
|
|
+ key, val := strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
|
|
|
labels[key] = strings.Trim(val, "\"")
|
|
|
}
|
|
|
}
|
|
@@ -1015,7 +1008,7 @@ func copyLevel(src, dest string) (string, error) {
|
|
|
return "", err
|
|
|
}
|
|
|
mcsDelete(tcon["level"])
|
|
|
- mcsAdd(scon["level"])
|
|
|
+ _ = mcsAdd(scon["level"])
|
|
|
tcon["level"] = scon["level"]
|
|
|
return tcon.Get(), nil
|
|
|
}
|
|
@@ -1095,3 +1088,124 @@ func dupSecOpt(src string) ([]string, error) {
|
|
|
func disableSecOpt() []string {
|
|
|
return []string{"disable"}
|
|
|
}
|
|
|
+
|
|
|
+// findUserInContext scans the reader for a valid SELinux context
|
|
|
+// match that is verified with the verifier. Invalid contexts are
|
|
|
+// skipped. It returns a matched context or an empty string if no
|
|
|
+// match is found. If a scanner error occurs, it is returned.
|
|
|
+func findUserInContext(context Context, r io.Reader, verifier func(string) error) (string, error) {
|
|
|
+ fromRole := context["role"]
|
|
|
+ fromType := context["type"]
|
|
|
+ scanner := bufio.NewScanner(r)
|
|
|
+
|
|
|
+ for scanner.Scan() {
|
|
|
+ fromConns := strings.Fields(scanner.Text())
|
|
|
+ if len(fromConns) == 0 {
|
|
|
+ // Skip blank lines
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ line := fromConns[0]
|
|
|
+
|
|
|
+ if line[0] == ';' || line[0] == '#' {
|
|
|
+ // Skip comments
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // user context files contexts are formatted as
|
|
|
+ // role_r:type_t:s0 where the user is missing.
|
|
|
+ lineArr := strings.SplitN(line, ":", 4)
|
|
|
+ // skip context with typo, or role and type do not match
|
|
|
+ if len(lineArr) != 3 ||
|
|
|
+ lineArr[0] != fromRole ||
|
|
|
+ lineArr[1] != fromType {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, cc := range fromConns[1:] {
|
|
|
+ toConns := strings.SplitN(cc, ":", 4)
|
|
|
+ if len(toConns) != 3 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ context["role"] = toConns[0]
|
|
|
+ context["type"] = toConns[1]
|
|
|
+
|
|
|
+ outConn := context.get()
|
|
|
+ if err := verifier(outConn); err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ return outConn, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := scanner.Err(); err != nil {
|
|
|
+ return "", errors.Wrap(err, "failed to scan for context")
|
|
|
+ }
|
|
|
+
|
|
|
+ return "", nil
|
|
|
+}
|
|
|
+
|
|
|
+func getDefaultContextFromReaders(c *defaultSECtx) (string, error) {
|
|
|
+ if c.verifier == nil {
|
|
|
+ return "", ErrVerifierNil
|
|
|
+ }
|
|
|
+
|
|
|
+ context, err := newContext(c.scon)
|
|
|
+ if err != nil {
|
|
|
+ return "", errors.Wrapf(err, "failed to create label for %s", c.scon)
|
|
|
+ }
|
|
|
+
|
|
|
+ // set so the verifier validates the matched context with the provided user and level.
|
|
|
+ context["user"] = c.user
|
|
|
+ context["level"] = c.level
|
|
|
+
|
|
|
+ conn, err := findUserInContext(context, c.userRdr, c.verifier)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ if conn != "" {
|
|
|
+ return conn, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ conn, err = findUserInContext(context, c.defaultRdr, c.verifier)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ if conn != "" {
|
|
|
+ return conn, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return "", errors.Wrapf(ErrContextMissing, "context not found: %q", c.scon)
|
|
|
+}
|
|
|
+
|
|
|
+func getDefaultContextWithLevel(user, level, scon string) (string, error) {
|
|
|
+ userPath := filepath.Join(policyRoot, selinuxUsersDir, user)
|
|
|
+ defaultPath := filepath.Join(policyRoot, defaultContexts)
|
|
|
+
|
|
|
+ fu, err := os.Open(userPath)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ defer fu.Close()
|
|
|
+
|
|
|
+ fd, err := os.Open(defaultPath)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ defer fd.Close()
|
|
|
+
|
|
|
+ c := defaultSECtx{
|
|
|
+ user: user,
|
|
|
+ level: level,
|
|
|
+ scon: scon,
|
|
|
+ userRdr: fu,
|
|
|
+ defaultRdr: fd,
|
|
|
+ verifier: securityCheckContext,
|
|
|
+ }
|
|
|
+
|
|
|
+ return getDefaultContextFromReaders(&c)
|
|
|
+}
|