package reportbuf import ( "errors" "io/fs" "os" "os/user" "path/filepath" "strconv" ) // ErrDataDirOutsideVolume is returned by PrepareDataDir for a DATA_DIR // that is not the volume or a path below it, written in full. var ErrDataDirOutsideVolume = errors.New( "must be /data or a path below it, with no '.', '..' or extra '/'") // PrepareDataDir gets dir, the server's DATA_DIR, ready for owner, the // user the server runs as, so that a host directory mounted at volume, // /data in the image, needs no preparing: it creates dir, gives volume // and everything in it to owner, and gives volume and dir the mode the // server gives a directory it creates. dir must be volume or a path // below it, with no '.', '..', empty part or '/' at the end. // // bin/entrypoint.sh runs this as root, which would follow a symbolic // link anywhere, so every step goes through an os.Root opened on // volume: it follows a link only when it is written as a relative // path that stays inside volume, and refuses any other. A process on // the host can swap a link onto a path in volume at any moment while // this runs, but the os.Root checks each link as it reaches it, so the // most that can do is make a step fail or act on something else // inside volume. func PrepareDataDir(volume, dir string, owner *user.User) error { // rel is dir as a path from volume; IsLocal is false for one that // leads out of it. rel, err := filepath.Rel(volume, dir) if err != nil || dir != filepath.Clean(dir) || !filepath.IsLocal(rel) { return ErrDataDirOutsideVolume } uid, err := strconv.Atoi(owner.Uid) if err != nil { return err } gid, err := strconv.Atoi(owner.Gid) if err != nil { return err } root, err := os.OpenRoot(volume) if err != nil { return err } defer func() { _ = root.Close() }() err = root.MkdirAll(rel, dirPerms) if err != nil { return err } // WalkDir does not follow a symbolic link, and Lchown gives the // link itself to owner, not what it points to. err = fs.WalkDir(root.FS(), ".", func(name string, _ fs.DirEntry, err error) error { if err != nil { return err } return root.Lchown(name, uid, gid) }) if err != nil { return err } err = root.Chmod(".", dirPerms) if err != nil { return err } return root.Chmod(rel, dirPerms) }