// Returns the UID and GID to enforce. If the UID or GID is -1, it is not // to be enforced. Neither or both or either of the UID or GID may be -1. func resolveUIDGID(p *Permission) (uid, gid int, err error) { if p.UID == "$r" { uid = os.Getuid() } else if p.UID != "" { uid, err = passwd.ParseUID(p.UID) if err != nil { return } } else { uid = -1 } if p.GID == "$r" { gid = os.Getgid() } else if p.GID != "" { gid, err = passwd.ParseGID(p.GID) if err != nil { return } } else { gid = -1 } return }
func enforceGID(gid, path string) error { newGID, err := passwd.ParseGID(gid) if err != nil { return err } // So this is a surprisingly complicated dance if we want to be free of // potentially hazardous race conditions. We have a path. We can't assume // anything about its ownership, or mode, whether it's a symlink, etc. // // The big risk is that someone is able to create a symlink pointing to // something they want to illicitly access. Note that since /var/run will // commonly be used and because this directory is world-writeable, ala /tmp, // this is a real risk. // // So we have to make sure we don't follow symlinks. Assume we are running // as root (necessary, since we're chowning), and that nothing running as // root is malicious. // // We open the directory as a file so we can modify it using that reference // without worrying about the resolution of the path changing under us. But // we need to make sure we don't follow symlinks. This requires special OS // support, alas. dir, err := deos.OpenNoSymlinks(path) if err != nil { return err } defer dir.Close() fi, err := dir.Stat() if err != nil { return err } // Attributes of the directory can still change, but its type certainly // can't. This guarantee is enough for our purposes. if (fi.Mode() & os.ModeType) != os.ModeDir { return fmt.Errorf("challenge path %#v is not a directory", path) } curUID, err := deos.GetFileUID(fi) if err != nil { return err } dir.Chmod((fi.Mode() | 0070) & ^os.ModeType) // Ignore errors. dir.Chown(curUID, newGID) // Ignore errors. return nil }
func (h *ihandler) DropPrivileges() error { if h.dropped { return nil } // Extras if !h.info.NoBanSuid { // Try and bansuid, but don't process errors. It may not be supported on // the current platform, and Linux won't allow SECUREBITS to be set unless // one is root (or has the right capability). This is basically a // best-effort thing. bansuid.BanSuid() } // Various fixups if uidFlag.Value() != "" && gidFlag.Value() == "" { gid, err := passwd.GetGIDForUID(uidFlag.Value()) if err != nil { return err } gidFlag.SetValue(strconv.FormatInt(int64(gid), 10)) } if h.info.DefaultChroot == "" { h.info.DefaultChroot = "/" } chrootPath := chrootFlag.Value() if chrootPath == "" { chrootPath = h.info.DefaultChroot } uid := -1 gid := -1 if uidFlag.Value() != "" { var err error uid, err = passwd.ParseUID(uidFlag.Value()) if err != nil { return err } gid, err = passwd.ParseGID(gidFlag.Value()) if err != nil { return err } } if (uid <= 0) != (gid <= 0) { return fmt.Errorf("Either both or neither of the UID and GID must be positive") } if uid > 0 { chrootErr, err := daemon.DropPrivileges(uid, gid, chrootPath) if err != nil { return fmt.Errorf("Failed to drop privileges: %v", err) } if chrootErr != nil && chrootFlag.Value() != "" && chrootFlag.Value() != "/" { return fmt.Errorf("Failed to chroot: %v", chrootErr) } } else if chrootFlag.Value() != "" && chrootFlag.Value() != "/" { return fmt.Errorf("Must use privilege dropping to use chroot; set -uid") } // If we still have any caps (maybe because we didn't setuid), try and drop them. err := caps.Drop() if err != nil { return fmt.Errorf("cannot drop caps: %v", err) } if !h.info.AllowRoot && daemon.IsRoot() { return fmt.Errorf("Daemon must not run as root or with capabilities; run as non-root user or use -uid") } h.dropped = true return nil }
func parsePermissions(r io.Reader) (ps []Permission, erasePaths map[string]struct{}, err error) { br := bufio.NewReader(r) Lnum := 0 erasePaths = map[string]struct{}{} seenPaths := map[string]struct{}{} for { Lnum++ L, err := br.ReadString('\n') if err == io.EOF { break } if err != nil { return nil, nil, err } L = strings.TrimSpace(L) if L == "" || strings.HasPrefix(L, "#") { continue } // keys/*/privkey 0640 0750 - - m := re_permissionLine.FindStringSubmatch(L) if m == nil { return nil, nil, fmt.Errorf("line %d: badly formatted line: %q", Lnum, L) } path := filepath.Clean(m[1]) if path == ".." || strings.HasPrefix(path, "../") || filepath.IsAbs(path) { return nil, nil, fmt.Errorf("line %d: path must remain within the DB root", Lnum, L) } if _, seen := seenPaths[path]; seen { return nil, nil, fmt.Errorf("line %d: duplicate path entry: %q", Lnum, L) } seenPaths[path] = struct{}{} if m[2] == "inherit" { erasePaths[path] = struct{}{} continue } fileMode, err := strconv.ParseUint(m[3], 8, 12) if err != nil { return nil, nil, fmt.Errorf("line %d: invalid file mode: %q", Lnum, m[3]) } dirMode, err := strconv.ParseUint(m[4], 8, 12) if err != nil { return nil, nil, fmt.Errorf("line %d: invalid dir mode: %q", Lnum, m[4]) } // Validate UID uid := m[6] if uid == "-" { uid = "" } if uid != "" && uid != "$r" { _, err := passwd.ParseUID(uid) if err != nil { return nil, nil, fmt.Errorf("line %d: invalid UID: %q: %v", Lnum, uid, err) } } // Validate GID gid := m[7] if gid == "-" { gid = "" } if gid != "" && gid != "$r" { _, err = passwd.ParseGID(gid) if err != nil { return nil, nil, fmt.Errorf("line %d: invalid GID: %q: %v", Lnum, gid, err) } } // p := Permission{ Path: path, FileMode: os.FileMode(fileMode), DirMode: os.FileMode(dirMode), UID: uid, GID: gid, } ps = append(ps, p) } return ps, erasePaths, nil }