selinux.go 13 KB

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