selinux_linux.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. // +build selinux,linux
  2. package selinux
  3. import (
  4. "bufio"
  5. "bytes"
  6. "crypto/rand"
  7. "encoding/binary"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "io/ioutil"
  12. "os"
  13. "path/filepath"
  14. "regexp"
  15. "strconv"
  16. "strings"
  17. "sync"
  18. "syscall"
  19. )
  20. const (
  21. // Enforcing constant indicate SELinux is in enforcing mode
  22. Enforcing = 1
  23. // Permissive constant to indicate SELinux is in permissive mode
  24. Permissive = 0
  25. // Disabled constant to indicate SELinux is disabled
  26. Disabled = -1
  27. selinuxDir = "/etc/selinux/"
  28. selinuxConfig = selinuxDir + "config"
  29. selinuxfsMount = "/sys/fs/selinux"
  30. selinuxTypeTag = "SELINUXTYPE"
  31. selinuxTag = "SELINUX"
  32. xattrNameSelinux = "security.selinux"
  33. stRdOnly = 0x01
  34. selinuxfsMagic = 0xf97cff8c
  35. )
  36. type selinuxState struct {
  37. enabledSet bool
  38. enabled bool
  39. selinuxfsSet bool
  40. selinuxfs string
  41. mcsList map[string]bool
  42. sync.Mutex
  43. }
  44. var (
  45. // ErrMCSAlreadyExists is returned when trying to allocate a duplicate MCS.
  46. ErrMCSAlreadyExists = errors.New("MCS label already exists")
  47. // ErrEmptyPath is returned when an empty path has been specified.
  48. ErrEmptyPath = errors.New("empty path")
  49. assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
  50. roFileLabel string
  51. state = selinuxState{
  52. mcsList: make(map[string]bool),
  53. }
  54. )
  55. // Context is a representation of the SELinux label broken into 4 parts
  56. type Context map[string]string
  57. func (s *selinuxState) setEnable(enabled bool) bool {
  58. s.Lock()
  59. defer s.Unlock()
  60. s.enabledSet = true
  61. s.enabled = enabled
  62. return s.enabled
  63. }
  64. func (s *selinuxState) getEnabled() bool {
  65. s.Lock()
  66. enabled := s.enabled
  67. enabledSet := s.enabledSet
  68. s.Unlock()
  69. if enabledSet {
  70. return enabled
  71. }
  72. enabled = false
  73. if fs := getSelinuxMountPoint(); fs != "" {
  74. if con, _ := CurrentLabel(); con != "kernel" {
  75. enabled = true
  76. }
  77. }
  78. return s.setEnable(enabled)
  79. }
  80. // SetDisabled disables selinux support for the package
  81. func SetDisabled() {
  82. state.setEnable(false)
  83. }
  84. func (s *selinuxState) setSELinuxfs(selinuxfs string) string {
  85. s.Lock()
  86. defer s.Unlock()
  87. s.selinuxfsSet = true
  88. s.selinuxfs = selinuxfs
  89. return s.selinuxfs
  90. }
  91. func verifySELinuxfsMount(mnt string) bool {
  92. var buf syscall.Statfs_t
  93. for {
  94. err := syscall.Statfs(mnt, &buf)
  95. if err == nil {
  96. break
  97. }
  98. if err == syscall.EAGAIN {
  99. continue
  100. }
  101. return false
  102. }
  103. if uint32(buf.Type) != uint32(selinuxfsMagic) {
  104. return false
  105. }
  106. if (buf.Flags & stRdOnly) != 0 {
  107. return false
  108. }
  109. return true
  110. }
  111. func findSELinuxfs() string {
  112. // fast path: check the default mount first
  113. if verifySELinuxfsMount(selinuxfsMount) {
  114. return selinuxfsMount
  115. }
  116. // check if selinuxfs is available before going the slow path
  117. fs, err := ioutil.ReadFile("/proc/filesystems")
  118. if err != nil {
  119. return ""
  120. }
  121. if !bytes.Contains(fs, []byte("\tselinuxfs\n")) {
  122. return ""
  123. }
  124. // slow path: try to find among the mounts
  125. f, err := os.Open("/proc/self/mountinfo")
  126. if err != nil {
  127. return ""
  128. }
  129. defer f.Close()
  130. scanner := bufio.NewScanner(f)
  131. for {
  132. mnt := findSELinuxfsMount(scanner)
  133. if mnt == "" { // error or not found
  134. return ""
  135. }
  136. if verifySELinuxfsMount(mnt) {
  137. return mnt
  138. }
  139. }
  140. }
  141. // findSELinuxfsMount returns a next selinuxfs mount point found,
  142. // if there is one, or an empty string in case of EOF or error.
  143. func findSELinuxfsMount(s *bufio.Scanner) string {
  144. for s.Scan() {
  145. txt := s.Text()
  146. // The first field after - is fs type.
  147. // Safe as spaces in mountpoints are encoded as \040
  148. if !strings.Contains(txt, " - selinuxfs ") {
  149. continue
  150. }
  151. const mPos = 5 // mount point is 5th field
  152. fields := strings.SplitN(txt, " ", mPos+1)
  153. if len(fields) < mPos+1 {
  154. continue
  155. }
  156. return fields[mPos-1]
  157. }
  158. return ""
  159. }
  160. func (s *selinuxState) getSELinuxfs() string {
  161. s.Lock()
  162. selinuxfs := s.selinuxfs
  163. selinuxfsSet := s.selinuxfsSet
  164. s.Unlock()
  165. if selinuxfsSet {
  166. return selinuxfs
  167. }
  168. return s.setSELinuxfs(findSELinuxfs())
  169. }
  170. // getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
  171. // filesystem or an empty string if no mountpoint is found. Selinuxfs is
  172. // a proc-like pseudo-filesystem that exposes the selinux policy API to
  173. // processes. The existence of an selinuxfs mount is used to determine
  174. // whether selinux is currently enabled or not.
  175. func getSelinuxMountPoint() string {
  176. return state.getSELinuxfs()
  177. }
  178. // GetEnabled returns whether selinux is currently enabled.
  179. func GetEnabled() bool {
  180. return state.getEnabled()
  181. }
  182. func readConfig(target string) string {
  183. var (
  184. val, key string
  185. bufin *bufio.Reader
  186. )
  187. in, err := os.Open(selinuxConfig)
  188. if err != nil {
  189. return ""
  190. }
  191. defer in.Close()
  192. bufin = bufio.NewReader(in)
  193. for done := false; !done; {
  194. var line string
  195. if line, err = bufin.ReadString('\n'); err != nil {
  196. if err != io.EOF {
  197. return ""
  198. }
  199. done = true
  200. }
  201. line = strings.TrimSpace(line)
  202. if len(line) == 0 {
  203. // Skip blank lines
  204. continue
  205. }
  206. if line[0] == ';' || line[0] == '#' {
  207. // Skip comments
  208. continue
  209. }
  210. if groups := assignRegex.FindStringSubmatch(line); groups != nil {
  211. key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
  212. if key == target {
  213. return strings.Trim(val, "\"")
  214. }
  215. }
  216. }
  217. return ""
  218. }
  219. func getSELinuxPolicyRoot() string {
  220. return filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
  221. }
  222. func readCon(fpath string) (string, error) {
  223. if fpath == "" {
  224. return "", ErrEmptyPath
  225. }
  226. in, err := os.Open(fpath)
  227. if err != nil {
  228. return "", err
  229. }
  230. defer in.Close()
  231. var retval string
  232. if _, err := fmt.Fscanf(in, "%s", &retval); err != nil {
  233. return "", err
  234. }
  235. return strings.Trim(retval, "\x00"), nil
  236. }
  237. // SetFileLabel sets the SELinux label for this path or returns an error.
  238. func SetFileLabel(fpath string, label string) error {
  239. if fpath == "" {
  240. return ErrEmptyPath
  241. }
  242. return lsetxattr(fpath, xattrNameSelinux, []byte(label), 0)
  243. }
  244. // FileLabel returns the SELinux label for this path or returns an error.
  245. func FileLabel(fpath string) (string, error) {
  246. if fpath == "" {
  247. return "", ErrEmptyPath
  248. }
  249. label, err := lgetxattr(fpath, xattrNameSelinux)
  250. if err != nil {
  251. return "", err
  252. }
  253. // Trim the NUL byte at the end of the byte buffer, if present.
  254. if len(label) > 0 && label[len(label)-1] == '\x00' {
  255. label = label[:len(label)-1]
  256. }
  257. return string(label), nil
  258. }
  259. /*
  260. SetFSCreateLabel tells kernel the label to create all file system objects
  261. created by this task. Setting label="" to return to default.
  262. */
  263. func SetFSCreateLabel(label string) error {
  264. return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), label)
  265. }
  266. /*
  267. FSCreateLabel returns the default label the kernel which the kernel is using
  268. for file system objects created by this task. "" indicates default.
  269. */
  270. func FSCreateLabel() (string, error) {
  271. return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()))
  272. }
  273. // CurrentLabel returns the SELinux label of the current process thread, or an error.
  274. func CurrentLabel() (string, error) {
  275. return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid()))
  276. }
  277. // PidLabel returns the SELinux label of the given pid, or an error.
  278. func PidLabel(pid int) (string, error) {
  279. return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
  280. }
  281. /*
  282. ExecLabel returns the SELinux label that the kernel will use for any programs
  283. that are executed by the current process thread, or an error.
  284. */
  285. func ExecLabel() (string, error) {
  286. return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()))
  287. }
  288. func writeCon(fpath string, val string) error {
  289. if fpath == "" {
  290. return ErrEmptyPath
  291. }
  292. out, err := os.OpenFile(fpath, os.O_WRONLY, 0)
  293. if err != nil {
  294. return err
  295. }
  296. defer out.Close()
  297. if val != "" {
  298. _, err = out.Write([]byte(val))
  299. } else {
  300. _, err = out.Write(nil)
  301. }
  302. return err
  303. }
  304. /*
  305. CanonicalizeContext takes a context string and writes it to the kernel
  306. the function then returns the context that the kernel will use. This function
  307. can be used to see if two contexts are equivalent
  308. */
  309. func CanonicalizeContext(val string) (string, error) {
  310. return readWriteCon(filepath.Join(getSelinuxMountPoint(), "context"), val)
  311. }
  312. func readWriteCon(fpath string, val string) (string, error) {
  313. if fpath == "" {
  314. return "", ErrEmptyPath
  315. }
  316. f, err := os.OpenFile(fpath, os.O_RDWR, 0)
  317. if err != nil {
  318. return "", err
  319. }
  320. defer f.Close()
  321. _, err = f.Write([]byte(val))
  322. if err != nil {
  323. return "", err
  324. }
  325. var retval string
  326. if _, err := fmt.Fscanf(f, "%s", &retval); err != nil {
  327. return "", err
  328. }
  329. return strings.Trim(retval, "\x00"), nil
  330. }
  331. /*
  332. SetExecLabel sets the SELinux label that the kernel will use for any programs
  333. that are executed by the current process thread, or an error.
  334. */
  335. func SetExecLabel(label string) error {
  336. return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), label)
  337. }
  338. // Get returns the Context as a string
  339. func (c Context) Get() string {
  340. if c["level"] != "" {
  341. return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
  342. }
  343. return fmt.Sprintf("%s:%s:%s", c["user"], c["role"], c["type"])
  344. }
  345. // NewContext creates a new Context struct from the specified label
  346. func NewContext(label string) Context {
  347. c := make(Context)
  348. if len(label) != 0 {
  349. con := strings.SplitN(label, ":", 4)
  350. c["user"] = con[0]
  351. c["role"] = con[1]
  352. c["type"] = con[2]
  353. if len(con) > 3 {
  354. c["level"] = con[3]
  355. }
  356. }
  357. return c
  358. }
  359. // ReserveLabel reserves the MLS/MCS level component of the specified label
  360. func ReserveLabel(label string) {
  361. if len(label) != 0 {
  362. con := strings.SplitN(label, ":", 4)
  363. if len(con) > 3 {
  364. mcsAdd(con[3])
  365. }
  366. }
  367. }
  368. func selinuxEnforcePath() string {
  369. return fmt.Sprintf("%s/enforce", getSelinuxMountPoint())
  370. }
  371. // EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled
  372. func EnforceMode() int {
  373. var enforce int
  374. enforceS, err := readCon(selinuxEnforcePath())
  375. if err != nil {
  376. return -1
  377. }
  378. enforce, err = strconv.Atoi(string(enforceS))
  379. if err != nil {
  380. return -1
  381. }
  382. return enforce
  383. }
  384. /*
  385. SetEnforceMode sets the current SELinux mode Enforcing, Permissive.
  386. Disabled is not valid, since this needs to be set at boot time.
  387. */
  388. func SetEnforceMode(mode int) error {
  389. return writeCon(selinuxEnforcePath(), fmt.Sprintf("%d", mode))
  390. }
  391. /*
  392. DefaultEnforceMode returns the systems default SELinux mode Enforcing,
  393. Permissive or Disabled. Note this is is just the default at boot time.
  394. EnforceMode tells you the systems current mode.
  395. */
  396. func DefaultEnforceMode() int {
  397. switch readConfig(selinuxTag) {
  398. case "enforcing":
  399. return Enforcing
  400. case "permissive":
  401. return Permissive
  402. }
  403. return Disabled
  404. }
  405. func mcsAdd(mcs string) error {
  406. if mcs == "" {
  407. return nil
  408. }
  409. state.Lock()
  410. defer state.Unlock()
  411. if state.mcsList[mcs] {
  412. return ErrMCSAlreadyExists
  413. }
  414. state.mcsList[mcs] = true
  415. return nil
  416. }
  417. func mcsDelete(mcs string) {
  418. if mcs == "" {
  419. return
  420. }
  421. state.Lock()
  422. defer state.Unlock()
  423. state.mcsList[mcs] = false
  424. }
  425. func intToMcs(id int, catRange uint32) string {
  426. var (
  427. SETSIZE = int(catRange)
  428. TIER = SETSIZE
  429. ORD = id
  430. )
  431. if id < 1 || id > 523776 {
  432. return ""
  433. }
  434. for ORD > TIER {
  435. ORD = ORD - TIER
  436. TIER--
  437. }
  438. TIER = SETSIZE - TIER
  439. ORD = ORD + TIER
  440. return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
  441. }
  442. func uniqMcs(catRange uint32) string {
  443. var (
  444. n uint32
  445. c1, c2 uint32
  446. mcs string
  447. )
  448. for {
  449. binary.Read(rand.Reader, binary.LittleEndian, &n)
  450. c1 = n % catRange
  451. binary.Read(rand.Reader, binary.LittleEndian, &n)
  452. c2 = n % catRange
  453. if c1 == c2 {
  454. continue
  455. } else {
  456. if c1 > c2 {
  457. c1, c2 = c2, c1
  458. }
  459. }
  460. mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
  461. if err := mcsAdd(mcs); err != nil {
  462. continue
  463. }
  464. break
  465. }
  466. return mcs
  467. }
  468. /*
  469. ReleaseLabel will unreserve the MLS/MCS Level field of the specified label.
  470. Allowing it to be used by another process.
  471. */
  472. func ReleaseLabel(label string) {
  473. if len(label) != 0 {
  474. con := strings.SplitN(label, ":", 4)
  475. if len(con) > 3 {
  476. mcsDelete(con[3])
  477. }
  478. }
  479. }
  480. // ROFileLabel returns the specified SELinux readonly file label
  481. func ROFileLabel() string {
  482. return roFileLabel
  483. }
  484. /*
  485. ContainerLabels returns an allocated processLabel and fileLabel to be used for
  486. container labeling by the calling process.
  487. */
  488. func ContainerLabels() (processLabel string, fileLabel string) {
  489. var (
  490. val, key string
  491. bufin *bufio.Reader
  492. )
  493. if !GetEnabled() {
  494. return "", ""
  495. }
  496. lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot())
  497. in, err := os.Open(lxcPath)
  498. if err != nil {
  499. return "", ""
  500. }
  501. defer in.Close()
  502. bufin = bufio.NewReader(in)
  503. for done := false; !done; {
  504. var line string
  505. if line, err = bufin.ReadString('\n'); err != nil {
  506. if err == io.EOF {
  507. done = true
  508. } else {
  509. goto exit
  510. }
  511. }
  512. line = strings.TrimSpace(line)
  513. if len(line) == 0 {
  514. // Skip blank lines
  515. continue
  516. }
  517. if line[0] == ';' || line[0] == '#' {
  518. // Skip comments
  519. continue
  520. }
  521. if groups := assignRegex.FindStringSubmatch(line); groups != nil {
  522. key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
  523. if key == "process" {
  524. processLabel = strings.Trim(val, "\"")
  525. }
  526. if key == "file" {
  527. fileLabel = strings.Trim(val, "\"")
  528. }
  529. if key == "ro_file" {
  530. roFileLabel = strings.Trim(val, "\"")
  531. }
  532. }
  533. }
  534. if processLabel == "" || fileLabel == "" {
  535. return "", ""
  536. }
  537. if roFileLabel == "" {
  538. roFileLabel = fileLabel
  539. }
  540. exit:
  541. scon := NewContext(processLabel)
  542. if scon["level"] != "" {
  543. mcs := uniqMcs(1024)
  544. scon["level"] = mcs
  545. processLabel = scon.Get()
  546. scon = NewContext(fileLabel)
  547. scon["level"] = mcs
  548. fileLabel = scon.Get()
  549. }
  550. return processLabel, fileLabel
  551. }
  552. // SecurityCheckContext validates that the SELinux label is understood by the kernel
  553. func SecurityCheckContext(val string) error {
  554. return writeCon(fmt.Sprintf("%s/context", getSelinuxMountPoint()), val)
  555. }
  556. /*
  557. CopyLevel returns a label with the MLS/MCS level from src label replaced on
  558. the dest label.
  559. */
  560. func CopyLevel(src, dest string) (string, error) {
  561. if src == "" {
  562. return "", nil
  563. }
  564. if err := SecurityCheckContext(src); err != nil {
  565. return "", err
  566. }
  567. if err := SecurityCheckContext(dest); err != nil {
  568. return "", err
  569. }
  570. scon := NewContext(src)
  571. tcon := NewContext(dest)
  572. mcsDelete(tcon["level"])
  573. mcsAdd(scon["level"])
  574. tcon["level"] = scon["level"]
  575. return tcon.Get(), nil
  576. }
  577. // Prevent users from relabing system files
  578. func badPrefix(fpath string) error {
  579. if fpath == "" {
  580. return ErrEmptyPath
  581. }
  582. badPrefixes := []string{"/usr"}
  583. for _, prefix := range badPrefixes {
  584. if strings.HasPrefix(fpath, prefix) {
  585. return fmt.Errorf("relabeling content in %s is not allowed", prefix)
  586. }
  587. }
  588. return nil
  589. }
  590. // Chcon changes the `fpath` file object to the SELinux label `label`.
  591. // If `fpath` is a directory and `recurse`` is true, Chcon will walk the
  592. // directory tree setting the label.
  593. func Chcon(fpath string, label string, recurse bool) error {
  594. if fpath == "" {
  595. return ErrEmptyPath
  596. }
  597. if label == "" {
  598. return nil
  599. }
  600. if err := badPrefix(fpath); err != nil {
  601. return err
  602. }
  603. callback := func(p string, info os.FileInfo, err error) error {
  604. return SetFileLabel(p, label)
  605. }
  606. if recurse {
  607. return filepath.Walk(fpath, callback)
  608. }
  609. return SetFileLabel(fpath, label)
  610. }
  611. // DupSecOpt takes an SELinux process label and returns security options that
  612. // can be used to set the SELinux Type and Level for future container processes.
  613. func DupSecOpt(src string) []string {
  614. if src == "" {
  615. return nil
  616. }
  617. con := NewContext(src)
  618. if con["user"] == "" ||
  619. con["role"] == "" ||
  620. con["type"] == "" {
  621. return nil
  622. }
  623. dup := []string{"user:" + con["user"],
  624. "role:" + con["role"],
  625. "type:" + con["type"],
  626. }
  627. if con["level"] != "" {
  628. dup = append(dup, "level:"+con["level"])
  629. }
  630. return dup
  631. }
  632. // DisableSecOpt returns a security opt that can be used to disable SELinux
  633. // labeling support for future container processes.
  634. func DisableSecOpt() []string {
  635. return []string{"disable"}
  636. }