From 704f9ee8722d559afe96aa39e0bfbf0606a8e391 Mon Sep 17 00:00:00 2001 From: Trilok Geer Date: Fri, 25 Jul 2025 19:29:08 +0530 Subject: [PATCH 1/2] UPSTREAM: : fix CVE-2025-53547 Signed-off-by: Trilok Geer Co-authored-by: Manish Pillai --- internal/plugins/helm/v1/chartutil/chart.go | 29 ++++++ .../plugins/helm/v1/chartutil/chart_test.go | 91 +++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/internal/plugins/helm/v1/chartutil/chart.go b/internal/plugins/helm/v1/chartutil/chart.go index 5587c2175e..a770d27500 100644 --- a/internal/plugins/helm/v1/chartutil/chart.go +++ b/internal/plugins/helm/v1/chartutil/chart.go @@ -190,9 +190,38 @@ func fetchChartDependencies(chartPath string) error { RepositoryConfig: settings.RepositoryConfig, RepositoryCache: settings.RepositoryCache, } + // CVE-2025-53547: Ensure Chart.lock is not a symlink before dependency build to prevent code injection + if err := validateForSymlink(chartPath); err != nil { + return err + } + if err := man.Build(); err != nil { fmt.Println(out.String()) return err } return nil } + +// validateForSymlink checks if Chart.lock is a symbolic link to prevent CVE-2025-53547. +// +// Reference: https://github.com/helm/helm/security/advisories/GHSA-557j-xg8c-q2mm +// +// Note: The upstream fix requires a higher Go version than is currently available +// downstream. Therefore, we are using this wrapper to avoid upgrading Helm or Go. +func validateForSymlink(chartPath string) error { + dest := filepath.Join(chartPath, "Chart.lock") + + info, err := os.Lstat(dest) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("error getting info for %q: %w", dest, err) + } else if err == nil { + if info.Mode()&os.ModeSymlink != 0 { + link, err := os.Readlink(dest) + if err != nil { + return fmt.Errorf("error reading symlink for %q: %w", dest, err) + } + return fmt.Errorf("the Chart.lock file is a symlink to %q. This could lead to code injection (CVE-2025-53547)", link) + } + } + return nil +} diff --git a/internal/plugins/helm/v1/chartutil/chart_test.go b/internal/plugins/helm/v1/chartutil/chart_test.go index 06d34ecc0b..e3b0a03e2f 100644 --- a/internal/plugins/helm/v1/chartutil/chart_test.go +++ b/internal/plugins/helm/v1/chartutil/chart_test.go @@ -209,3 +209,94 @@ func runTestCase(t *testing.T, testDir string, tc createChartTestCase) { assert.NoError(t, err) assert.Equal(t, filepath.Join(chartutil.HelmChartsDir, tc.expectChartName), chartPath) } + +// TestCVE2025_53547_Protection tests that ScaffoldChart properly detects and blocks symlinked Chart.lock files. +// This validates the CVE-2025-53547 mitigation implemented in validateForSymlink function. +// Reference: https://github.com/helm/helm/security/advisories/GHSA-557j-xg8c-q2mm +func TestCVE2025_53547_Protection(t *testing.T) { + tests := []struct { + name string + setupAfterScaffolding func(string) error // Setup function called after initial scaffolding + expectCVEError bool + }{ + { + name: "safe chart with no Chart.lock", + setupAfterScaffolding: func(scaffoldedChartPath string) error { + // Remove any Chart.lock file + chartLockPath := filepath.Join(scaffoldedChartPath, "Chart.lock") + os.Remove(chartLockPath) + return nil + }, + expectCVEError: false, + }, + { + name: "CVE-2025-53547: symlinked Chart.lock attack", + setupAfterScaffolding: func(scaffoldedChartPath string) error { + // Create target file for symlink + targetFile := filepath.Join(scaffoldedChartPath, "target.yaml") + targetContent := "dependencies: []" + if err := os.WriteFile(targetFile, []byte(targetContent), 0644); err != nil { + return err + } + + // Remove any existing Chart.lock and create symlink + chartLockPath := filepath.Join(scaffoldedChartPath, "Chart.lock") + os.Remove(chartLockPath) // Remove if exists + return os.Symlink("target.yaml", chartLockPath) + }, + expectCVEError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create temporary directories + tmpProjectDir, err := os.MkdirTemp("", "test-project-cve") + assert.NoError(t, err) + defer os.RemoveAll(tmpProjectDir) + + tmpChartDir, err := os.MkdirTemp("", "test-chart-cve") + assert.NoError(t, err) + defer os.RemoveAll(tmpChartDir) + + // Step 1: Create chart with dependencies to generate Chart.lock + chartYaml := `apiVersion: v2 +name: test-chart +version: 1.0.0 +appVersion: 1.0.0 +description: Test chart for CVE-2025-53547 testing +type: application +` + err = os.WriteFile(filepath.Join(tmpChartDir, "Chart.yaml"), []byte(chartYaml), 0644) + assert.NoError(t, err) + + // Step 2: Initial scaffolding + chrt, err := chartutil.LoadChart(chartutil.Options{Chart: tmpChartDir}) + assert.NoError(t, err) + assert.NotNil(t, chrt) + + _, _, err = chartutil.ScaffoldChart(chrt, tmpProjectDir) + assert.NoError(t, err) + + // Step 3: Setup test scenario in scaffolded chart + scaffoldedChartPath := filepath.Join(tmpProjectDir, chartutil.HelmChartsDir, chrt.Name()) + err = tt.setupAfterScaffolding(scaffoldedChartPath) + assert.NoError(t, err) + + // Step 4: Try to scaffold again + chrt2, err := chartutil.LoadChart(chartutil.Options{Chart: scaffoldedChartPath}) + assert.NoError(t, err) + assert.NotNil(t, chrt2) + + _, _, err = chartutil.ScaffoldChart(chrt2, tmpProjectDir) + + if tt.expectCVEError { + // CVE attack case: should fail with CVE protection message + assert.Error(t, err) + assert.Contains(t, err.Error(), "the Chart.lock file is a symlink to") + } else { + assert.NoError(t, err) + } + }) + } +} From 38d0a2f36fcba80c9a5a999867e28045c47a81bd Mon Sep 17 00:00:00 2001 From: Manish Pillai Date: Tue, 9 Sep 2025 17:00:30 +0530 Subject: [PATCH 2/2] changes --- .../plugins/helm/v1/chartutil/chart_test.go | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/internal/plugins/helm/v1/chartutil/chart_test.go b/internal/plugins/helm/v1/chartutil/chart_test.go index e3b0a03e2f..cfbc2e3e5d 100644 --- a/internal/plugins/helm/v1/chartutil/chart_test.go +++ b/internal/plugins/helm/v1/chartutil/chart_test.go @@ -214,6 +214,17 @@ func runTestCase(t *testing.T, testDir string, tc createChartTestCase) { // This validates the CVE-2025-53547 mitigation implemented in validateForSymlink function. // Reference: https://github.com/helm/helm/security/advisories/GHSA-557j-xg8c-q2mm func TestCVE2025_53547_Protection(t *testing.T) { + // Set up test server like in TestChart + srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz") + if err != nil { + t.Fatalf("Failed to create new temp server: %s", err) + } + defer srv.Stop() + + if err := srv.LinkIndices(); err != nil { + t.Fatalf("Failed to link server indices: %s", err) + } + tests := []struct { name string setupAfterScaffolding func(string) error // Setup function called after initial scaffolding @@ -229,6 +240,21 @@ func TestCVE2025_53547_Protection(t *testing.T) { }, expectCVEError: false, }, + { + name: "safe chart with regular Chart.lock", + setupAfterScaffolding: func(scaffoldedChartPath string) error { + // Ensure a regular (non-symlinked) Chart.lock file exists + chartLockPath := filepath.Join(scaffoldedChartPath, "Chart.lock") + + if _, err := os.Stat(chartLockPath); os.IsNotExist(err) { + return fmt.Errorf("chart.lock doesn't exist at %s: %v", chartLockPath, err) + } else if err != nil { + return err + } + return nil + }, + expectCVEError: false, + }, { name: "CVE-2025-53547: symlinked Chart.lock attack", setupAfterScaffolding: func(scaffoldedChartPath string) error { @@ -250,6 +276,16 @@ func TestCVE2025_53547_Protection(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + // Set up Helm environment like in runTestCase + os.Setenv("XDG_CONFIG_HOME", filepath.Join(srv.Root(), ".config")) + os.Setenv("XDG_CACHE_HOME", filepath.Join(srv.Root(), ".cache")) + os.Setenv("HELM_REPOSITORY_CONFIG", filepath.Join(srv.Root(), "repositories.yaml")) + os.Setenv("HELM_REPOSITORY_CACHE", filepath.Join(srv.Root())) + defer os.Unsetenv("XDG_CONFIG_HOME") + defer os.Unsetenv("XDG_CACHE_HOME") + defer os.Unsetenv("HELM_REPOSITORY_CONFIG") + defer os.Unsetenv("HELM_REPOSITORY_CACHE") + // Create temporary directories tmpProjectDir, err := os.MkdirTemp("", "test-project-cve") assert.NoError(t, err) @@ -259,14 +295,18 @@ func TestCVE2025_53547_Protection(t *testing.T) { assert.NoError(t, err) defer os.RemoveAll(tmpChartDir) - // Step 1: Create chart with dependencies to generate Chart.lock - chartYaml := `apiVersion: v2 + // Step 1: Create chart with dependencies using test server + chartYaml := fmt.Sprintf(`apiVersion: v2 name: test-chart version: 1.0.0 appVersion: 1.0.0 description: Test chart for CVE-2025-53547 testing type: application -` +dependencies: + - name: test-chart + version: "1.2.3" + repository: "%s" +`, srv.URL()) err = os.WriteFile(filepath.Join(tmpChartDir, "Chart.yaml"), []byte(chartYaml), 0644) assert.NoError(t, err)