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 }
// Change all directory permissions to be correct. func (db *DB) conformPermissions() error { err := filepath.Walk(db.path, func(path string, info os.FileInfo, err error) error { if err != nil { return err } rpath, err := filepath.Rel(db.path, path) if err != nil { return err } mode := info.Mode() switch mode & os.ModeType { case 0: case os.ModeDir: db.extantDirs[rpath] = struct{}{} case os.ModeSymlink: l, err := os.Readlink(path) if err != nil { return err } if filepath.IsAbs(l) { return fmt.Errorf("database symlinks must not have absolute targets: %v: %v", path, l) } ll := filepath.Join(filepath.Dir(path), l) ll, err = filepath.Abs(ll) if err != nil { return err } ok, err := pathIsWithin(ll, db.path) if err != nil { return err } if !ok { return fmt.Errorf("database symlinks must point to within the database directory: %v: %v", path, ll) } _, err = os.Stat(ll) if os.IsNotExist(err) { log.Warnf("broken symlink, removing: %v -> %v", path, l) err := os.Remove(path) if err != nil { return err } } else if err != nil { log.Errore(err, "stat symlink") return err } default: return fmt.Errorf("unexpected file type in state directory: %s", mode) } perm := db.longestMatching(rpath) if perm == nil { log.Warnf("object without any permissions specified: %v", rpath) } else { correctPerm := perm.FileMode if (mode & os.ModeType) == os.ModeDir { correctPerm = perm.DirMode } if (mode & os.ModeType) != os.ModeSymlink { if fperm := mode.Perm(); fperm != correctPerm { log.Warnf("%#v has wrong mode %v, changing to %v", rpath, fperm, correctPerm) err := os.Chmod(path, correctPerm) if err != nil { return err } } } correctUID, correctGID, err := resolveUIDGID(perm) if err != nil { return err } if correctUID >= 0 || correctGID >= 0 { curUID, err := deos.GetFileUID(info) if err != nil { return err } curGID, err := deos.GetFileGID(info) if err != nil { return err } if correctUID < 0 { correctUID = curUID } if correctGID < 0 { correctGID = curGID } if curUID != correctUID || curGID != correctGID { log.Warnf("%#v has wrong UID/GID %v/%v, changing to %v/%v", rpath, curUID, curGID, correctUID, correctGID) err := os.Lchown(path, correctUID, correctGID) // Can't chown if not root so be a bit forgiving, but always moan log.Errore(err, "could not lchown file ", rpath) } } } return nil }) if err != nil { return err } return nil }