Repository: chainguard-dev/apko
Pull request #1488 (2025-01-21 23:12:43)

Guard against os-release panic


Patch:
diff --git a/go.mod b/go.mod
index 80038ac..fa10933 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,6 @@ require (
 	chainguard.dev/sdk v0.1.29
 	github.com/chainguard-dev/clog v1.5.1
 	github.com/charmbracelet/log v0.4.0
-	github.com/dominodatalab/os-release v0.0.0-20190522011736-bcdb4a3e3c2f
 	github.com/go-git/go-git/v5 v5.13.1
 	github.com/google/go-cmp v0.6.0
 	github.com/google/go-containerregistry v0.20.3
diff --git a/go.sum b/go.sum
index 8418824..fb15fe2 100644
--- a/go.sum
+++ b/go.sum
@@ -83,8 +83,6 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
 github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dominodatalab/os-release v0.0.0-20190522011736-bcdb4a3e3c2f h1:oEt43goQgsL1DzoOyQ/UZHQw7t9TqwyJec9W0vh0wfE=
-github.com/dominodatalab/os-release v0.0.0-20190522011736-bcdb4a3e3c2f/go.mod h1:RU3x9VqPvzbOGJ3wtP0pPBtUOp4yU/yzA/8qdxgi/6Q=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
diff --git a/pkg/build/sbom.go b/pkg/build/sbom.go
index ca783f6..557bf13 100644
--- a/pkg/build/sbom.go
+++ b/pkg/build/sbom.go
@@ -15,17 +15,17 @@
 package build
 
 import (
+	"bufio"
 	"context"
 	"errors"
 	"fmt"
-	"io"
 	"io/fs"
 	"log/slog"
 	"path/filepath"
 	"sort"
+	"strings"
 	"time"
 
-	osr "github.com/dominodatalab/os-release"
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	ggcrtypes "github.com/google/go-containerregistry/pkg/v1/types"
@@ -151,13 +151,20 @@ func (bc *Context) GenerateImageSBOM(ctx context.Context, arch types.Architectur
 	return sboms, nil
 }
 
+type ReleaseData struct {
+	ID         string
+	Name       string
+	PrettyName string
+	VersionID  string
+}
+
 // readReleaseData reads the information from /etc/os-release
 //
 // If no os-release file is found, it returns a Data struct with ID set to "unknown".
-func readReleaseData(fsys fs.FS) (*osr.Data, error) {
+func readReleaseData(fsys fs.FS) (*ReleaseData, error) {
 	f, err := fsys.Open("/etc/os-release")
 	if errors.Is(err, fs.ErrNotExist) {
-		return &osr.Data{
+		return &ReleaseData{
 			ID:        "unknown",
 			Name:      "apko-generated image",
 			VersionID: "unknown",
@@ -166,12 +173,38 @@ func readReleaseData(fsys fs.FS) (*osr.Data, error) {
 		return nil, fmt.Errorf("opening os-release: %w", err)
 	}
 	defer f.Close()
-	osReleaseData, err := io.ReadAll(f)
-	if err != nil {
+
+	scanner := bufio.NewScanner(f)
+
+	kv := map[string]string{}
+	for scanner.Scan() {
+		line := scanner.Text()
+		if line == "" {
+			continue
+		}
+
+		if strings.HasPrefix(line, "#") {
+			continue
+		}
+
+		before, after, ok := strings.Cut(line, "=")
+		if !ok {
+			return nil, fmt.Errorf("invalid os-release line: %q", line)
+		}
+
+		kv[before] = strings.Trim(after, "\"")
+	}
+
+	if err := scanner.Err(); err != nil {
 		return nil, fmt.Errorf("reading os-release: %w", err)
 	}
 
-	return osr.Parse(string(osReleaseData)), nil
+	return &ReleaseData{
+		ID:         kv["ID"],
+		Name:       kv["NAME"],
+		PrettyName: kv["PRETTY_NAME"],
+		VersionID:  kv["VERSION_ID"],
+	}, nil
 }
 
 func GenerateIndexSBOM(ctx context.Context, o options.Options, ic types.ImageConfiguration, indexDigest name.Digest, imgs map[types.Architecture]oci.SignedImage) ([]types.SBOM, error) {


Test patch:
diff --git a/pkg/build/sbom_test.go b/pkg/build/sbom_test.go
index 0335d83..cfca1d8 100644
--- a/pkg/build/sbom_test.go
+++ b/pkg/build/sbom_test.go
@@ -25,7 +25,8 @@ import (
 )
 
 func TestReadReleaseData(t *testing.T) {
-	osinfoData := `ID=wolfi
+	osinfoData := `# This is a comment that should be ignored.
+ID=wolfi
 NAME="Wolfi"
 PRETTY_NAME="Wolfi"
 VERSION_ID="2022, 20230914"
@@ -52,3 +53,14 @@ func TestReadReleaseData_EmptyDefaults(t *testing.T) {
 	require.Equal(t, "unknown", info.VersionID)
 	require.Equal(t, "", info.PrettyName)
 }
+
+func TestBadReleaseData(t *testing.T) {
+	osinfoData := `hello, world! this is not a valid os-release file
+`
+	fsys := apkfs.NewMemFS()
+	require.NoError(t, fsys.MkdirAll(filepath.Dir("/etc/os-release"), os.FileMode(0o644)))
+	require.NoError(t, fsys.WriteFile("/etc/os-release", []byte(osinfoData), os.FileMode(0o644)))
+	// Bad data in file should err.
+	_, err := readReleaseData(fsys)
+	require.Error(t, err)
+}