selinux.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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. "github.com/docker/docker/pkg/mount"
  17. "github.com/opencontainers/runc/libcontainer/system"
  18. )
  19. const (
  20. Enforcing = 1
  21. Permissive = 0
  22. Disabled = -1
  23. selinuxDir = "/etc/selinux/"
  24. selinuxConfig = selinuxDir + "config"
  25. selinuxTypeTag = "SELINUXTYPE"
  26. selinuxTag = "SELINUX"
  27. selinuxPath = "/sys/fs/selinux"
  28. xattrNameSelinux = "security.selinux"
  29. stRdOnly = 0x01
  30. )
  31. var (
  32. assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
  33. mcsList = make(map[string]bool)
  34. mcsLock sync.Mutex
  35. selinuxfs = "unknown"
  36. selinuxEnabled = false // Stores whether selinux is currently enabled
  37. selinuxEnabledChecked = false // Stores whether selinux enablement has been checked or established yet
  38. )
  39. type SELinuxContext map[string]string
  40. // SetDisabled disables selinux support for the package
  41. func SetDisabled() {
  42. selinuxEnabled, selinuxEnabledChecked = false, true
  43. }
  44. // getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
  45. // filesystem or an empty string if no mountpoint is found. Selinuxfs is
  46. // a proc-like pseudo-filesystem that exposes the selinux policy API to
  47. // processes. The existence of an selinuxfs mount is used to determine
  48. // whether selinux is currently enabled or not.
  49. func getSelinuxMountPoint() string {
  50. if selinuxfs != "unknown" {
  51. return selinuxfs
  52. }
  53. selinuxfs = ""
  54. mounts, err := mount.GetMounts()
  55. if err != nil {
  56. return selinuxfs
  57. }
  58. for _, mount := range mounts {
  59. if mount.Fstype == "selinuxfs" {
  60. selinuxfs = mount.Mountpoint
  61. break
  62. }
  63. }
  64. if selinuxfs != "" {
  65. var buf syscall.Statfs_t
  66. syscall.Statfs(selinuxfs, &buf)
  67. if (buf.Flags & stRdOnly) == 1 {
  68. selinuxfs = ""
  69. }
  70. }
  71. return selinuxfs
  72. }
  73. // SelinuxEnabled returns whether selinux is currently enabled.
  74. func SelinuxEnabled() bool {
  75. if selinuxEnabledChecked {
  76. return selinuxEnabled
  77. }
  78. selinuxEnabledChecked = true
  79. if fs := getSelinuxMountPoint(); fs != "" {
  80. if con, _ := Getcon(); con != "kernel" {
  81. selinuxEnabled = true
  82. }
  83. }
  84. return selinuxEnabled
  85. }
  86. func readConfig(target string) (value string) {
  87. var (
  88. val, key string
  89. bufin *bufio.Reader
  90. )
  91. in, err := os.Open(selinuxConfig)
  92. if err != nil {
  93. return ""
  94. }
  95. defer in.Close()
  96. bufin = bufio.NewReader(in)
  97. for done := false; !done; {
  98. var line string
  99. if line, err = bufin.ReadString('\n'); err != nil {
  100. if err != io.EOF {
  101. return ""
  102. }
  103. done = true
  104. }
  105. line = strings.TrimSpace(line)
  106. if len(line) == 0 {
  107. // Skip blank lines
  108. continue
  109. }
  110. if line[0] == ';' || line[0] == '#' {
  111. // Skip comments
  112. continue
  113. }
  114. if groups := assignRegex.FindStringSubmatch(line); groups != nil {
  115. key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
  116. if key == target {
  117. return strings.Trim(val, "\"")
  118. }
  119. }
  120. }
  121. return ""
  122. }
  123. func getSELinuxPolicyRoot() string {
  124. return selinuxDir + readConfig(selinuxTypeTag)
  125. }
  126. func readCon(name string) (string, error) {
  127. var val string
  128. in, err := os.Open(name)
  129. if err != nil {
  130. return "", err
  131. }
  132. defer in.Close()
  133. _, err = fmt.Fscanf(in, "%s", &val)
  134. return val, err
  135. }
  136. // Setfilecon sets the SELinux label for this path or returns an error.
  137. func Setfilecon(path string, scon string) error {
  138. return system.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0)
  139. }
  140. // Getfilecon returns the SELinux label for this path or returns an error.
  141. func Getfilecon(path string) (string, error) {
  142. con, err := system.Lgetxattr(path, xattrNameSelinux)
  143. if err != nil {
  144. return "", err
  145. }
  146. // Trim the NUL byte at the end of the byte buffer, if present.
  147. if len(con) > 0 && con[len(con)-1] == '\x00' {
  148. con = con[:len(con)-1]
  149. }
  150. return string(con), nil
  151. }
  152. func Setfscreatecon(scon string) error {
  153. return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), scon)
  154. }
  155. func Getfscreatecon() (string, error) {
  156. return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()))
  157. }
  158. // Getcon returns the SELinux label of the current process thread, or an error.
  159. func Getcon() (string, error) {
  160. return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid()))
  161. }
  162. // Getpidcon returns the SELinux label of the given pid, or an error.
  163. func Getpidcon(pid int) (string, error) {
  164. return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
  165. }
  166. func Getexeccon() (string, error) {
  167. return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()))
  168. }
  169. func writeCon(name string, val string) error {
  170. out, err := os.OpenFile(name, os.O_WRONLY, 0)
  171. if err != nil {
  172. return err
  173. }
  174. defer out.Close()
  175. if val != "" {
  176. _, err = out.Write([]byte(val))
  177. } else {
  178. _, err = out.Write(nil)
  179. }
  180. return err
  181. }
  182. func Setexeccon(scon string) error {
  183. return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon)
  184. }
  185. func (c SELinuxContext) Get() string {
  186. return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
  187. }
  188. func NewContext(scon string) SELinuxContext {
  189. c := make(SELinuxContext)
  190. if len(scon) != 0 {
  191. con := strings.SplitN(scon, ":", 4)
  192. c["user"] = con[0]
  193. c["role"] = con[1]
  194. c["type"] = con[2]
  195. c["level"] = con[3]
  196. }
  197. return c
  198. }
  199. func ReserveLabel(scon string) {
  200. if len(scon) != 0 {
  201. con := strings.SplitN(scon, ":", 4)
  202. mcsAdd(con[3])
  203. }
  204. }
  205. func selinuxEnforcePath() string {
  206. return fmt.Sprintf("%s/enforce", selinuxPath)
  207. }
  208. func SelinuxGetEnforce() int {
  209. var enforce int
  210. enforceS, err := readCon(selinuxEnforcePath())
  211. if err != nil {
  212. return -1
  213. }
  214. enforce, err = strconv.Atoi(string(enforceS))
  215. if err != nil {
  216. return -1
  217. }
  218. return enforce
  219. }
  220. func SelinuxSetEnforce(mode int) error {
  221. return writeCon(selinuxEnforcePath(), fmt.Sprintf("%d", mode))
  222. }
  223. func SelinuxGetEnforceMode() int {
  224. switch readConfig(selinuxTag) {
  225. case "enforcing":
  226. return Enforcing
  227. case "permissive":
  228. return Permissive
  229. }
  230. return Disabled
  231. }
  232. func mcsAdd(mcs string) error {
  233. mcsLock.Lock()
  234. defer mcsLock.Unlock()
  235. if mcsList[mcs] {
  236. return fmt.Errorf("MCS Label already exists")
  237. }
  238. mcsList[mcs] = true
  239. return nil
  240. }
  241. func mcsDelete(mcs string) {
  242. mcsLock.Lock()
  243. mcsList[mcs] = false
  244. mcsLock.Unlock()
  245. }
  246. func IntToMcs(id int, catRange uint32) string {
  247. var (
  248. SETSIZE = int(catRange)
  249. TIER = SETSIZE
  250. ORD = id
  251. )
  252. if id < 1 || id > 523776 {
  253. return ""
  254. }
  255. for ORD > TIER {
  256. ORD = ORD - TIER
  257. TIER -= 1
  258. }
  259. TIER = SETSIZE - TIER
  260. ORD = ORD + TIER
  261. return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
  262. }
  263. func uniqMcs(catRange uint32) string {
  264. var (
  265. n uint32
  266. c1, c2 uint32
  267. mcs string
  268. )
  269. for {
  270. binary.Read(rand.Reader, binary.LittleEndian, &n)
  271. c1 = n % catRange
  272. binary.Read(rand.Reader, binary.LittleEndian, &n)
  273. c2 = n % catRange
  274. if c1 == c2 {
  275. continue
  276. } else {
  277. if c1 > c2 {
  278. t := c1
  279. c1 = c2
  280. c2 = t
  281. }
  282. }
  283. mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
  284. if err := mcsAdd(mcs); err != nil {
  285. continue
  286. }
  287. break
  288. }
  289. return mcs
  290. }
  291. func FreeLxcContexts(scon string) {
  292. if len(scon) != 0 {
  293. con := strings.SplitN(scon, ":", 4)
  294. mcsDelete(con[3])
  295. }
  296. }
  297. func GetLxcContexts() (processLabel string, fileLabel string) {
  298. var (
  299. val, key string
  300. bufin *bufio.Reader
  301. )
  302. if !SelinuxEnabled() {
  303. return "", ""
  304. }
  305. lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot())
  306. in, err := os.Open(lxcPath)
  307. if err != nil {
  308. return "", ""
  309. }
  310. defer in.Close()
  311. bufin = bufio.NewReader(in)
  312. for done := false; !done; {
  313. var line string
  314. if line, err = bufin.ReadString('\n'); err != nil {
  315. if err == io.EOF {
  316. done = true
  317. } else {
  318. goto exit
  319. }
  320. }
  321. line = strings.TrimSpace(line)
  322. if len(line) == 0 {
  323. // Skip blank lines
  324. continue
  325. }
  326. if line[0] == ';' || line[0] == '#' {
  327. // Skip comments
  328. continue
  329. }
  330. if groups := assignRegex.FindStringSubmatch(line); groups != nil {
  331. key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
  332. if key == "process" {
  333. processLabel = strings.Trim(val, "\"")
  334. }
  335. if key == "file" {
  336. fileLabel = strings.Trim(val, "\"")
  337. }
  338. }
  339. }
  340. if processLabel == "" || fileLabel == "" {
  341. return "", ""
  342. }
  343. exit:
  344. // mcs := IntToMcs(os.Getpid(), 1024)
  345. mcs := uniqMcs(1024)
  346. scon := NewContext(processLabel)
  347. scon["level"] = mcs
  348. processLabel = scon.Get()
  349. scon = NewContext(fileLabel)
  350. scon["level"] = mcs
  351. fileLabel = scon.Get()
  352. return processLabel, fileLabel
  353. }
  354. func SecurityCheckContext(val string) error {
  355. return writeCon(fmt.Sprintf("%s.context", selinuxPath), val)
  356. }
  357. func CopyLevel(src, dest string) (string, error) {
  358. if src == "" {
  359. return "", nil
  360. }
  361. if err := SecurityCheckContext(src); err != nil {
  362. return "", err
  363. }
  364. if err := SecurityCheckContext(dest); err != nil {
  365. return "", err
  366. }
  367. scon := NewContext(src)
  368. tcon := NewContext(dest)
  369. mcsDelete(tcon["level"])
  370. mcsAdd(scon["level"])
  371. tcon["level"] = scon["level"]
  372. return tcon.Get(), nil
  373. }
  374. // Prevent users from relabing system files
  375. func badPrefix(fpath string) error {
  376. var badprefixes = []string{"/usr"}
  377. for _, prefix := range badprefixes {
  378. if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) {
  379. return fmt.Errorf("Relabeling content in %s is not allowed.", prefix)
  380. }
  381. }
  382. return nil
  383. }
  384. // Change the fpath file object to the SELinux label scon.
  385. // If the fpath is a directory and recurse is true Chcon will walk the
  386. // directory tree setting the label
  387. func Chcon(fpath string, scon string, recurse bool) error {
  388. if scon == "" {
  389. return nil
  390. }
  391. if err := badPrefix(fpath); err != nil {
  392. return err
  393. }
  394. callback := func(p string, info os.FileInfo, err error) error {
  395. return Setfilecon(p, scon)
  396. }
  397. if recurse {
  398. return filepath.Walk(fpath, callback)
  399. }
  400. return Setfilecon(fpath, scon)
  401. }
  402. // DupSecOpt takes an SELinux process label and returns security options that
  403. // can will set the SELinux Type and Level for future container processes
  404. func DupSecOpt(src string) []string {
  405. if src == "" {
  406. return nil
  407. }
  408. con := NewContext(src)
  409. if con["user"] == "" ||
  410. con["role"] == "" ||
  411. con["type"] == "" ||
  412. con["level"] == "" {
  413. return nil
  414. }
  415. return []string{"label:user:" + con["user"],
  416. "label:role:" + con["role"],
  417. "label:type:" + con["type"],
  418. "label:level:" + con["level"]}
  419. }
  420. // DisableSecOpt returns a security opt that can be used to disabling SELinux
  421. // labeling support for future container processes
  422. func DisableSecOpt() []string {
  423. return []string{"label:disable"}
  424. }