PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/oz-daemon/launch.go

https://gitlab.com/Lin0x/oz
Go | 472 lines | 412 code | 40 blank | 20 comment | 119 complexity | 802b714880b7b7c10c4378ec8213c8e5 MD5 | raw file
  1. package daemon
  2. import (
  3. "bufio"
  4. "bytes"
  5. "crypto/rand"
  6. "encoding/hex"
  7. "encoding/json"
  8. "fmt"
  9. "io"
  10. "net/url"
  11. "os"
  12. "os/exec"
  13. "os/user"
  14. "path"
  15. "path/filepath"
  16. "strconv"
  17. "strings"
  18. "sync"
  19. "syscall"
  20. "github.com/subgraph/oz"
  21. "github.com/subgraph/oz/network"
  22. "github.com/subgraph/oz/oz-init"
  23. "github.com/subgraph/oz/xpra"
  24. "github.com/op/go-logging"
  25. "github.com/subgraph/oz/fs"
  26. )
  27. type Sandbox struct {
  28. daemon *daemonState
  29. id int
  30. display int
  31. profile *oz.Profile
  32. init *exec.Cmd
  33. user *user.User
  34. cred *syscall.Credential
  35. fs *fs.Filesystem
  36. stderr io.ReadCloser
  37. addr string
  38. xpra *xpra.Xpra
  39. ready sync.WaitGroup
  40. waiting sync.WaitGroup
  41. network *network.SandboxNetwork
  42. mountedFiles []string
  43. rawEnv []string
  44. }
  45. func createSocketPath(base string) (string, error) {
  46. bs := make([]byte, 8)
  47. _, err := rand.Read(bs)
  48. if err != nil {
  49. return "", err
  50. }
  51. return path.Join(base, fmt.Sprintf("oz-init-control-%s", hex.EncodeToString(bs))), nil
  52. }
  53. func createInitCommand(initPath string, cloneNet bool) *exec.Cmd {
  54. cmd := exec.Command(initPath)
  55. cmd.Dir = "/"
  56. cloneFlags := uintptr(syscall.CLONE_NEWNS)
  57. cloneFlags |= syscall.CLONE_NEWIPC
  58. cloneFlags |= syscall.CLONE_NEWPID
  59. cloneFlags |= syscall.CLONE_NEWUTS
  60. if cloneNet {
  61. cloneFlags |= syscall.CLONE_NEWNET
  62. }
  63. cmd.SysProcAttr = &syscall.SysProcAttr{
  64. //Chroot: chroot,
  65. Cloneflags: cloneFlags,
  66. }
  67. cmd.Env = []string{}
  68. return cmd
  69. }
  70. func (d *daemonState) launch(p *oz.Profile, msg *LaunchMsg, rawEnv []string, uid, gid uint32, log *logging.Logger) (*Sandbox, error) {
  71. /*
  72. u, err := user.LookupId(fmt.Sprintf("%d", uid))
  73. if err != nil {
  74. return nil, fmt.Errorf("failed to lookup user for uid=%d: %v", uid, err)
  75. }
  76. fs := fs.NewFromProfile(p, u, d.config.SandboxPath, d.config.UseFullDev, d.log)
  77. if err := fs.Setup(d.config.ProfileDir); err != nil {
  78. return nil, err
  79. }
  80. */
  81. u, err := user.LookupId(strconv.FormatUint(uint64(uid), 10))
  82. if err != nil {
  83. return nil, fmt.Errorf("Failed to look up user with uid=%ld: %v", uid, err)
  84. }
  85. groups, err := d.sanitizeGroups(p, u.Username, msg.Gids)
  86. if err != nil {
  87. return nil, fmt.Errorf("Unable to sanitize user groups: %v", err)
  88. }
  89. display := 0
  90. if p.XServer.Enabled && p.Networking.Nettype == network.TYPE_HOST {
  91. display = d.nextDisplay
  92. d.nextDisplay += 1
  93. }
  94. stn := new(network.SandboxNetwork)
  95. stn.Nettype = p.Networking.Nettype
  96. if p.Networking.Nettype == network.TYPE_BRIDGE {
  97. stn, err = network.PrepareSandboxNetwork(nil, d.network, p.Networking.IpByte, log)
  98. if err != nil {
  99. return nil, fmt.Errorf("Unable to prepare veth network: %+v", err)
  100. }
  101. if err := network.NetInit(stn, d.network, log); err != nil {
  102. return nil, fmt.Errorf("Unable to create veth networking: %+v", err)
  103. }
  104. }
  105. socketPath, err := createSocketPath(path.Join(d.config.SandboxPath, "sockets"))
  106. if err != nil {
  107. return nil, fmt.Errorf("Failed to create random socket path: %v", err)
  108. }
  109. initPath := path.Join(d.config.PrefixPath, "bin", "oz-init")
  110. cmd := createInitCommand(initPath, (stn.Nettype != network.TYPE_HOST))
  111. pp, err := cmd.StderrPipe()
  112. if err != nil {
  113. //fs.Cleanup()
  114. return nil, fmt.Errorf("error creating stderr pipe for init process: %v", err)
  115. }
  116. pi, err := cmd.StdinPipe()
  117. if err != nil {
  118. //fs.Cleanup()
  119. return nil, fmt.Errorf("error creating stdin pipe for init process: %v", err)
  120. }
  121. jdata, err := json.Marshal(ozinit.InitData{
  122. Display: display,
  123. User: *u,
  124. Uid: uid,
  125. Gid: gid,
  126. Gids: groups,
  127. Network: network.SandboxNetwork{
  128. Interface: stn.Interface,
  129. VethHost: stn.VethHost,
  130. VethGuest: stn.VethGuest,
  131. Ip: stn.Ip,
  132. Gateway: stn.Gateway,
  133. Class: stn.Class,
  134. Nettype: stn.Nettype,
  135. },
  136. Profile: *p,
  137. Config: *d.config,
  138. Sockaddr: socketPath,
  139. LaunchEnv: msg.Env,
  140. })
  141. if err != nil {
  142. return nil, fmt.Errorf("Unable to marshal init state: %+v", err)
  143. }
  144. io.Copy(pi, bytes.NewBuffer(jdata))
  145. pi.Close()
  146. if err := cmd.Start(); err != nil {
  147. //fs.Cleanup()
  148. return nil, fmt.Errorf("Unable to start process: %+v", err)
  149. }
  150. //rootfs := path.Join(d.config.SandboxPath, "rootfs")
  151. sbox := &Sandbox{
  152. daemon: d,
  153. id: d.nextSboxId,
  154. display: display,
  155. profile: p,
  156. init: cmd,
  157. cred: &syscall.Credential{Uid: uid, Gid: gid, Groups: msg.Gids},
  158. user: u,
  159. fs: fs.NewFilesystem(d.config, log),
  160. //addr: path.Join(rootfs, ozinit.SocketAddress),
  161. addr: socketPath,
  162. stderr: pp,
  163. network: stn,
  164. rawEnv: rawEnv,
  165. }
  166. sbox.ready.Add(1)
  167. sbox.waiting.Add(1)
  168. go sbox.logMessages()
  169. sbox.waiting.Wait()
  170. if p.Networking.Nettype == network.TYPE_BRIDGE {
  171. if err := network.NetAttach(stn, d.network, cmd.Process.Pid); err != nil {
  172. cmd.Process.Kill()
  173. return nil, fmt.Errorf("Unable to attach veth networking: %+v", err)
  174. }
  175. }
  176. cmd.Process.Signal(syscall.SIGUSR1)
  177. wgNet := new(sync.WaitGroup)
  178. if p.Networking.Nettype != network.TYPE_HOST &&
  179. p.Networking.Nettype != network.TYPE_NONE &&
  180. len(p.Networking.Sockets) > 0 {
  181. wgNet.Add(1)
  182. go func() {
  183. defer wgNet.Done()
  184. sbox.ready.Wait()
  185. err := network.ProxySetup(sbox.init.Process.Pid, p.Networking.Sockets, d.log, sbox.ready)
  186. if err != nil {
  187. log.Warning("Unable to create connection proxy: %+s", err)
  188. }
  189. }()
  190. }
  191. if !msg.Noexec {
  192. go func() {
  193. sbox.ready.Wait()
  194. wgNet.Wait()
  195. go sbox.launchProgram(d.config.PrefixPath, msg.Path, msg.Pwd, msg.Args, log)
  196. }()
  197. }
  198. if sbox.profile.XServer.Enabled {
  199. go func() {
  200. sbox.ready.Wait()
  201. go sbox.startXpraClient()
  202. }()
  203. }
  204. d.nextSboxId += 1
  205. d.sandboxes = append(d.sandboxes, sbox)
  206. return sbox, nil
  207. }
  208. func (d *daemonState) sanitizeGroups(p *oz.Profile, username string, gids []uint32) (map[string]uint32, error) {
  209. allowedGroups := d.config.DefaultGroups
  210. allowedGroups = append(allowedGroups, p.AllowedGroups...)
  211. if len(d.systemGroups) == 0 {
  212. if err := d.cacheSystemGroups(); err != nil {
  213. return nil, err
  214. }
  215. }
  216. groups := map[string]uint32{}
  217. for _, sg := range d.systemGroups {
  218. for _, gg := range allowedGroups {
  219. if sg.Name == gg {
  220. found := false
  221. for _, uname := range sg.Members {
  222. if uname == username {
  223. found = true
  224. break
  225. }
  226. }
  227. if !found {
  228. continue
  229. }
  230. d.log.Debug("Allowing user: %s (%d)", gg, sg.Gid)
  231. groups[sg.Name] = sg.Gid
  232. break
  233. }
  234. }
  235. }
  236. return groups, nil
  237. }
  238. func (sbox *Sandbox) launchProgram(binpath, cpath, pwd string, args []string, log *logging.Logger) {
  239. if sbox.profile.AllowFiles {
  240. sbox.whitelistArgumentFiles(binpath, pwd, args, log)
  241. }
  242. err := ozinit.RunProgram(sbox.addr, cpath, pwd, args)
  243. if err != nil {
  244. log.Error("run program command failed: %v", err)
  245. }
  246. }
  247. func (sbox *Sandbox) MountFiles(files []string, readonly bool, binpath string, log *logging.Logger) error {
  248. pmnt := path.Join(binpath, "bin", "oz-mount")
  249. args := files
  250. if readonly {
  251. args = append([]string{"--readonly"}, files...)
  252. }
  253. cmnt := exec.Command(pmnt, args...)
  254. cmnt.Env = []string{
  255. "_OZ_NSPID=" + strconv.Itoa(sbox.init.Process.Pid),
  256. "_OZ_HOMEDIR=" + sbox.user.HomeDir,
  257. }
  258. log.Debug("Attempting to add file with %s to sandbox %s: %+s", pmnt, sbox.profile.Name, files)
  259. pout, err := cmnt.CombinedOutput()
  260. if err != nil || cmnt.ProcessState.Success() == false {
  261. log.Warning("Unable to bind files to sandbox: %s", string(pout))
  262. return fmt.Errorf("%s", string(pout[2:]))
  263. }
  264. for _, mfile := range files {
  265. found := false
  266. for _, mmfile := range sbox.mountedFiles {
  267. if mfile == mmfile {
  268. found = true
  269. break
  270. }
  271. }
  272. if !found {
  273. sbox.mountedFiles = append(sbox.mountedFiles, mfile)
  274. }
  275. }
  276. log.Info("%s", string(pout))
  277. return nil
  278. }
  279. func (sbox *Sandbox) UnmountFile(file, binpath string, log *logging.Logger) error {
  280. pmnt := path.Join(binpath, "bin", "oz-umount")
  281. cmnt := exec.Command(pmnt, file)
  282. cmnt.Env = []string{
  283. "_OZ_NSPID=" + strconv.Itoa(sbox.init.Process.Pid),
  284. "_OZ_HOMEDIR=" + sbox.user.HomeDir,
  285. }
  286. pout, err := cmnt.CombinedOutput()
  287. if err != nil || cmnt.ProcessState.Success() == false {
  288. log.Warning("Unable to unbind file from sandbox: %s", string(pout))
  289. return fmt.Errorf("%s", string(pout[2:]))
  290. }
  291. for i, item := range sbox.mountedFiles {
  292. if item == file {
  293. sbox.mountedFiles = append(sbox.mountedFiles[:i], sbox.mountedFiles[i+1:]...)
  294. }
  295. }
  296. log.Info("%s", string(pout))
  297. return nil
  298. }
  299. func (sbox *Sandbox) whitelistArgumentFiles(binpath, pwd string, args []string, log *logging.Logger) {
  300. var files []string
  301. for _, fpath := range args {
  302. if strings.HasPrefix(fpath, "file://") {
  303. fpath = strings.Replace(fpath, "file://", "", 1)
  304. fpath, _ = url.QueryUnescape(fpath)
  305. }
  306. if filepath.IsAbs(fpath) == false {
  307. fpath = path.Join(pwd, fpath)
  308. }
  309. if !strings.HasPrefix(fpath, sbox.user.HomeDir) && !strings.HasPrefix(fpath, "/media/user") {
  310. continue
  311. }
  312. if _, err := os.Stat(fpath); err == nil {
  313. log.Notice("Adding file `%s` to sandbox `%s`.", fpath, sbox.profile.Name)
  314. files = append(files, fpath)
  315. }
  316. }
  317. if len(files) > 0 {
  318. sbox.MountFiles(files, false, binpath, log)
  319. }
  320. }
  321. func (sbox *Sandbox) remove(log *logging.Logger) {
  322. sboxes := []*Sandbox{}
  323. for _, sb := range sbox.daemon.sandboxes {
  324. if sb == sbox {
  325. // sb.fs.Cleanup()
  326. if sb.profile.Networking.Nettype == network.TYPE_BRIDGE {
  327. sb.network.Cleanup(log)
  328. }
  329. os.Remove(sb.addr)
  330. } else {
  331. sboxes = append(sboxes, sb)
  332. }
  333. }
  334. sbox.daemon.sandboxes = sboxes
  335. }
  336. func (sbox *Sandbox) logMessages() {
  337. scanner := bufio.NewScanner(sbox.stderr)
  338. seenOk := false
  339. seenWaiting := false
  340. for scanner.Scan() {
  341. line := scanner.Text()
  342. if line == "WAITING" && !seenWaiting {
  343. sbox.daemon.log.Info("oz-init (%s) is waiting for init", sbox.profile.Name)
  344. seenWaiting = true
  345. sbox.waiting.Done()
  346. } else if line == "OK" && !seenOk {
  347. sbox.daemon.log.Info("oz-init (%s) is ready", sbox.profile.Name)
  348. seenOk = true
  349. sbox.ready.Done()
  350. } else if len(line) > 1 {
  351. sbox.logLine(line)
  352. }
  353. }
  354. sbox.stderr.Close()
  355. }
  356. func (sbox *Sandbox) logLine(line string) {
  357. if len(line) < 2 {
  358. return
  359. }
  360. f := sbox.getLogFunc(line[0])
  361. msg := line[2:]
  362. if f != nil {
  363. f("[%s] %s", sbox.profile.Name, msg)
  364. } else {
  365. sbox.daemon.log.Info("[%s] %s", sbox.profile.Name, line)
  366. }
  367. }
  368. func (sbox *Sandbox) getLogFunc(c byte) func(string, ...interface{}) {
  369. log := sbox.daemon.log
  370. switch c {
  371. case 'D':
  372. return log.Debug
  373. case 'I':
  374. return log.Info
  375. case 'N':
  376. return log.Notice
  377. case 'W':
  378. return log.Warning
  379. case 'E':
  380. return log.Error
  381. case 'C':
  382. return log.Critical
  383. }
  384. return nil
  385. }
  386. func (sbox *Sandbox) startXpraClient() {
  387. u, err := user.LookupId(fmt.Sprintf("%d", sbox.cred.Uid))
  388. if err != nil {
  389. sbox.daemon.Error("Failed to lookup user for uid=%d, cannot start xpra", sbox.cred.Uid)
  390. return
  391. }
  392. xpraPath := path.Join(u.HomeDir, ".Xoz", sbox.profile.Name)
  393. sbox.xpra = xpra.NewClient(
  394. &sbox.profile.XServer,
  395. uint64(sbox.display),
  396. sbox.cred,
  397. path.Join(sbox.daemon.config.PrefixPath, "bin", "oz-seccomp"),
  398. xpraPath,
  399. sbox.profile.Name,
  400. sbox.daemon.log)
  401. sbox.xpra.Process.Env = append(sbox.rawEnv, sbox.xpra.Process.Env...)
  402. //sbox.daemon.log.Debug("%s %s", strings.Join(sbox.xpra.Process.Env, " "), strings.Join(sbox.xpra.Process.Args, " "))
  403. if sbox.daemon.config.LogXpra {
  404. sbox.setupXpraLogging()
  405. }
  406. if err := sbox.xpra.Process.Start(); err != nil {
  407. sbox.daemon.Warning("Failed to start xpra client: %v", err)
  408. }
  409. }
  410. func (sbox *Sandbox) setupXpraLogging() {
  411. stdout, err := sbox.xpra.Process.StdoutPipe()
  412. if err != nil {
  413. sbox.daemon.Warning("Failed to create xpra stdout pipe: %v", err)
  414. return
  415. }
  416. stderr, err := sbox.xpra.Process.StderrPipe()
  417. if err != nil {
  418. stdout.Close()
  419. sbox.daemon.Warning("Failed to create xpra stderr pipe: %v", err)
  420. }
  421. go sbox.logPipeOutput(stdout, "xpra-client-stdout")
  422. go sbox.logPipeOutput(stderr, "xpra-client-stderr")
  423. }
  424. func (sbox *Sandbox) logPipeOutput(p io.Reader, label string) {
  425. scanner := bufio.NewScanner(p)
  426. for scanner.Scan() {
  427. line := scanner.Text()
  428. sbox.daemon.log.Info("[%s] (%s) %s", sbox.profile.Name, label, line)
  429. }
  430. }