package bundle import ( "fmt" "os" "path/filepath" "strings" ) // WriteExtractedFiles writes payload files from a verified bundle under root (directories created as needed). func WriteExtractedFiles(root string, v *VerifiedContents) error { return WriteExtractedFilesWithLimits(root, v, Limits{}) } // WriteExtractedFilesWithLimits confines all filesystem operations to an open // directory handle. Replacing existing files uses unlink + exclusive creation, // so existing hard links and symlinks cannot redirect writes outside the root. func WriteExtractedFilesWithLimits(root string, v *VerifiedContents, limits Limits) error { l, err := limits.Normalized() if err != nil { return err } if v == nil { return fmt.Errorf("bundle: nil verified contents") } if len(v.Manifest.Files) > l.MaxEntries-2 { return ErrLimit } seen := map[string]bool{} var total int64 for _, fe := range v.Manifest.Files { if err := validatePath(fe.Path, l.MaxPathBytes); err != nil { return err } if seen[strings.ToLower(fe.Path)] { return fmt.Errorf("bundle: duplicate path %q", fe.Path) } seen[strings.ToLower(fe.Path)] = true data, ok := v.Files[fe.Path] if !ok { return fmt.Errorf("bundle: missing file %q", fe.Path) } if int64(len(data)) > l.MaxFileBytes || int64(len(data)) > l.MaxExpandedBytes-total { return ErrLimit } total += int64(len(data)) } root = filepath.Clean(root) if err := os.MkdirAll(root, 0o755); err != nil { return err } confined, err := os.OpenRoot(root) if err != nil { return err } defer func() { _ = confined.Close() }() for _, fe := range v.Manifest.Files { data, ok := v.Files[fe.Path] if !ok { return fmt.Errorf("bundle: missing file %q", fe.Path) } parts := strings.Split(fe.Path, "/") for i := 1; i < len(parts); i++ { dir := filepath.FromSlash(strings.Join(parts[:i], "/")) if err := confined.Mkdir(dir, 0o755); err != nil && !os.IsExist(err) { return err } info, err := confined.Lstat(dir) if err != nil { return err } if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 { return fmt.Errorf("bundle: unsafe directory %q", dir) } } dest := filepath.FromSlash(fe.Path) info, err := confined.Lstat(dest) if err == nil { if !info.Mode().IsRegular() { return fmt.Errorf("bundle: unsafe destination %q", dest) } if err = confined.Remove(dest); err != nil { return err } } else if !os.IsNotExist(err) { return err } f, err := confined.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644) if err != nil { return err } _, writeErr := f.Write(data) closeErr := f.Close() if writeErr != nil { return writeErr } if closeErr != nil { return closeErr } } return nil } // FindMainBirdConf returns the first path ending with bird.conf from extracted manifest order. func (v *VerifiedContents) FindMainBirdConf() string { for _, fe := range v.Manifest.Files { if strings.HasSuffix(fe.Path, "bird.conf") { return fe.Path } } return "" }