diff --git a/SPECS/skopeo/CVE-2025-58058.patch b/SPECS/skopeo/CVE-2025-58058.patch new file mode 100644 index 00000000000..6528068d59f --- /dev/null +++ b/SPECS/skopeo/CVE-2025-58058.patch @@ -0,0 +1,461 @@ +From 3000a79cefbd71c7ffcd4854aa6d0992f1d19018 Mon Sep 17 00:00:00 2001 +From: Ulrich Kunitz +Date: Thu, 21 Aug 2025 17:57:47 +0200 +Subject: [PATCH] Address Security Issue GHSA-jc7w-c686-c4v9 + +This commit addresses security issue GHSA-jc7w-c686-c4v9. + +The mitigating measures are described for the Reader type and I added a +TestZeroPrefixIssue function to test the mitigations. + +// # Security concerns +// +// Note that LZMA format doesn't support a magic marker in the header. So +// [NewReader] cannot determine whether it reads the actual header. For instance +// the LZMA stream might have a zero byte in front of the reader, leading to +// larger dictionary sizes and file sizes. The code will detect later that there +// are problems with the stream, but the dictionary has already been allocated +// and this might consume a lot of memory. +// +// Version 0.5.14 introduces built-in mitigations: +// +// - The [ReaderConfig] DictCap field is now interpreted as a limit for the +// dictionary size. +// - The default is 2 Gigabytes (2^31 bytes). +// - Users can check with the [Reader.Header] method what the actual values are in +// their LZMA files and set a smaller limit using [ReaderConfig]. +// - The dictionary size doesn't exceed the larger of the file size and +// the minimum dictionary size. This is another measure to prevent huge +// memory allocations for the dictionary. +// - The code supports stream sizes only up to a pebibyte (1024^5). + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/ulikunitz/xz/commit/88ddf1d0d98d688db65de034f48960b2760d2ae2.patch +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/ulikunitz/xz/commit/88ddf1d0d98d688db65de034f48960b2760d2ae2.patch +--- + vendor/github.com/ulikunitz/xz/TODO.md | 11 +- + vendor/github.com/ulikunitz/xz/lzma/header.go | 55 ++++---- + vendor/github.com/ulikunitz/xz/lzma/reader.go | 123 +++++++++++++++--- + vendor/github.com/ulikunitz/xz/lzma/writer.go | 30 ++--- + 4 files changed, 160 insertions(+), 59 deletions(-) + +diff --git a/vendor/github.com/ulikunitz/xz/TODO.md b/vendor/github.com/ulikunitz/xz/TODO.md +index a3d6f19..e550dc7 100644 +--- a/vendor/github.com/ulikunitz/xz/TODO.md ++++ b/vendor/github.com/ulikunitz/xz/TODO.md +@@ -1,8 +1,13 @@ + # TODO list + +-## Release v0.5.x +- +-1. Support check flag in gxz command. ++## Release v0.5.14 ++ ++* If the DictionarySize is larger than the UncompressedSize set it to ++ UncompressedSize ++* make a Header() (h Header, ok bool) function so the user can implement its own ++ policy ++* Add documentation to Reader to explain the situation ++* Add a TODO for the rewrite version + + ## Release v0.6 + +diff --git a/vendor/github.com/ulikunitz/xz/lzma/header.go b/vendor/github.com/ulikunitz/xz/lzma/header.go +index 1ae7d80..34aa097 100644 +--- a/vendor/github.com/ulikunitz/xz/lzma/header.go ++++ b/vendor/github.com/ulikunitz/xz/lzma/header.go +@@ -60,36 +60,36 @@ const noHeaderSize uint64 = 1<<64 - 1 + // HeaderLen provides the length of the LZMA file header. + const HeaderLen = 13 + +-// header represents the header of an LZMA file. +-type header struct { +- properties Properties +- dictCap int +- // uncompressed size; negative value if no size is given +- size int64 ++// Header represents the Header of an LZMA file. ++type Header struct { ++ Properties Properties ++ DictSize uint32 ++ // uncompressed Size; negative value if no Size is given ++ Size int64 + } + + // marshalBinary marshals the header. +-func (h *header) marshalBinary() (data []byte, err error) { +- if err = h.properties.verify(); err != nil { ++func (h *Header) marshalBinary() (data []byte, err error) { ++ if err = h.Properties.verify(); err != nil { + return nil, err + } +- if !(0 <= h.dictCap && int64(h.dictCap) <= MaxDictCap) { ++ if !(h.DictSize <= MaxDictCap) { + return nil, fmt.Errorf("lzma: DictCap %d out of range", +- h.dictCap) ++ h.DictSize) + } + + data = make([]byte, 13) + + // property byte +- data[0] = h.properties.Code() ++ data[0] = h.Properties.Code() + + // dictionary capacity +- putUint32LE(data[1:5], uint32(h.dictCap)) ++ putUint32LE(data[1:5], uint32(h.DictSize)) + + // uncompressed size + var s uint64 +- if h.size > 0 { +- s = uint64(h.size) ++ if h.Size > 0 { ++ s = uint64(h.Size) + } else { + s = noHeaderSize + } +@@ -99,20 +99,20 @@ func (h *header) marshalBinary() (data []byte, err error) { + } + + // unmarshalBinary unmarshals the header. +-func (h *header) unmarshalBinary(data []byte) error { ++func (h *Header) unmarshalBinary(data []byte) error { + if len(data) != HeaderLen { + return errors.New("lzma.unmarshalBinary: data has wrong length") + } + + // properties + var err error +- if h.properties, err = PropertiesForCode(data[0]); err != nil { ++ if h.Properties, err = PropertiesForCode(data[0]); err != nil { + return err + } + + // dictionary capacity +- h.dictCap = int(uint32LE(data[1:])) +- if h.dictCap < 0 { ++ h.DictSize = uint32LE(data[1:]) ++ if int(h.DictSize) < 0 { + return errors.New( + "LZMA header: dictionary capacity exceeds maximum " + + "integer") +@@ -121,10 +121,10 @@ func (h *header) unmarshalBinary(data []byte) error { + // uncompressed size + s := uint64LE(data[5:]) + if s == noHeaderSize { +- h.size = -1 ++ h.Size = -1 + } else { +- h.size = int64(s) +- if h.size < 0 { ++ h.Size = int64(s) ++ if h.Size < 0 { + return errors.New( + "LZMA header: uncompressed size " + + "out of int64 range") +@@ -134,9 +134,9 @@ func (h *header) unmarshalBinary(data []byte) error { + return nil + } + +-// validDictCap checks whether the dictionary capacity is correct. This ++// validDictSize checks whether the dictionary capacity is correct. This + // is used to weed out wrong file headers. +-func validDictCap(dictcap int) bool { ++func validDictSize(dictcap int) bool { + if int64(dictcap) == MaxDictCap { + return true + } +@@ -155,13 +155,16 @@ func validDictCap(dictcap int) bool { + // dictionary sizes of 2^n or 2^n+2^(n-1) with n >= 10 or 2^32-1. If + // there is an explicit size it must not exceed 256 GiB. The length of + // the data argument must be HeaderLen. ++// ++// This function should be disregarded because there is no guarantee that LZMA ++// files follow the constraints. + func ValidHeader(data []byte) bool { +- var h header ++ var h Header + if err := h.unmarshalBinary(data); err != nil { + return false + } +- if !validDictCap(h.dictCap) { ++ if !validDictSize(int(h.DictSize)) { + return false + } +- return h.size < 0 || h.size <= 1<<38 ++ return h.Size < 0 || h.Size <= 1<<38 + } +diff --git a/vendor/github.com/ulikunitz/xz/lzma/reader.go b/vendor/github.com/ulikunitz/xz/lzma/reader.go +index ae911c3..f619eba 100644 +--- a/vendor/github.com/ulikunitz/xz/lzma/reader.go ++++ b/vendor/github.com/ulikunitz/xz/lzma/reader.go +@@ -6,25 +6,32 @@ + // Reader and Writer support the classic LZMA format. Reader2 and + // Writer2 support the decoding and encoding of LZMA2 streams. + // +-// The package is written completely in Go and doesn't rely on any external ++// The package is written completely in Go and does not rely on any external + // library. + package lzma + + import ( + "errors" ++ "fmt" + "io" + ) + + // ReaderConfig stores the parameters for the reader of the classic LZMA + // format. + type ReaderConfig struct { ++ // Since v0.5.14 this parameter sets an upper limit for a .lzma file's ++ // dictionary size. This helps to mitigate problems with mangled ++ // headers. + DictCap int + } + + // fill converts the zero values of the configuration to the default values. + func (c *ReaderConfig) fill() { + if c.DictCap == 0 { +- c.DictCap = 8 * 1024 * 1024 ++ // set an upper limit of 2 GB for dictionary capacity to address ++ // the zero prefix security issue. ++ c.DictCap = 1 << 31 ++ // original: c.DictCap = 8 * 1024 * 1024 + } + } + +@@ -39,10 +46,33 @@ func (c *ReaderConfig) Verify() error { + } + + // Reader provides a reader for LZMA files or streams. ++// ++// # Security concerns ++// ++// Note that LZMA format doesn't support a magic marker in the header. So ++// [NewReader] cannot determine whether it reads the actual header. For instance ++// the LZMA stream might have a zero byte in front of the reader, leading to ++// larger dictionary sizes and file sizes. The code will detect later that there ++// are problems with the stream, but the dictionary has already been allocated ++// and this might consume a lot of memory. ++// ++// Version 0.5.14 introduces built-in mitigations: ++// ++// - The [ReaderConfig] DictCap field is now interpreted as a limit for the ++// dictionary size. ++// - The default is 2 Gigabytes (2^31 bytes). ++// - Users can check with the [Reader.Header] method what the actual values are in ++// their LZMA files and set a smaller limit using [ReaderConfig]. ++// - The dictionary size doesn't exceed the larger of the file size and ++// the minimum dictionary size. This is another measure to prevent huge ++// memory allocations for the dictionary. ++// - The code supports stream sizes only up to a pebibyte (1024^5). + type Reader struct { +- lzma io.Reader +- h header +- d *decoder ++ lzma io.Reader ++ header Header ++ // headerOrig stores the original header read from the stream. ++ headerOrig Header ++ d *decoder + } + + // NewReader creates a new reader for an LZMA stream using the classic +@@ -51,8 +81,37 @@ func NewReader(lzma io.Reader) (r *Reader, err error) { + return ReaderConfig{}.NewReader(lzma) + } + ++// ErrDictSize reports about an error of the dictionary size. ++type ErrDictSize struct { ++ ConfigDictCap int ++ HeaderDictSize uint32 ++ Message string ++} ++ ++// Error returns the error message. ++func (e *ErrDictSize) Error() string { ++ return e.Message ++} ++ ++func newErrDictSize(messageformat string, ++ configDictCap int, headerDictSize uint32, ++ args ...interface{}) *ErrDictSize { ++ newArgs := make([]interface{}, len(args)+2) ++ newArgs[0] = configDictCap ++ newArgs[1] = headerDictSize ++ copy(newArgs[2:], args) ++ return &ErrDictSize{ ++ ConfigDictCap: configDictCap, ++ HeaderDictSize: headerDictSize, ++ Message: fmt.Sprintf(messageformat, newArgs...), ++ } ++} ++ ++// We support only files not larger than 1 << 50 bytes (a pebibyte, 1024^5). ++const maxStreamSize = 1 << 50 ++ + // NewReader creates a new reader for an LZMA stream in the classic +-// format. The function reads and verifies the the header of the LZMA ++// format. The function reads and verifies the header of the LZMA + // stream. + func (c ReaderConfig) NewReader(lzma io.Reader) (r *Reader, err error) { + if err = c.Verify(); err != nil { +@@ -66,29 +125,63 @@ func (c ReaderConfig) NewReader(lzma io.Reader) (r *Reader, err error) { + return nil, err + } + r = &Reader{lzma: lzma} +- if err = r.h.unmarshalBinary(data); err != nil { ++ if err = r.header.unmarshalBinary(data); err != nil { + return nil, err + } +- if r.h.dictCap < MinDictCap { +- r.h.dictCap = MinDictCap ++ r.headerOrig = r.header ++ dictSize := int64(r.header.DictSize) ++ if int64(c.DictCap) < dictSize { ++ return nil, newErrDictSize( ++ "lzma: header dictionary size %[2]d exceeds configured dictionary capacity %[1]d", ++ c.DictCap, uint32(dictSize), ++ ) ++ } ++ if dictSize < MinDictCap { ++ dictSize = MinDictCap ++ } ++ // original code: disabled this because there is no point in increasing ++ // the dictionary above what is stated in the file. ++ /* ++ if int64(c.DictCap) > int64(dictSize) { ++ dictSize = int64(c.DictCap) ++ } ++ */ ++ size := r.header.Size ++ if size >= 0 && size < dictSize { ++ dictSize = size + } +- dictCap := r.h.dictCap +- if c.DictCap > dictCap { +- dictCap = c.DictCap ++ // Protect against modified or malicious headers. ++ if size > maxStreamSize { ++ return nil, fmt.Errorf( ++ "lzma: stream size %d exceeds a pebibyte (1024^5)", ++ size) + } ++ if dictSize < MinDictCap { ++ dictSize = MinDictCap ++ } ++ ++ r.header.DictSize = uint32(dictSize) + +- state := newState(r.h.properties) +- dict, err := newDecoderDict(dictCap) ++ state := newState(r.header.Properties) ++ dict, err := newDecoderDict(int(dictSize)) + if err != nil { + return nil, err + } +- r.d, err = newDecoder(ByteReader(lzma), state, dict, r.h.size) ++ r.d, err = newDecoder(ByteReader(lzma), state, dict, r.header.Size) + if err != nil { + return nil, err + } + return r, nil + } + ++// Header returns the header as read from the LZMA stream. It is intended to ++// allow the user to understand what parameters are typically provided in the ++// headers of the LZMA files and set the DictCap field in [ReaderConfig] ++// accordingly. ++func (r *Reader) Header() (h Header, ok bool) { ++ return r.headerOrig, r.d != nil ++} ++ + // EOSMarker indicates that an EOS marker has been encountered. + func (r *Reader) EOSMarker() bool { + return r.d.eosMarker +diff --git a/vendor/github.com/ulikunitz/xz/lzma/writer.go b/vendor/github.com/ulikunitz/xz/lzma/writer.go +index e8f8981..dd935c3 100644 +--- a/vendor/github.com/ulikunitz/xz/lzma/writer.go ++++ b/vendor/github.com/ulikunitz/xz/lzma/writer.go +@@ -13,7 +13,7 @@ import ( + // MinDictCap and MaxDictCap provide the range of supported dictionary + // capacities. + const ( +- MinDictCap = 1 << 12 ++ MinDictCap = 1 << 12 + MaxDictCap = 1<<32 - 1 + ) + +@@ -96,21 +96,21 @@ func (c *WriterConfig) Verify() error { + } + + // header returns the header structure for this configuration. +-func (c *WriterConfig) header() header { +- h := header{ +- properties: *c.Properties, +- dictCap: c.DictCap, +- size: -1, ++func (c *WriterConfig) header() Header { ++ h := Header{ ++ Properties: *c.Properties, ++ DictSize: uint32(c.DictCap), ++ Size: -1, + } + if c.SizeInHeader { +- h.size = c.Size ++ h.Size = c.Size + } + return h + } + + // Writer writes an LZMA stream in the classic format. + type Writer struct { +- h header ++ h Header + bw io.ByteWriter + buf *bufio.Writer + e *encoder +@@ -130,12 +130,12 @@ func (c WriterConfig) NewWriter(lzma io.Writer) (w *Writer, err error) { + w.buf = bufio.NewWriter(lzma) + w.bw = w.buf + } +- state := newState(w.h.properties) +- m, err := c.Matcher.new(w.h.dictCap) ++ state := newState(w.h.Properties) ++ m, err := c.Matcher.new(int(w.h.DictSize)) + if err != nil { + return nil, err + } +- dict, err := newEncoderDict(w.h.dictCap, c.BufSize, m) ++ dict, err := newEncoderDict(int(w.h.DictSize), c.BufSize, m) + if err != nil { + return nil, err + } +@@ -171,8 +171,8 @@ func (w *Writer) writeHeader() error { + + // Write puts data into the Writer. + func (w *Writer) Write(p []byte) (n int, err error) { +- if w.h.size >= 0 { +- m := w.h.size ++ if w.h.Size >= 0 { ++ m := w.h.Size + m -= w.e.Compressed() + int64(w.e.dict.Buffered()) + if m < 0 { + m = 0 +@@ -192,9 +192,9 @@ func (w *Writer) Write(p []byte) (n int, err error) { + // Close closes the writer stream. It ensures that all data from the + // buffer will be compressed and the LZMA stream will be finished. + func (w *Writer) Close() error { +- if w.h.size >= 0 { ++ if w.h.Size >= 0 { + n := w.e.Compressed() + int64(w.e.dict.Buffered()) +- if n != w.h.size { ++ if n != w.h.Size { + return errSize + } + } +-- +2.45.4 + diff --git a/SPECS/skopeo/skopeo.spec b/SPECS/skopeo/skopeo.spec index d3a77c7914e..0f0eda71669 100644 --- a/SPECS/skopeo/skopeo.spec +++ b/SPECS/skopeo/skopeo.spec @@ -1,7 +1,7 @@ Summary: Inspect container images and repositories on registries Name: skopeo Version: 1.14.4 -Release: 5%{?dist} +Release: 6%{?dist} License: Apache-2.0 Vendor: Microsoft Corporation Distribution: Azure Linux @@ -13,6 +13,7 @@ Patch1: CVE-2024-6104.patch Patch2: CVE-2023-45288.patch Patch3: CVE-2024-9676.patch Patch4: CVE-2025-27144.patch +Patch5: CVE-2025-58058.patch %global debug_package %{nil} %define our_gopath %{_topdir}/.gopath @@ -51,6 +52,9 @@ make test-unit-local %{_mandir}/man1/%%{name}* %changelog +* Tue Sep 02 2025 Azure Linux Security Servicing Account - 1.14.4-6 +- Patch for CVE-2025-58058 + * Thu Apr 10 2025 Kanishk Bansal - 1.14.4-5 - Remove extraction command from build