From 621ebfbea989498fe4178d364460b3771318d56d Mon Sep 17 00:00:00 2001 From: anushkagupta200615-jpg Date: Mon, 13 Jul 2026 23:51:51 +0530 Subject: [PATCH 1/6] fix(installer): prevent file descriptor exhaustion during OCI plugin extraction Closes #32344 Signed-off-by: anushkagupta200615-jpg --- internal/plugin/installer/oci_installer.go | 32 ++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/internal/plugin/installer/oci_installer.go b/internal/plugin/installer/oci_installer.go index 383ddb914..a67857a71 100644 --- a/internal/plugin/installer/oci_installer.go +++ b/internal/plugin/installer/oci_installer.go @@ -209,6 +209,26 @@ func extractTarGz(r io.Reader, targetDir string) error { return extractTar(gzr, targetDir) } +// extractFile creates a single file from the tar archive +func extractFile(path string, mode int64, src io.Reader) error { + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0o755); err != nil { + return err + } + + outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(mode)) + if err != nil { + return err + } + defer outFile.Close() + + if _, err := io.Copy(outFile, src); err != nil { + return err + } + + return nil +} + // extractTar extracts a tar archive to a directory func extractTar(r io.Reader, targetDir string) error { tarReader := tar.NewReader(r) @@ -233,17 +253,7 @@ func extractTar(r io.Reader, targetDir string) error { return err } case tar.TypeReg: - dir := filepath.Dir(path) - if err := os.MkdirAll(dir, 0o755); err != nil { - return err - } - - outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) - if err != nil { - return err - } - defer outFile.Close() - if _, err := io.Copy(outFile, tarReader); err != nil { + if err := extractFile(path, header.Mode, tarReader); err != nil { return err } case tar.TypeXGlobalHeader, tar.TypeXHeader: From 09a13522d04d84b89cbb869007c28924bcb381a2 Mon Sep 17 00:00:00 2001 From: anushkagupta200615-jpg Date: Tue, 14 Jul 2026 00:19:53 +0530 Subject: [PATCH 2/6] test: add file descriptor exhaustion regression test fix: use correct file flags for oci installer Signed-off-by: anushkagupta200615-jpg --- internal/plugin/installer/oci_installer.go | 2 +- .../installer/oci_installer_unix_test.go | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 internal/plugin/installer/oci_installer_unix_test.go diff --git a/internal/plugin/installer/oci_installer.go b/internal/plugin/installer/oci_installer.go index a67857a71..dcbe9a245 100644 --- a/internal/plugin/installer/oci_installer.go +++ b/internal/plugin/installer/oci_installer.go @@ -216,7 +216,7 @@ func extractFile(path string, mode int64, src io.Reader) error { return err } - outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(mode)) + outFile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(mode)) if err != nil { return err } diff --git a/internal/plugin/installer/oci_installer_unix_test.go b/internal/plugin/installer/oci_installer_unix_test.go new file mode 100644 index 000000000..d17b3cc8a --- /dev/null +++ b/internal/plugin/installer/oci_installer_unix_test.go @@ -0,0 +1,79 @@ +//go:build !windows + +/* +Copyright The Helm Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package installer + +import ( + "archive/tar" + "bytes" + "fmt" + "syscall" + "testing" +) + +func TestExtractTarFileDescriptorLeak(t *testing.T) { + var rLimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + t.Skipf("Skipping test because Getrlimit failed: %v", err) + } + + // Lower the limit to 50 + oldLimit := rLimit + rLimit.Cur = 50 + if rLimit.Max < 50 { + rLimit.Max = 50 + } + + err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + t.Skipf("Skipping test because Setrlimit failed: %v", err) + } + defer func() { + // Restore the original limit + if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &oldLimit); err != nil { + t.Logf("Failed to restore RLIMIT_NOFILE: %v", err) + } + }() + + // Create a dummy tar archive with 100 files (more than the limit of 50) + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + for i := 0; i < 100; i++ { + hdr := &tar.Header{ + Name: fmt.Sprintf("file_%d.txt", i), + Mode: 0600, + Size: 0, + Typeflag: tar.TypeReg, + } + if err := tw.WriteHeader(hdr); err != nil { + t.Fatalf("Failed to write header: %v", err) + } + if _, err := tw.Write([]byte{}); err != nil { + t.Fatalf("Failed to write content: %v", err) + } + } + if err := tw.Close(); err != nil { + t.Fatalf("Failed to close tar writer: %v", err) + } + + // Extract the tar archive + tempDir := t.TempDir() + if err := extractTar(&buf, tempDir); err != nil { + t.Fatalf("extractTar failed, likely due to file descriptor exhaustion: %v", err) + } +} From 0be6095f5e0996a5130fbbf4087280d7c60d6b1b Mon Sep 17 00:00:00 2001 From: anushkagupta200615-jpg Date: Tue, 14 Jul 2026 00:25:42 +0530 Subject: [PATCH 3/6] test: avoid raising hard limit for RLIMIT_NOFILE Signed-off-by: anushkagupta200615-jpg --- internal/plugin/installer/oci_installer_unix_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/plugin/installer/oci_installer_unix_test.go b/internal/plugin/installer/oci_installer_unix_test.go index d17b3cc8a..74614fe63 100644 --- a/internal/plugin/installer/oci_installer_unix_test.go +++ b/internal/plugin/installer/oci_installer_unix_test.go @@ -34,10 +34,10 @@ func TestExtractTarFileDescriptorLeak(t *testing.T) { // Lower the limit to 50 oldLimit := rLimit - rLimit.Cur = 50 if rLimit.Max < 50 { - rLimit.Max = 50 + t.Skipf("Skipping test because Max limit (%d) is too low", rLimit.Max) } + rLimit.Cur = 50 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) if err != nil { From 8490fd28de7e91c9db827becb5f1c50a5df318bb Mon Sep 17 00:00:00 2001 From: anushkagupta200615-jpg Date: Tue, 14 Jul 2026 00:41:42 +0530 Subject: [PATCH 4/6] fix: revert file open flags to O_RDWR to align with existing logic Signed-off-by: anushkagupta200615-jpg --- internal/plugin/installer/oci_installer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/plugin/installer/oci_installer.go b/internal/plugin/installer/oci_installer.go index dcbe9a245..a67857a71 100644 --- a/internal/plugin/installer/oci_installer.go +++ b/internal/plugin/installer/oci_installer.go @@ -216,7 +216,7 @@ func extractFile(path string, mode int64, src io.Reader) error { return err } - outFile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(mode)) + outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(mode)) if err != nil { return err } From 05fd9c46edd7e6ae3f350e47ca43fb1dfec2631f Mon Sep 17 00:00:00 2001 From: anushkagupta200615-jpg Date: Tue, 14 Jul 2026 00:50:57 +0530 Subject: [PATCH 5/6] fix: address review feedback regarding file closing and octal syntax Signed-off-by: anushkagupta200615-jpg --- internal/plugin/installer/oci_installer.go | 8 ++++---- internal/plugin/installer/oci_installer_unix_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/plugin/installer/oci_installer.go b/internal/plugin/installer/oci_installer.go index a67857a71..675fbde8c 100644 --- a/internal/plugin/installer/oci_installer.go +++ b/internal/plugin/installer/oci_installer.go @@ -209,24 +209,24 @@ func extractTarGz(r io.Reader, targetDir string) error { return extractTar(gzr, targetDir) } -// extractFile creates a single file from the tar archive +// extractFile creates a single file from the tar archive. func extractFile(path string, mode int64, src io.Reader) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o755); err != nil { return err } - outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(mode)) + outFile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(mode)) if err != nil { return err } - defer outFile.Close() if _, err := io.Copy(outFile, src); err != nil { + outFile.Close() return err } - return nil + return outFile.Close() } // extractTar extracts a tar archive to a directory diff --git a/internal/plugin/installer/oci_installer_unix_test.go b/internal/plugin/installer/oci_installer_unix_test.go index 74614fe63..7fb9876da 100644 --- a/internal/plugin/installer/oci_installer_unix_test.go +++ b/internal/plugin/installer/oci_installer_unix_test.go @@ -56,7 +56,7 @@ func TestExtractTarFileDescriptorLeak(t *testing.T) { for i := 0; i < 100; i++ { hdr := &tar.Header{ Name: fmt.Sprintf("file_%d.txt", i), - Mode: 0600, + Mode: 0o600, Size: 0, Typeflag: tar.TypeReg, } From 543af2112be4f255861d42b7481a5f30499ee356 Mon Sep 17 00:00:00 2001 From: anushkagupta200615-jpg Date: Tue, 14 Jul 2026 02:06:07 +0530 Subject: [PATCH 6/6] fix: address final review comments on error handling and build tags Signed-off-by: anushkagupta200615-jpg --- internal/plugin/installer/oci_installer.go | 4 +++- internal/plugin/installer/oci_installer_unix_test.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/plugin/installer/oci_installer.go b/internal/plugin/installer/oci_installer.go index 675fbde8c..ea9e16262 100644 --- a/internal/plugin/installer/oci_installer.go +++ b/internal/plugin/installer/oci_installer.go @@ -222,7 +222,9 @@ func extractFile(path string, mode int64, src io.Reader) error { } if _, err := io.Copy(outFile, src); err != nil { - outFile.Close() + if cErr := outFile.Close(); cErr != nil { + return fmt.Errorf("%w (also failed to close: %v)", err, cErr) + } return err } diff --git a/internal/plugin/installer/oci_installer_unix_test.go b/internal/plugin/installer/oci_installer_unix_test.go index 7fb9876da..90cba1631 100644 --- a/internal/plugin/installer/oci_installer_unix_test.go +++ b/internal/plugin/installer/oci_installer_unix_test.go @@ -1,4 +1,4 @@ -//go:build !windows +//go:build unix /* Copyright The Helm Authors.