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
zsvo
zsvo-test
pkg-manager
# Build artifacts

View file

@ -191,6 +191,7 @@ type autoBuildSession struct {
toolRoot string
autoBuildDeps bool
resolver *debian.Resolver
depResolver *debian.DependencyResolver
builder *builder.Builder
toolInstaller *installer.Installer
builtPackages map[string]string
@ -207,6 +208,7 @@ func newAutoBuildSession(workDir string, autoBuildDeps bool) *autoBuildSession {
toolRoot: filepath.Join(workDir, "bootstrap-root"),
autoBuildDeps: autoBuildDeps,
resolver: debian.NewResolver(),
depResolver: debian.NewDependencyResolver(),
builder: b,
toolInstaller: installer.NewInstaller(filepath.Join(workDir, "bootstrap-root")),
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)
// ВАЖНО: Мы НЕ собираем Debian зависимости!
// Мы используем Debian только как источник исходников
// Все зависимости должны быть уже установлены в LFS системе
if len(srcInfo.BuildDepends) > 0 {
fmt.Printf("⚠️ Found Debian Build-Depends: %s\n", strings.Join(srcInfo.BuildDepends, ", "))
fmt.Printf(" In LFS, make sure these dependencies are available in your toolchain\n")
// Авто-разрешение зависимостей: строим все Build-Depends рекурсивно
if len(srcInfo.BuildDepends) > 0 && s.autoBuildDeps {
fmt.Printf("🔍 Resolving %d build dependencies for %s...\n", len(srcInfo.BuildDepends), requestName)
// Collect dependencies that need to be built
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
for attempt := 0; attempt < 2; attempt++ {
bar := newProgressUI(rcp.Name)
@ -335,16 +389,18 @@ func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildD
}
builtPackage := filepath.Join(rcp.GetPackageDir(s.workDir), rcp.GetPackageFileName())
s.builtPackages[rcp.Name] = builtPackage
if buildErr != nil {
if !allowFailure {
return "", fmt.Errorf("failed to auto-build %s: %w", requestName, buildErr)
}
if err := s.installBuildDependency(requestName, builtPackage); err != nil {
return "", err
}
// allowFailure=true means we tolerate build failure, but we shouldn't try to install a broken package
// 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
}
@ -353,8 +409,12 @@ func (s *autoBuildSession) installSystemPackage(pkg string, currentBuildingPacka
// Try to resolve and build the package as a dependency
fmt.Printf("Building system dependency %s through zsvo...\n", pkg)
// Map common Debian package names to source packages
sourcePkg := mapDebianPackageToSource(pkg)
// Use comprehensive Debian resolver to map binary package to source
sourcePkg, err := s.depResolver.BinaryToSource(pkg)
if err != nil {
// Fall back to built-in mapping for common packages
sourcePkg = mapDebianPackageToSource(pkg)
}
if sourcePkg == "" {
fmt.Printf("Skipping Debian-specific dependency: %s\n", pkg)
return nil // Skip Debian-specific packages
@ -367,7 +427,7 @@ func (s *autoBuildSession) installSystemPackage(pkg string, currentBuildingPacka
}
// Try to build the dependency
_, err := s.buildPackageWithFallback(sourcePkg, true, false, []string{})
_, err = s.buildPackageWithFallback(sourcePkg, true, false, []string{})
if err != nil {
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)
packagePath, err := s.buildPackageWithFallback(dep, true, false, append(stack, dep))
if err != nil {
fmt.Printf("Warning: failed to build dependency %s: %v (continuing anyway)\n", dep, err)
// Continue anyway instead of failing
fmt.Printf("Warning: failed to build dependency %s: %v (skipping installation)\n", dep, err)
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)
}
@ -958,9 +1022,12 @@ func autoRecipeFromDebian(src *debian.SourceInfo) *recipe.Recipe {
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{
"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{
@ -974,8 +1041,8 @@ func autoRecipeFromDebian(src *debian.SourceInfo) *recipe.Recipe {
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",
"# 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.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.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/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}; " +
"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",

View file

@ -2,7 +2,13 @@
# Simple install script for auto-generated packages
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"

View file

@ -259,8 +259,8 @@ func (b *Builder) executeCommand(workDir, command string, env []string) error {
cmd.Dir = workDir
cmd.Env = env
if b.quiet {
// Create a context with timeout to prevent hanging
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
// Create a context with timeout to prevent hanging - use 2 hours for large packages like gcc/llvm
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", command)
@ -277,8 +277,8 @@ func (b *Builder) executeCommand(workDir, command string, env []string) error {
return nil
}
// For non-quiet mode, still set a reasonable timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
// For non-quiet mode, still set a reasonable timeout - use 2 hours for large packages
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
defer cancel()
cmd = exec.CommandContext(ctx, "sh", "-c", command)
cmd.Dir = workDir
@ -614,25 +614,43 @@ func (b *Builder) validateSourceFiles(recipe *recipe.Recipe, sourceDir string) e
}
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{
"rm -rf /",
"rm -rf /*",
":(){ :|:& };:", // fork bomb
"rm -rf/",
`:(){:|:&};:`, // fork bomb (normalized)
":(){ :|:& };:", // fork bomb (original)
"chmod 777 /",
"chown root",
"sudo ",
"su ",
"su -",
"> /dev/sda",
"> /dev/hda",
">/dev/sda",
">/dev/hda",
"mkfs",
"format",
"format /",
"fdisk",
// Additional patterns
"dd if=",
"chmod -R 777 /",
"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/usr",
"rm-rf/bin",
@ -645,17 +663,25 @@ func (b *Builder) validateCommand(command string) error {
"poweroff",
"init 0",
"init 6",
"eval",
"$(", // command substitution
"${", // parameter expansion
}
cmdLower := strings.ToLower(command)
// Check for dangerous patterns
// Check against original
for _, pattern := range dangerousPatterns {
if strings.Contains(cmdLower, 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
suspiciousChars := []string{"\x00", "\r", "\n", "\t"}
for _, char := range suspiciousChars {
@ -676,14 +702,12 @@ func (b *Builder) validateCommand(command string) error {
}
// Check for command substitution patterns that could bypass security
dangerousSubstitutions := []string{
"`",
"$|",
}
for _, pattern := range dangerousSubstitutions {
if strings.Contains(command, pattern) {
return fmt.Errorf("command contains potentially dangerous substitution: %s", pattern)
// Backticks and $() are blocked
if strings.Contains(command, "`") {
return fmt.Errorf("command contains potentially dangerous backtick substitution")
}
if strings.Contains(command, "$(") {
return fmt.Errorf("command contains potentially dangerous command substitution $()")
}
// 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