Skip to content

MIC: Add support for self-contained initrd for LiveOS ISO images #233

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 46 commits into
base: main
Choose a base branch
from

Conversation

gmileka
Copy link
Contributor

@gmileka gmileka commented May 7, 2025


Checklist

  • Tests added/updated
  • Documentation updated (if needed)
  • Code conforms to style guidelines

@gmileka gmileka changed the title User/gmileka/only initrd MIC: Add support for self-contained initrd for LiveOS ISO images May 8, 2025
@gmileka gmileka force-pushed the user/gmileka/only-initrd branch from 3232c46 to 19b21eb Compare May 9, 2025 01:46
@gmileka gmileka force-pushed the user/gmileka/only-initrd branch from 19b21eb to 4b3f6ed Compare May 9, 2025 01:51
return fmt.Errorf("error reading tar: %w", err)
}

target := filepath.Join(outputDir, header.Name)

Check failure

Code scanning / CodeQL

Arbitrary file write extracting an archive containing symbolic links High

Unresolved path from an archive header, which may point outside the archive root, is used in
symlink creation
.

Copilot Autofix

AI 3 days ago

To fix the issue, we need to ensure that paths extracted from the archive are validated and resolved to prevent directory traversal or symbolic link exploitation. Specifically:

  1. Use filepath.EvalSymlinks to resolve any symbolic links in the target directory before validating paths.
  2. Ensure that both header.Name and header.Linkname are checked to confirm they do not escape the intended extraction directory.
  3. Reject any paths that resolve outside the outputDir.

Changes will be made to the expandTarGzArchive function:

  • Add validation logic for header.Name and header.Linkname using filepath.EvalSymlinks.
  • Modify the symbolic link creation logic to ensure the resolved paths are safe.

Suggested changeset 1
toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go b/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
--- a/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
+++ b/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
@@ -620,3 +620,11 @@
 
-		target := filepath.Join(outputDir, header.Name)
+		// Resolve and validate the target path
+		resolvedTarget, err := filepath.EvalSymlinks(filepath.Join(outputDir, header.Name))
+		if err != nil {
+			return fmt.Errorf("failed to resolve path (%s):\n%w", header.Name, err)
+		}
+		relPath, err := filepath.Rel(outputDir, resolvedTarget)
+		if err != nil || strings.HasPrefix(filepath.Clean(relPath), "..") {
+			return fmt.Errorf("invalid path (%s) escaping output directory", resolvedTarget)
+		}
 
@@ -624,12 +632,12 @@
 		case tar.TypeDir:
-			if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
-				return fmt.Errorf("failed to create folder (%s)\n%w", target, err)
+			if err := os.MkdirAll(resolvedTarget, os.FileMode(header.Mode)); err != nil {
+				return fmt.Errorf("failed to create folder (%s)\n%w", resolvedTarget, err)
 			}
 		case tar.TypeReg:
-			if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
-				return fmt.Errorf("failed to create parent folder for (%s)\n%w", target, err)
+			if err := os.MkdirAll(filepath.Dir(resolvedTarget), 0755); err != nil {
+				return fmt.Errorf("failed to create parent folder for (%s)\n%w", resolvedTarget, err)
 			}
-			outFile, err := os.Create(target)
+			outFile, err := os.Create(resolvedTarget)
 			if err != nil {
-				return fmt.Errorf("failed to create (%s):\n%w", target, err)
+				return fmt.Errorf("failed to create (%s):\n%w", resolvedTarget, err)
 			}
@@ -638,3 +646,3 @@
 			if err != nil {
-				return fmt.Errorf("failed to copy (%s) from archive:\n%w", target, err)
+				return fmt.Errorf("failed to copy (%s) from archive:\n%w", resolvedTarget, err)
 			}
@@ -642,11 +650,20 @@
 
