Skip to content

Commit c7abe3d

Browse files
authored
MGMT-24427: Validate boot artifacts in ISO after download (#854)
After downloading an ISO during Populate(), validate that expected boot artifacts (rootfs.img, vmlinuz) exist inside the ISO and are above minimum size thresholds. This extends the existing post-download validation (validateISOID) with boot artifact integrity checks. On failure, the ISO is removed and startup is blocked — same behavior as the existing volume ID validation. This catches cases where an ISO is internally corrupt, has a different layout, or was corrupted after download. Note: This does not fix OCPBUGS-86067 directly (which was a network-side truncation). This adds server-side defense-in-depth for ISO integrity. Assisted-by: Claude (claude.ai/code) Signed-off-by: Abhijeet Sadawarte <asadawar@redhat.com>
1 parent 2b81dd8 commit c7abe3d

2 files changed

Lines changed: 247 additions & 21 deletions

File tree

pkg/imagestore/imagestore.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ const (
9090
ImageTypeFull = "full-iso"
9191
ImageTypeMinimal = "minimal-iso"
9292
ImageTypeDisconnectedIso = "disconnected-iso"
93+
94+
// Minimum expected sizes for boot artifacts in RHCOS ISOs.
95+
// Reference: RHCOS artifacts on mirror.openshift.com — rootfs is ~1.1 GB, kernel is ~13 MB
96+
// for a typical x86_64 release. Thresholds are set ~10x below real sizes to catch
97+
// truncated downloads without false-positiving on normal variance across releases.
98+
minRootfsSize = 100 * 1024 * 1024
99+
minKernelSize = 5 * 1024 * 1024
93100
)
94101

95102
func NewImageStore(ed isoeditor.Editor, dataDir, imageServiceBaseURL string, insecureSkipVerify bool, versions []map[string]string,
@@ -233,6 +240,40 @@ func validateISOID(path string) error {
233240
return nil
234241
}
235242

243+
type bootArtifactValidationError struct {
244+
message string
245+
}
246+
247+
func (e *bootArtifactValidationError) Error() string { return e.message }
248+
249+
// validateBootArtifacts checks that the downloaded ISO contains rootfs and kernel
250+
// above minimum expected sizes. This catches rare cases where a download is truncated
251+
// by a CDN or proxy, producing an ISO that passes volume ID validation but fails to boot.
252+
func validateBootArtifacts(isoPath, arch string) error {
253+
_, rootfsSize, err := isoeditor.GetISOFileInfo(isoeditor.RootfsImagePath, isoPath)
254+
if err != nil {
255+
return &bootArtifactValidationError{fmt.Sprintf("rootfs not found in ISO: %v", err)}
256+
}
257+
if rootfsSize < minRootfsSize {
258+
return &bootArtifactValidationError{fmt.Sprintf("rootfs size %d bytes is below minimum %d", rootfsSize, minRootfsSize)}
259+
}
260+
261+
kernelPath := "/images/pxeboot/vmlinuz"
262+
_, kernelSize, err := isoeditor.GetISOFileInfo(kernelPath, isoPath)
263+
if err != nil && arch == "s390x" {
264+
kernelPath = "/images/pxeboot/kernel.img"
265+
_, kernelSize, err = isoeditor.GetISOFileInfo(kernelPath, isoPath)
266+
}
267+
if err != nil {
268+
return &bootArtifactValidationError{fmt.Sprintf("kernel not found in ISO: %v", err)}
269+
}
270+
if kernelSize < minKernelSize {
271+
return &bootArtifactValidationError{fmt.Sprintf("kernel size %d bytes is below minimum %d", kernelSize, minKernelSize)}
272+
}
273+
274+
return nil
275+
}
276+
236277
func (s *rhcosStore) Populate(ctx context.Context) error {
237278
if err := s.cleanDataDir(); err != nil {
238279
return err
@@ -270,6 +311,18 @@ func (s *rhcosStore) Populate(ctx context.Context) error {
270311
log.Error(message)
271312
return errors.New(message)
272313
}
314+
if err := validateBootArtifacts(fullPath, arch); err != nil {
315+
var sizeErr *bootArtifactValidationError
316+
if errors.As(err, &sizeErr) {
317+
message := fmt.Sprintf("failed to validate boot artifacts in %s: %v", fullPath, err)
318+
if removeErr := os.Remove(fullPath); removeErr != nil {
319+
log.WithError(removeErr).Errorf("failed to remove corrupt ISO %s", fullPath)
320+
}
321+
log.Error(message)
322+
return errors.New(message)
323+
}
324+
log.WithError(err).Warningf("boot artifact validation could not complete for %s", fullPath)
325+
}
273326
}
274327

275328
return nil

0 commit comments

Comments
 (0)