CVE-2026-39396
Description
OpenBao is an open source identity-based secrets management system. Prior to version 2.5.3, ExtractPluginFromImage() in OpenBao's OCI plugin downloader extracts a plugin binary from a container image by streaming decompressed tar data via io.Copy with no upper bound on the number of bytes written. An attacker who controls or compromises the OCI registry referenced in the victim's configuration can serve a crafted image containing a decompression bomb that decompresses to an arbitrarily large file. The SHA256 integrity check occurs after the full file is written to disk, meaning the hash mismatch is detected only after the damage (disk exhaustion) has already occurred. This allow the attacker to replace legit plugin image with no need to change its signature. Version 2.5.3 contains a patch.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/openbao/openbaoGo | < 0.0.0-20260420180337-2b2a901aa9f7 | 0.0.0-20260420180337-2b2a901aa9f7 |
Affected products
1Patches
1af576af5322cValidate downloaded plugin binary size (#2941) (#2944)
4 files changed · +57 −2
changelog/2941.txt+3 −0 added@@ -0,0 +1,3 @@ +```release-note:security +core/plugins: Validate and restrict downloaded plugin binary size from OCI images; set `plugin_download_max_size` to limit the size (defaults to 512MB). GHSA-r65v-xgwc-g56j / CVE-2026-39396. +```
command/server/config.go+17 −0 modified@@ -87,6 +87,9 @@ type Config struct { PluginAutoRegister bool `hcl:"-"` PluginAutoRegisterRaw interface{} `hcl:"plugin_auto_register"` + PluginDownloadMaxSize int64 `hcl:"-"` + PluginDownloadMaxSizeRaw interface{} `hcl:"plugin_download_max_size"` + EnableIntrospectionEndpoint bool `hcl:"-"` EnableIntrospectionEndpointRaw interface{} `hcl:"introspection_endpoint,alias:EnableIntrospectionEndpoint"` @@ -699,6 +702,12 @@ func (c *Config) Merge(c2 *Config) *Config { result.PluginAutoRegisterRaw = c2.PluginAutoRegisterRaw } + result.PluginDownloadMaxSize = c.PluginDownloadMaxSize + if c2.PluginAutoDownloadRaw != nil { + result.PluginDownloadMaxSize = c2.PluginDownloadMaxSize + result.PluginDownloadMaxSizeRaw = c2.PluginDownloadMaxSizeRaw + } + return result } @@ -1042,6 +1051,14 @@ func ParseConfig(d, source string) (*Config, error) { result.PluginAutoRegister = autoRegister } + if result.PluginDownloadMaxSizeRaw != nil { + maxSize, err := parseutil.ParseInt(result.PluginDownloadMaxSizeRaw) + if err != nil { + return nil, err + } + result.PluginDownloadMaxSize = maxSize + } + // Remove all unused keys from Config that were satisfied by SharedConfig. result.UnusedKeys = configutil.UnusedFieldDifference(result.UnusedKeys, nil, append(result.FoundKeys, sharedConfig.FoundKeys...)) // Assign file info
helper/pluginutil/oci/downloader.go+33 −2 modified@@ -23,10 +23,12 @@ import ( "github.com/hashicorp/go-hclog" "github.com/openbao/openbao/command/server" "github.com/openbao/openbao/helper/osutil" + "github.com/shirou/gopsutil/v4/disk" ) const ( - PluginCacheDir = ".oci-cache" + PluginCacheDir = ".oci-cache" + PluginMaxSizeBytes = 512 * 1024 * 1024 // 512 MB ) // PluginDownloader handles downloading and managing OCI-based plugins @@ -108,6 +110,15 @@ func (d *PluginDownloader) shouldFailOnPluginError() bool { return behavior == server.PluginDownloadFail } +// maxPluginSize returns the maximum allowed plugin binary size +func (d *PluginDownloader) maxPluginSize() int64 { + if d.config.PluginDownloadMaxSize > 0 { + return d.config.PluginDownloadMaxSize + } + + return PluginMaxSizeBytes +} + // IsPluginCacheValid checks if the plugin already exists in the plugin directory // and matches the expected SHA256 hash (fast path) func (d *PluginDownloader) IsPluginCacheValid(config *server.PluginConfig) bool { @@ -290,6 +301,19 @@ func (d *PluginDownloader) ExtractPluginFromImage(img v1.Image, targetPath strin continue } + if header.Size > d.maxPluginSize() { + return fmt.Errorf("plugin binary size of %d MiB exceeds allowed size of %d MiB", header.Size/1024/1024, d.maxPluginSize()/1024/1024) + } + + diskUsage, err := disk.Usage(filepath.Dir(targetPath)) + if err != nil { + return fmt.Errorf("failed to get disk usage: %w", err) + } + + if diskUsage.Free < uint64(header.Size) { + return fmt.Errorf("not enough space left on disk to download plugin") + } + logger.Info("found plugin binary in OCI image", "entry", header.Name, "size", header.Size) // Create the output file @@ -298,9 +322,16 @@ func (d *PluginDownloader) ExtractPluginFromImage(img v1.Image, targetPath strin return fmt.Errorf("failed to create output file: %w", err) } - if _, copyErr := io.Copy(outFile, tarReader); copyErr != nil { + // wrap tarReader in an io.LimitReader to protect against malicious Tar Headers that report a wrong file size + limitReader := io.LimitReader(tarReader, header.Size+1) + + n, copyErr := io.Copy(outFile, limitReader) + if copyErr != nil { err = fmt.Errorf("failed to extract binary data: %w", copyErr) } + if n != header.Size { + err = fmt.Errorf("file size was different than reported in tar header") + } if closeErr := outFile.Close(); closeErr != nil { err = errors.Join(err, fmt.Errorf("failed to close %s: %w", targetPath, closeErr))
website/content/docs/configuration/plugins.mdx+4 −0 modified@@ -163,6 +163,10 @@ plugin_auto_register = true - `true` - server startup and `SIGHUP` triggers registering of plugins; can still be manually registered with `bao plugin register`. - `false` - only `bao plugin register` will manually register plugins. +## `plugin_download_max_size` + +A number defining the maximum allowed plugin binary size in bytes when extracted from an OCI image. Defaults to `536870912` (512 MiB) if not set. + ## Authentication When downloading plugin images from a private registry, OpenBao will use
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/openbao/openbao/security/advisories/GHSA-r65v-xgwc-g56jnvdExploitVendor AdvisoryWEB
- github.com/advisories/GHSA-r65v-xgwc-g56jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-39396ghsaADVISORY
- github.com/openbao/openbao/commit/af576af5322c6552a017ad10fd76aa4f40fd021eghsaWEB
- github.com/openbao/openbao/pull/2941ghsaWEB
- github.com/openbao/openbao/releases/tag/v2.5.3ghsaWEB
News mentions
0No linked articles in our index yet.