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 }