Improve dependency resolver and fix critical bugs

- Add comprehensive Debian dependency resolver (400+ package mappings)

- Implement automatic recursive dependency resolution for build dependencies

- Fix logic errors in install.go: allowFailure handling, ensureBuildDependency

- Fix install-script.sh PKGDIR environment variable handling

- Fix hardcoded /tmp/install-script.sh path in autoRecipeFromDebian

- Fix wrong yyjson URL

- Increase build timeout from 30min to 2 hours

- Improve command validation security

- Fix temporary directory leaks in installer

- Update .gitignore to include zsvo-test binary
This commit is contained in:
itexpert228 2026-03-14 19:54:49 +03:00
parent fc315b1c2c
commit fbe74e8a87
No known key found for this signature in database
5 changed files with 1311 additions and 40 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
# Binary executables # Binary executables
zsvo zsvo
zsvo-test
pkg-manager pkg-manager
# Build artifacts # Build artifacts

View file

@ -191,6 +191,7 @@ type autoBuildSession struct {
toolRoot string toolRoot string
autoBuildDeps bool autoBuildDeps bool
resolver *debian.Resolver resolver *debian.Resolver
depResolver *debian.DependencyResolver
builder *builder.Builder builder *builder.Builder
toolInstaller *installer.Installer toolInstaller *installer.Installer
builtPackages map[string]string builtPackages map[string]string
@ -207,6 +208,7 @@ func newAutoBuildSession(workDir string, autoBuildDeps bool) *autoBuildSession {
toolRoot: filepath.Join(workDir, "bootstrap-root"), toolRoot: filepath.Join(workDir, "bootstrap-root"),
autoBuildDeps: autoBuildDeps, autoBuildDeps: autoBuildDeps,
resolver: debian.NewResolver(), resolver: debian.NewResolver(),
depResolver: debian.NewDependencyResolver(),
builder: b, builder: b,
toolInstaller: installer.NewInstaller(filepath.Join(workDir, "bootstrap-root")), toolInstaller: installer.NewInstaller(filepath.Join(workDir, "bootstrap-root")),
builtPackages: make(map[string]string), builtPackages: make(map[string]string),
@ -302,14 +304,66 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
fmt.Printf("Building %s from %s...\n", rcp.GetPackageName(), srcInfo.DSCURL) fmt.Printf("Building %s from %s...\n", rcp.GetPackageName(), srcInfo.DSCURL)
// ВАЖНО: Мы НЕ собираем Debian зависимости! // Авто-разрешение зависимостей: строим все Build-Depends рекурсивно
// Мы используем Debian только как источник исходников if len(srcInfo.BuildDepends) > 0 && s.autoBuildDeps {
// Все зависимости должны быть уже установлены в LFS системе fmt.Printf("🔍 Resolving %d build dependencies for %s...\n", len(srcInfo.BuildDepends), requestName)
if len(srcInfo.BuildDepends) > 0 {
fmt.Printf("⚠️ Found Debian Build-Depends: %s\n", strings.Join(srcInfo.BuildDepends, ", ")) // Collect dependencies that need to be built
fmt.Printf(" In LFS, make sure these dependencies are available in your toolchain\n") depsToBuild := make([]string, 0)
for _, dep := range srcInfo.BuildDepends {
depName := extractPackageNameFromConstraint(dep)
if depName == "" {
continue
} }
// Check if this is a Debian-specific package that should be skipped
sourcePkg := mapDebianPackageToSource(depName)
if sourcePkg == "" {
// Try comprehensive resolver
sourcePkg, _ = s.depResolver.BinaryToSource(depName)
if sourcePkg == "" {
fmt.Printf(" ⚠️ Skipping Debian-specific dependency: %s\n", depName)
continue
}
}
// Check if already built or available
if _, built := s.builtPackages[sourcePkg]; built {
continue
}
if toolAlreadyAvailable(sourcePkg) {
s.toolDepsReady[sourcePkg] = struct{}{}
continue
}
if _, ready := s.toolDepsReady[sourcePkg]; ready {
continue
}
depsToBuild = append(depsToBuild, sourcePkg)
}
// Build all dependencies first (in order)
if len(depsToBuild) > 0 {
fmt.Printf("📦 Need to build %d dependencies: %s\n", len(depsToBuild), strings.Join(depsToBuild, ", "))
for _, dep := range depsToBuild {
// Recursive build with dependency tracking
_, err := s.buildPackageWithFallback(dep, true, false, append(stack, requestName))
if err != nil {
fmt.Printf(" ⚠️ Failed to build dependency %s: %v (continuing anyway)\n", dep, err)
// Don't fail immediately - the main package might still build
}
}
// Refresh environment after building dependencies
s.refreshBuildEnv()
} else {
fmt.Printf(" ✅ All dependencies already available\n")
}
}
// Now build the main package
var buildErr error var buildErr error
for attempt := 0; attempt < 2; attempt++ { for attempt := 0; attempt < 2; attempt++ {
bar := newProgressUI(rcp.Name) bar := newProgressUI(rcp.Name)
@ -335,16 +389,18 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
} }
builtPackage := filepath.Join(rcp.GetPackageDir(s.workDir), rcp.GetPackageFileName()) builtPackage := filepath.Join(rcp.GetPackageDir(s.workDir), rcp.GetPackageFileName())
s.builtPackages[rcp.Name] = builtPackage
if buildErr != nil { if buildErr != nil {
if !allowFailure { if !allowFailure {
return "", fmt.Errorf("failed to auto-build %s: %w", requestName, buildErr) return "", fmt.Errorf("failed to auto-build %s: %w", requestName, buildErr)
} }
if err := s.installBuildDependency(requestName, builtPackage); err != nil { // allowFailure=true means we tolerate build failure, but we shouldn't try to install a broken package
return "", err // Log and return empty - caller should handle gracefully
} fmt.Printf("Warning: build of %s failed but allowFailure=true, skipping installation of broken package\n", requestName)
return "", fmt.Errorf("build failed for %s (allowFailure set): %w", requestName, buildErr)
} }
s.builtPackages[rcp.Name] = builtPackage
return builtPackage, nil return builtPackage, nil
} }
@ -353,8 +409,12 @@ func (s *autoBuildSession) installSystemPackage(pkg string, currentBuildingPacka
// Try to resolve and build the package as a dependency // Try to resolve and build the package as a dependency
fmt.Printf("Building system dependency %s through zsvo...\n", pkg) fmt.Printf("Building system dependency %s through zsvo...\n", pkg)
// Map common Debian package names to source packages // Use comprehensive Debian resolver to map binary package to source
sourcePkg := mapDebianPackageToSource(pkg) sourcePkg, err := s.depResolver.BinaryToSource(pkg)
if err != nil {
// Fall back to built-in mapping for common packages
sourcePkg = mapDebianPackageToSource(pkg)
}
if sourcePkg == "" { if sourcePkg == "" {
fmt.Printf("Skipping Debian-specific dependency: %s\n", pkg) fmt.Printf("Skipping Debian-specific dependency: %s\n", pkg)
return nil // Skip Debian-specific packages return nil // Skip Debian-specific packages
@ -367,7 +427,7 @@ func (s *autoBuildSession) installSystemPackage(pkg string, currentBuildingPacka
} }
// Try to build the dependency // Try to build the dependency
_, err := s.buildPackageWithFallback(sourcePkg, true, false, []string{}) _, err = s.buildPackageWithFallback(sourcePkg, true, false, []string{})
if err != nil { if err != nil {
return fmt.Errorf("failed to build system dependency %s (mapped from %s): %w", sourcePkg, pkg, err) return fmt.Errorf("failed to build system dependency %s (mapped from %s): %w", sourcePkg, pkg, err)
} }
@ -403,8 +463,12 @@ func (s *autoBuildSession) ensureBuildDependency(dep string, stack []string) err
fmt.Printf("Installing build dependency %s into %s...\n", dep, s.toolRoot) fmt.Printf("Installing build dependency %s into %s...\n", dep, s.toolRoot)
packagePath, err := s.buildPackageWithFallback(dep, true, false, append(stack, dep)) packagePath, err := s.buildPackageWithFallback(dep, true, false, append(stack, dep))
if err != nil { if err != nil {
fmt.Printf("Warning: failed to build dependency %s: %v (continuing anyway)\n", dep, err) fmt.Printf("Warning: failed to build dependency %s: %v (skipping installation)\n", dep, err)
// Continue anyway instead of failing return nil // Don't try to install a package that failed to build
}
if packagePath == "" {
fmt.Printf("Warning: build succeeded but no package path returned for %s (skipping installation)\n", dep)
return nil
} }
return s.installBuildDependency(dep, packagePath) return s.installBuildDependency(dep, packagePath)
} }
@ -958,9 +1022,12 @@ func autoRecipeFromDebian(src *debian.SourceInfo) *recipe.Recipe {
version = "0" version = "0"
} }
// Use external install script to avoid shell escaping issues // Use inline install commands instead of external script to avoid hardcoded paths
installCommands := []string{ installCommands := []string{
"PKGDIR={{pkgdir}} bash /tmp/install-script.sh", "if [ -f build/cmake_install.cmake ]; then DESTDIR={{pkgdir}} cmake --install build; " +
"elif [ -f build/meson-private/coredata.dat ]; then DESTDIR={{pkgdir}} meson install -C build; " +
"elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then make DESTDIR={{pkgdir}} PREFIX=/usr install; " +
"else mkdir -p {{pkgdir}}/usr/bin {{pkgdir}}/usr/lib {{pkgdir}}/usr/include; fi",
} }
return &recipe.Recipe{ return &recipe.Recipe{
@ -974,8 +1041,8 @@ func autoRecipeFromDebian(src *debian.SourceInfo) *recipe.Recipe {
Build: []string{ Build: []string{
"if [ ! -f configure ] && [ -f autogen.sh ]; then sh ./autogen.sh --no-check ${ZSVO_AUTOGEN_ARGS}; fi; if [ -f configure ]; then ./configure --prefix=/usr ${ZSVO_CONFIGURE_FLAGS}; fi", "if [ ! -f configure ] && [ -f autogen.sh ]; then sh ./autogen.sh --no-check ${ZSVO_AUTOGEN_ARGS}; fi; if [ -f configure ]; then ./configure --prefix=/usr ${ZSVO_CONFIGURE_FLAGS}; fi",
"# Download missing dependencies automatically", "# Download missing dependencies automatically",
"if [ ! -f \"src/3rdparty/yyjson/yyjson.c\" ]; then echo \"Downloading yyjson...\" && mkdir -p src/3rdparty/yyjson && curl -L https://raw.githubusercontent.com/yyjson/yyjson.c/master/src/yyjson.c -o src/3rdparty/yyjson/yyjson.c; fi", "if [ ! -f \"src/3rdparty/yyjson/yyjson.c\" ]; then echo \"Downloading yyjson...\" && mkdir -p src/3rdparty/yyjson && curl -L https://raw.githubusercontent.com/ibireme/yyjson/master/src/yyjson.c -o src/3rdparty/yyjson/yyjson.c; fi",
"if [ ! -f \"src/3rdparty/yyjson/yyjson.h\" ]; then echo \"Downloading yyjson header...\" && mkdir -p src/3rdparty/yyjson && curl -L https://raw.githubusercontent.com/yyjson/yyjson.c/master/src/yyjson.h -o src/3rdparty/yyjson/yyjson.h; fi", "if [ ! -f \"src/3rdparty/yyjson/yyjson.h\" ]; then echo \"Downloading yyjson header...\" && mkdir -p src/3rdparty/yyjson && curl -L https://raw.githubusercontent.com/ibireme/yyjson/master/src/yyjson.h -o src/3rdparty/yyjson/yyjson.h; fi",
"if [ -f CMakeLists.txt ]; then cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DUNIX=1 -DCMAKE_DISABLE_FIND_PACKAGE_Win32=1 ${ZSVO_CMAKE_FLAGS} && cmake --build build -j${jobs} -- ${ZSVO_CMAKE_BUILD_FLAGS}; " + "if [ -f CMakeLists.txt ]; then cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DUNIX=1 -DCMAKE_DISABLE_FIND_PACKAGE_Win32=1 ${ZSVO_CMAKE_FLAGS} && cmake --build build -j${jobs} -- ${ZSVO_CMAKE_BUILD_FLAGS}; " +
"elif [ -f meson.build ]; then meson setup build --prefix=/usr ${ZSVO_MESON_SETUP_ARGS} && meson compile -C build -j${jobs} ${ZSVO_MESON_COMPILE_ARGS}; " + "elif [ -f meson.build ]; then meson setup build --prefix=/usr ${ZSVO_MESON_SETUP_ARGS} && meson compile -C build -j${jobs} ${ZSVO_MESON_COMPILE_ARGS}; " +
"elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then make -j${jobs} ${ZSVO_MAKE_FLAGS}; fi", "elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then make -j${jobs} ${ZSVO_MAKE_FLAGS}; fi",

View file

@ -2,7 +2,13 @@
# Simple install script for auto-generated packages # Simple install script for auto-generated packages
set -e set -e
PKGDIR="$1" # Use PKGDIR from environment, fallback to first argument for backward compatibility
PKGDIR="${PKGDIR:-$1}"
if [ -z "$PKGDIR" ]; then
echo "Error: PKGDIR not set. Usage: PKGDIR=/path/to/dest $0" >&2
exit 1
fi
echo "Installing to $PKGDIR" echo "Installing to $PKGDIR"

View file

@ -259,8 +259,8 @@ func (b *Builder) executeCommand(workDir, command string, env []string) error {
cmd.Dir = workDir cmd.Dir = workDir
cmd.Env = env cmd.Env = env
if b.quiet { if b.quiet {
// Create a context with timeout to prevent hanging // Create a context with timeout to prevent hanging - use 2 hours for large packages like gcc/llvm
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
defer cancel() defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", command) cmd := exec.CommandContext(ctx, "sh", "-c", command)
@ -277,8 +277,8 @@ func (b *Builder) executeCommand(workDir, command string, env []string) error {
return nil return nil
} }
// For non-quiet mode, still set a reasonable timeout // For non-quiet mode, still set a reasonable timeout - use 2 hours for large packages
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
defer cancel() defer cancel()
cmd = exec.CommandContext(ctx, "sh", "-c", command) cmd = exec.CommandContext(ctx, "sh", "-c", command)
cmd.Dir = workDir cmd.Dir = workDir
@ -614,25 +614,43 @@ func (b *Builder) validateSourceFiles(recipe *recipe.Recipe, sourceDir string) e
} }
func (b *Builder) validateCommand(command string) error { func (b *Builder) validateCommand(command string) error {
// List of dangerous patterns to block // Normalize for pattern matching
cmdLower := strings.ToLower(command)
// Remove common bypass attempts: quotes, spaces, backslashes for normalization
normalized := cmdLower
normalized = strings.ReplaceAll(normalized, `"`, "")
normalized = strings.ReplaceAll(normalized, `'`, "")
normalized = strings.ReplaceAll(normalized, ` `, "")
normalized = strings.ReplaceAll(normalized, `\`, "")
// List of dangerous patterns to block (check both original and normalized)
dangerousPatterns := []string{ dangerousPatterns := []string{
"rm -rf /", "rm -rf /",
"rm -rf /*", "rm -rf /*",
":(){ :|:& };:", // fork bomb "rm -rf/",
`:(){:|:&};:`, // fork bomb (normalized)
":(){ :|:& };:", // fork bomb (original)
"chmod 777 /", "chmod 777 /",
"chown root", "chown root",
"sudo ", "sudo ",
"su ", "su -",
"> /dev/sda",
"> /dev/hda",
">/dev/sda", ">/dev/sda",
">/dev/hda", ">/dev/hda",
"mkfs", "mkfs",
"format", "format /",
"fdisk", "fdisk",
// Additional patterns
"dd if=", "dd if=",
"chmod -R 777 /", "chmod -R 777 /",
"chown -R root", "chown -R root",
"rm -rf /*", "rm -rf /etc",
"rm -rf /usr",
"rm -rf /bin",
"rm -rf /sbin",
"rm -rf /lib",
"rm -rf /lib64",
"rm-rf/etc", "rm-rf/etc",
"rm-rf/usr", "rm-rf/usr",
"rm-rf/bin", "rm-rf/bin",
@ -645,17 +663,25 @@ func (b *Builder) validateCommand(command string) error {
"poweroff", "poweroff",
"init 0", "init 0",
"init 6", "init 6",
"eval",
"$(", // command substitution
"${", // parameter expansion
} }
cmdLower := strings.ToLower(command) // Check against original
// Check for dangerous patterns
for _, pattern := range dangerousPatterns { for _, pattern := range dangerousPatterns {
if strings.Contains(cmdLower, pattern) { if strings.Contains(cmdLower, pattern) {
return fmt.Errorf("command contains potentially dangerous pattern: %s", pattern) return fmt.Errorf("command contains potentially dangerous pattern: %s", pattern)
} }
} }
// Check against normalized (catches bypass attempts like 'rm -rf "/"' or 'rm -rf / ')
for _, pattern := range dangerousPatterns {
if strings.Contains(normalized, pattern) {
return fmt.Errorf("command contains potentially dangerous pattern (normalized): %s", pattern)
}
}
// Check for suspicious characters that might indicate injection // Check for suspicious characters that might indicate injection
suspiciousChars := []string{"\x00", "\r", "\n", "\t"} suspiciousChars := []string{"\x00", "\r", "\n", "\t"}
for _, char := range suspiciousChars { for _, char := range suspiciousChars {
@ -676,14 +702,12 @@ func (b *Builder) validateCommand(command string) error {
} }
// Check for command substitution patterns that could bypass security // Check for command substitution patterns that could bypass security
dangerousSubstitutions := []string{ // Backticks and $() are blocked
"`", if strings.Contains(command, "`") {
"$|", return fmt.Errorf("command contains potentially dangerous backtick substitution")
}
for _, pattern := range dangerousSubstitutions {
if strings.Contains(command, pattern) {
return fmt.Errorf("command contains potentially dangerous substitution: %s", pattern)
} }
if strings.Contains(command, "$(") {
return fmt.Errorf("command contains potentially dangerous command substitution $()")
} }
// Log the command for audit purposes (only first 100 chars) // Log the command for audit purposes (only first 100 chars)

1173
pkg/debian/dep_resolver.go Normal file

File diff suppressed because it is too large Load diff