-			if err := os.Chmod(target, os.FileMode(header.Mode)); err != nil {
-				return fmt.Errorf("failed to set permissions (%d) on (%s):\n%w", os.FileMode(header.Mode), target, err)
+			if err := os.Chmod(resolvedTarget, os.FileMode(header.Mode)); err != nil {
+				return fmt.Errorf("failed to set permissions (%d) on (%s):\n%w", os.FileMode(header.Mode), resolvedTarget, err)
 			}
 		case tar.TypeSymlink:
-			if err := os.Symlink(header.Linkname, target); err != nil {
-				return fmt.Errorf("failed to create symbolic link (%s):\n%w", target, err)
+			// Resolve and validate the link target
+			resolvedLinkTarget, err := filepath.EvalSymlinks(filepath.Join(outputDir, header.Linkname))
+			if err != nil {
+				return fmt.Errorf("failed to resolve link target (%s):\n%w", header.Linkname, err)
+			}
+			relLinkPath, err := filepath.Rel(outputDir, resolvedLinkTarget)
+			if err != nil || strings.HasPrefix(filepath.Clean(relLinkPath), "..") {
+				return fmt.Errorf("invalid link target (%s) escaping output directory", resolvedLinkTarget)
+			}
+			if err := os.Symlink(resolvedLinkTarget, resolvedTarget); err != nil {
+				return fmt.Errorf("failed to create symbolic link (%s):\n%w", resolvedTarget, err)
 			}
 		default:
-			return fmt.Errorf("failed to process unsupported file type in archive (%s): (%v)", target, header.Typeflag)
+			return fmt.Errorf("failed to process unsupported file type in archive (%s): (%v)", resolvedTarget, header.Typeflag)
 		}
EOF
@@ -620,3 +620,11 @@

target := filepath.Join(outputDir, header.Name)
// Resolve and validate the target path
resolvedTarget, err := filepath.EvalSymlinks(filepath.Join(outputDir, header.Name))
if err != nil {
return fmt.Errorf("failed to resolve path (%s):\n%w", header.Name, err)
}
relPath, err := filepath.Rel(outputDir, resolvedTarget)
if err != nil || strings.HasPrefix(filepath.Clean(relPath), "..") {
return fmt.Errorf("invalid path (%s) escaping output directory", resolvedTarget)
}

@@ -624,12 +632,12 @@
case tar.TypeDir:
if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
return fmt.Errorf("failed to create folder (%s)\n%w", target, err)
if err := os.MkdirAll(resolvedTarget, os.FileMode(header.Mode)); err != nil {
return fmt.Errorf("failed to create folder (%s)\n%w", resolvedTarget, err)
}
case tar.TypeReg:
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return fmt.Errorf("failed to create parent folder for (%s)\n%w", target, err)
if err := os.MkdirAll(filepath.Dir(resolvedTarget), 0755); err != nil {
return fmt.Errorf("failed to create parent folder for (%s)\n%w", resolvedTarget, err)
}
outFile, err := os.Create(target)
outFile, err := os.Create(resolvedTarget)
if err != nil {
return fmt.Errorf("failed to create (%s):\n%w", target, err)
return fmt.Errorf("failed to create (%s):\n%w", resolvedTarget, err)
}
@@ -638,3 +646,3 @@
if err != nil {
return fmt.Errorf("failed to copy (%s) from archive:\n%w", target, err)
return fmt.Errorf("failed to copy (%s) from archive:\n%w", resolvedTarget, err)
}
@@ -642,11 +650,20 @@

if err := os.Chmod(target, os.FileMode(header.Mode)); err != nil {
return fmt.Errorf("failed to set permissions (%d) on (%s):\n%w", os.FileMode(header.Mode), target, err)
if err := os.Chmod(resolvedTarget, os.FileMode(header.Mode)); err != nil {
return fmt.Errorf("failed to set permissions (%d) on (%s):\n%w", os.FileMode(header.Mode), resolvedTarget, err)
}
case tar.TypeSymlink:
if err := os.Symlink(header.Linkname, target); err != nil {
return fmt.Errorf("failed to create symbolic link (%s):\n%w", target, err)
// Resolve and validate the link target
resolvedLinkTarget, err := filepath.EvalSymlinks(filepath.Join(outputDir, header.Linkname))
if err != nil {
return fmt.Errorf("failed to resolve link target (%s):\n%w", header.Linkname, err)
}
relLinkPath, err := filepath.Rel(outputDir, resolvedLinkTarget)
if err != nil || strings.HasPrefix(filepath.Clean(relLinkPath), "..") {
return fmt.Errorf("invalid link target (%s) escaping output directory", resolvedLinkTarget)
}
if err := os.Symlink(resolvedLinkTarget, resolvedTarget); err != nil {
return fmt.Errorf("failed to create symbolic link (%s):\n%w", resolvedTarget, err)
}
default:
return fmt.Errorf("failed to process unsupported file type in archive (%s): (%v)", target, header.Typeflag)
return fmt.Errorf("failed to process unsupported file type in archive (%s): (%v)", resolvedTarget, header.Typeflag)
}
Copilot is powered by AI and may make mistakes. Always verify output.

tr := tar.NewReader(gzr)
for {
header, err := tr.Next()

Check failure

Code scanning / CodeQL

Arbitrary file access during archive extraction ("Zip Slip") High

Unsanitized archive entry, which may contain '..', is used in a
file system operation
.
Unsanitized archive entry, which may contain '..', is used in a
file system operation
.
Unsanitized archive entry, which may contain '..', is used in a
file system operation
.
Unsanitized archive entry, which may contain '..', is used in a
file system operation
.

Copilot Autofix

AI 3 days ago

To fix the issue, we need to validate the header.Name field before using it to construct file paths. Specifically, we should ensure that the path does not contain directory traversal elements (..) or absolute paths. This can be achieved by checking the sanitized path against the intended outputDir to ensure it remains within the expected directory.

Steps to fix:

  1. Use filepath.Clean to sanitize the header.Name field.
  2. Ensure the resulting path is within the outputDir by comparing the base directory of the constructed path with outputDir.
  3. Reject or skip entries that fail these checks.

Suggested changeset 1
toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go b/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
--- a/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
+++ b/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
@@ -620,3 +620,13 @@
 
-		target := filepath.Join(outputDir, header.Name)
+		// Sanitize and validate the path
+		cleanedName := filepath.Clean(header.Name)
+		if strings.Contains(cleanedName, "..") || filepath.IsAbs(cleanedName) {
+			logger.Log.Warnf("Skipping potentially unsafe archive entry: %s", header.Name)
+			continue
+		}
+		target := filepath.Join(outputDir, cleanedName)
+		if !strings.HasPrefix(target, filepath.Clean(outputDir)+string(os.PathSeparator)) {
+			logger.Log.Warnf("Skipping entry outside output directory: %s", header.Name)
+			continue
+		}
 
EOF
@@ -620,3 +620,13 @@

target := filepath.Join(outputDir, header.Name)
// Sanitize and validate the path
cleanedName := filepath.Clean(header.Name)
if strings.Contains(cleanedName, "..") || filepath.IsAbs(cleanedName) {
logger.Log.Warnf("Skipping potentially unsafe archive entry: %s", header.Name)
continue
}
target := filepath.Join(outputDir, cleanedName)
if !strings.HasPrefix(target, filepath.Clean(outputDir)+string(os.PathSeparator)) {
logger.Log.Warnf("Skipping entry outside output directory: %s", header.Name)
continue
}

Copilot is powered by AI and may make mistakes. Always verify output.
return fmt.Errorf("failed to set permissions (%d) on (%s):\n%w", os.FileMode(header.Mode), target, err)
}
case tar.TypeSymlink:
if err := os.Symlink(header.Linkname, target); err != nil {

Check failure

Code scanning / CodeQL

Arbitrary file write extracting an archive containing symbolic links High

Unresolved path from an archive header, which may point outside the archive root, is used in
symlink creation
.

Copilot Autofix

AI 2 days ago

To fix the issue, the code must validate and resolve symbolic links before creating them. Specifically:

  1. Use filepath.EvalSymlinks to resolve the header.Linkname and ensure it points to a location within the outputDir.
  2. Check the resolved path against the outputDir using filepath.Rel to confirm it does not escape the intended directory.
  3. Reject symbolic links that fail these checks to prevent arbitrary file writes outside the target directory.

The changes will be applied to the expandTarGzArchive function, specifically within the case tar.TypeSymlink block. Necessary imports and validations will be added.


Suggested changeset 1
toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go b/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
--- a/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
+++ b/toolkit/tools/pkg/imagecustomizerlib/liveosisoimages.go
@@ -646,3 +646,13 @@
 		case tar.TypeSymlink:
-			if err := os.Symlink(header.Linkname, target); err != nil {
+			// Resolve the symbolic link target and validate it
+			resolvedLinkname, err := filepath.EvalSymlinks(filepath.Join(outputDir, header.Linkname))
+			if err != nil {
+				return fmt.Errorf("failed to resolve symbolic link target (%s):\n%w", header.Linkname, err)
+			}
+			relPath, err := filepath.Rel(outputDir, resolvedLinkname)
+			if err != nil || strings.HasPrefix(filepath.Clean(relPath), "..") {
+				return fmt.Errorf("symbolic link target (%s) escapes the output directory (%s)", resolvedLinkname, outputDir)
+			}
+			// Create the symbolic link
+			if err := os.Symlink(resolvedLinkname, target); err != nil {
 				return fmt.Errorf("failed to create symbolic link (%s):\n%w", target, err)
EOF
@@ -646,3 +646,13 @@
case tar.TypeSymlink:
if err := os.Symlink(header.Linkname, target); err != nil {
// Resolve the symbolic link target and validate it
resolvedLinkname, err := filepath.EvalSymlinks(filepath.Join(outputDir, header.Linkname))
if err != nil {
return fmt.Errorf("failed to resolve symbolic link target (%s):\n%w", header.Linkname, err)
}
relPath, err := filepath.Rel(outputDir, resolvedLinkname)
if err != nil || strings.HasPrefix(filepath.Clean(relPath), "..") {
return fmt.Errorf("symbolic link target (%s) escapes the output directory (%s)", resolvedLinkname, outputDir)
}
// Create the symbolic link
if err := os.Symlink(resolvedLinkname, target); err != nil {
return fmt.Errorf("failed to create symbolic link (%s):\n%w", target, err)
Copilot is powered by AI and may make mistakes. Always verify output.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant