diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 95be0453fa..53d94cea3a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,12 @@ # Release Notes +## 5.6.1 +### Bugfixes +- Fixed a bug where network creation and removal events were displayed incorrectly when the `journald` events driver was in use. +- Fixed a bug where the `--security-opt seccomp=unconfined` option was broken on Windows ([#26855](https://github.com/containers/podman/issues/26855)). +- Fixed a bug where containers created with a name longer than 64 characters, no explicit hostname, the the `container_name_as_hostname` option in `containers.conf` set to `true` would fail to start. +- Fixed a bug where Podman would fail to start containers when runc 1.3.0 or later was used as the OCI runtime ([#26938](https://github.com/containers/podman/issues/26938)). + ## 5.6.0 ### Features - A new set of commands for managing Quadlets has been added as `podman quadlet install` (install a new Quadlet for the current user), `podman quadlet list` (list installed Quadlets), `podman quadlet print` (print the contents of a Quadlet file), and `podman quadlet rm` (remove a Quadlet). These commands are presently not available with the remote Podman client - we expect support for this to arrive in a future release. diff --git a/go.mod b/go.mod index 982df4d9f7..5bc4d7f037 100644 --- a/go.mod +++ b/go.mod @@ -170,7 +170,7 @@ require ( github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect - github.com/ulikunitz/xz v0.5.12 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect github.com/vbatts/tar-split v0.12.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect diff --git a/go.sum b/go.sum index 0b4fe046b6..acef697a90 100644 --- a/go.sum +++ b/go.sum @@ -434,8 +434,8 @@ github.com/u-root/u-root v0.12.1-0.20240114161452-ab3534910ced h1:G0F7Hmwph1Ojoz github.com/u-root/u-root v0.12.1-0.20240114161452-ab3534910ced/go.mod h1:jtkuv6BVn5jo/WAHgQ1k9XfzHEe1hZmq9yDUvbgL+Iw= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM= diff --git a/libpod/container.go b/libpod/container.go index a6a509ae6c..3fba6c8819 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -741,14 +741,14 @@ func (c *Container) hostname(network bool) string { // containers.conf, use a sanitized version of the container's name // as the hostname. Since the container name must already match // the set '[a-zA-Z0-9][a-zA-Z0-9_.-]*', we can just remove any - // underscores and limit it to 253 characters to make it a valid + // underscores and limit it to 64 characters to make it a valid // hostname. if c.runtime.config.Containers.ContainerNameAsHostName { sanitizedHostname := strings.ReplaceAll(c.Name(), "_", "") - if len(sanitizedHostname) <= 253 { + if len(sanitizedHostname) <= 64 { return sanitizedHostname } - return sanitizedHostname[:253] + return sanitizedHostname[:64] } // Otherwise use the container's short ID as the hostname. diff --git a/libpod/container_internal_common.go b/libpod/container_internal_common.go index efb6074d72..86b45a92b3 100644 --- a/libpod/container_internal_common.go +++ b/libpod/container_internal_common.go @@ -420,6 +420,8 @@ func (c *Container) generateSpec(ctx context.Context) (s *spec.Spec, cleanupFunc // Podman decided for --no-dereference as many // bin-utils tools (e..g, touch, chown, cp) do. options = append(options, "copy-symlink") + case "copy", "nocopy": + // no real OCI runtime bind mount options, these should already be handled by the named volume mount above default: options = append(options, o) } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 941d3e048c..5651c4669d 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -504,6 +504,15 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai _, err := r.state.Volume(vol.Name) if err == nil { // The volume exists, we're good + // Make sure to drop all volume-opt options as they only apply to + // the volume create which we don't do again. + var volOpts []string + for _, opts := range vol.Options { + if !strings.HasPrefix(opts, "volume-opt") { + volOpts = append(volOpts, opts) + } + } + vol.Options = volOpts continue } else if !errors.Is(err, define.ErrNoSuchVolume) { return nil, fmt.Errorf("retrieving named volume %s for new container: %w", vol.Name, err) @@ -530,6 +539,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai if len(vol.Options) > 0 { isDriverOpts := false driverOpts := make(map[string]string) + var volOpts []string for _, opts := range vol.Options { if strings.HasPrefix(opts, "volume-opt") { isDriverOpts = true @@ -538,8 +548,11 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai return nil, err } driverOpts[driverOptKey] = driverOptValue + } else { + volOpts = append(volOpts, opts) } } + vol.Options = volOpts if isDriverOpts { parsedOptions := []VolumeCreateOption{WithVolumeOptions(driverOpts)} volOptions = append(volOptions, parsedOptions...) diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index fac038e096..98a4cfe669 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -788,7 +788,7 @@ var _ = Describe("Verify podman containers.conf usage", func() { }) startContainer := func(params ...string) string { - args := []string{"create"} + args := []string{"run", "-d"} for _, param := range params { if param == "--name" { args = append(args, "--replace") @@ -888,7 +888,7 @@ var _ = Describe("Verify podman containers.conf usage", func() { name = getContainerConfig(containerID, "{{ .Name }}") // Double check that name actually got set correctly Expect(name).To(Equal(longHostname)) - // Hostname should be the container name truncated to 253 characters - Expect(hostname).To(Equal(name[:253])) + // Hostname should be the container name truncated to 64 characters + Expect(hostname).To(Equal(name[:64])) }) }) diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index b0020e1c67..75f3d874ed 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -3,17 +3,20 @@ package integration import ( + "encoding/json" "fmt" "os" "os/exec" "os/user" "path/filepath" + "strconv" "strings" . "github.com/containers/podman/v5/test/utils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gexec" + "github.com/opencontainers/runtime-spec/specs-go" ) // in-container mount point: using a path that is definitely not present @@ -447,9 +450,27 @@ var _ = Describe("Podman run with volumes", func() { Expect(separateVolumeSession).Should(ExitCleanly()) Expect(separateVolumeSession.OutputToString()).To(Equal(baselineOutput)) - copySession := podmanTest.Podman([]string{"run", "--rm", "-v", "testvol3:/etc/apk:copy", ALPINE, "stat", "-c", "%h", "/etc/apk/arch"}) - copySession.WaitWithDefaultTimeout() - Expect(copySession).Should(ExitCleanly()) + podmanTest.PodmanExitCleanly("run", "--name", "testctr", "-v", "testvol3:/etc/apk:copy", ALPINE, "stat", "-c", "%h", "/etc/apk/arch") + + inspect := podmanTest.PodmanExitCleanly("container", "inspect", "testctr", "--format", "{{.OCIConfigPath}}") + + // Make extra check that the OCI config does not contain the copy opt, runc 1.3.0 fails on that while crun does not. + // We only test crun upstream so make sure the spec is sane: https://github.com/containers/podman/issues/26938 + f, err := os.Open(inspect.OutputToString()) + Expect(err).ToNot(HaveOccurred()) + defer f.Close() + var spec specs.Spec + err = json.NewDecoder(f).Decode(&spec) + Expect(err).ToNot(HaveOccurred()) + + found := false + for _, m := range spec.Mounts { + if m.Destination == "/etc/apk" { + found = true + Expect(m.Options).To(Equal([]string{"rprivate", "nosuid", "nodev", "rbind"})) + } + } + Expect(found).To(BeTrue(), "OCI spec contains /etc/apk mount") noCopySession := podmanTest.Podman([]string{"run", "--rm", "-v", "testvol4:/etc/apk:nocopy", ALPINE, "stat", "-c", "%h", "/etc/apk/arch"}) noCopySession.WaitWithDefaultTimeout() @@ -859,14 +880,17 @@ VOLUME /test/`, ALPINE) It("podman run with --mount and named volume with driver-opts", func() { // anonymous volume mount with driver opts vol := "type=volume,source=test_vol,dst=/test,volume-opt=type=tmpfs,volume-opt=device=tmpfs,volume-opt=o=nodev" - session := podmanTest.Podman([]string{"run", "--rm", "--mount", vol, ALPINE, "echo", "hello"}) - session.WaitWithDefaultTimeout() - Expect(session).Should(ExitCleanly()) + // Loop twice to cover both the initial code path that creates the volume and the ones which reuses it. + for i := range 2 { + name := "testctr" + strconv.Itoa(i) + podmanTest.PodmanExitCleanly("run", "--name", name, "--mount", vol, ALPINE, "echo", "hello") - inspectVol := podmanTest.Podman([]string{"volume", "inspect", "test_vol"}) - inspectVol.WaitWithDefaultTimeout() - Expect(inspectVol).Should(ExitCleanly()) - Expect(inspectVol.OutputToString()).To(ContainSubstring("nodev")) + inspectVol := podmanTest.PodmanExitCleanly("volume", "inspect", "test_vol") + Expect(inspectVol.OutputToString()).To(ContainSubstring("nodev")) + + inspect := podmanTest.PodmanExitCleanly("container", "inspect", name, "--format", "{{range .Mounts}}{{.Options}}{{end}}") + Expect(inspect.OutputToString()).To(ContainSubstring("[nosuid nodev rbind]")) + } }) It("volume permissions after run", func() { diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index ffcab5a1bf..4acfd0a443 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -338,7 +338,7 @@ EOF # send a random string to the container. This will cause the container # to output the string to its logs, then exit. teststring=$(random_string 30) - echo "$teststring" | nc 127.0.0.1 $port_out + echo "$teststring" > /dev/tcp/127.0.0.1/$port_out # Confirm that the container log output is the string we sent it. run_podman wait $cid diff --git a/test/system/252-quadlet.bats b/test/system/252-quadlet.bats index 0e2c15f8cb..3b2bbb5be7 100644 --- a/test/system/252-quadlet.bats +++ b/test/system/252-quadlet.bats @@ -1188,8 +1188,8 @@ spec: EOF # Bind the port to force a an error when starting the pod - timeout --foreground -v --kill=10 10 ncat -l 127.0.0.1 $port & - nc_pid=$! + timeout --foreground -v --kill=10 10 socat TCP-LISTEN:$port,bind=127.0.0.1,fork - & + socat_pid=$! # Create the Quadlet file local quadlet_file=$PODMAN_TMPDIR/start_err_$(safename).kube @@ -1214,7 +1214,7 @@ EOF run -0 journalctl -eu $QUADLET_SERVICE_NAME assert "$output" =~ "$port: bind: address already in use" "journal contains the real podman start error" - kill "$nc_pid" + kill "$socat_pid" } # https://github.com/containers/podman/issues/25786 diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index fd07c16939..27887b619b 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -153,7 +153,7 @@ load helpers.network # emit random string, and check it teststring=$(random_string 30) - echo "$teststring" | nc 127.0.0.1 $myport + echo "$teststring" > /dev/tcp/127.0.0.1/$myport run_podman logs $cid # Sigh. We can't check line-by-line, because 'nc' output order is @@ -296,7 +296,7 @@ load helpers.network # emit random string, and check it teststring=$(random_string 30) - echo "$teststring" | nc 127.0.0.1 $myport + echo "$teststring" > /dev/tcp/127.0.0.1/$myport run_podman logs $cid # Sigh. We can't check line-by-line, because 'nc' output order is @@ -806,26 +806,26 @@ nameserver 8.8.8.8" "nameserver order is correct" cid="$output" # make sure binding the same port fails - run timeout 5 ncat -l 127.0.0.1 $port - assert "$status" -eq 2 "ncat unexpected exit code" - assert "$output" =~ "127.0.0.1:$port: Address already in use" "ncat error message" + run timeout 5 socat TCP-LISTEN:$port,bind=127.0.0.1,fork - + assert "$status" -eq 1 "socat unexpected exit code" + assert "$output" =~ ".* 127.0.0.1:$port.* Address already in use" "socat error message" for port in $(seq $port $end_port); do run_podman exec -d $cid nc -l -p $port -e /bin/cat - # we have to rety ncat as it can flake as we exec in the background so nc -l + # we have to retry socat as it can flake as we exec in the background so nc -l # might not have bound the port yet, retry seems simpler than checking if the # port is bound in the container, https://github.com/containers/podman/issues/21561. retries=5 while [[ $retries -gt 0 ]]; do - run ncat 127.0.0.1 $port <<<$random + run socat - TCP:127.0.0.1:$port <<<$random if [[ $status -eq 0 ]]; then break fi sleep 0.5 retries=$((retries -1)) done - is "$output" "$random" "ncat got data back (netmode=$netmode port=$port)" + is "$output" "$random" "socat got data back (netmode=$netmode port=$port)" done run_podman rm -f -t0 $cid diff --git a/test/system/README.md b/test/system/README.md index fdd873a655..2e9d2f0ce6 100644 --- a/test/system/README.md +++ b/test/system/README.md @@ -86,7 +86,6 @@ Requirements - bats - jq - skopeo -- nmap-ncat - httpd-tools - openssl - socat diff --git a/test/system/helpers.network.bash b/test/system/helpers.network.bash index 0ffcabc576..8d0b419b36 100644 --- a/test/system/helpers.network.bash +++ b/test/system/helpers.network.bash @@ -423,7 +423,7 @@ function wait_for_port() { function tcp_port_probe() { local address="${2:-0.0.0.0}" - : | nc "${address}" "${1}" + (exec echo -n >/dev/tcp/"$address/$1") >/dev/null 2>&1 } ### Pasta Helpers ############################################################## diff --git a/vendor/github.com/ulikunitz/xz/TODO.md b/vendor/github.com/ulikunitz/xz/TODO.md index c466ffeda5..8f9650c13d 100644 --- a/vendor/github.com/ulikunitz/xz/TODO.md +++ b/vendor/github.com/ulikunitz/xz/TODO.md @@ -1,9 +1,5 @@ # TODO list -## Release v0.5.x - -1. Support check flag in gxz command. - ## Release v0.6 1. Review encoder and check for lzma improvements under xz. @@ -86,6 +82,19 @@ ## Log +## 2025-08-28 + +Release v0.5.14 addresses the security vulnerability CVE-2025-58058. If you put +bytes in from of a LZMA stream, the header might not be read correctly and +memory for the dictionary buffer allocated. I have implemented mitigations for +the problem. + +### 2025-08-20 + +Release v0.5.13 addressed issue #61 regarding handling of multiple WriteClosers +together. So I added a new package xio with a WriteCloserStack to address the +issue. + ### 2024-04-03 Release v0.5.12 updates README.md and SECURITY.md to address the supply chain diff --git a/vendor/github.com/ulikunitz/xz/lzma/header.go b/vendor/github.com/ulikunitz/xz/lzma/header.go index 1ae7d80cab..34aa097e15 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 ae911c3893..eef6bea76b 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 GiB-1 for dictionary capacity + // to address the zero prefix security issue. + c.DictCap = (1 << 31) - 1 + // 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 minus 1 byte (2^31-1 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), + ) } - dictCap := r.h.dictCap - if c.DictCap > dictCap { - dictCap = c.DictCap + 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 + } + // 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 } - state := newState(r.h.properties) - dict, err := newDecoderDict(dictCap) + r.header.DictSize = uint32(dictSize) + + 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 e8f89811d3..f73bb73f28 100644 --- a/vendor/github.com/ulikunitz/xz/lzma/writer.go +++ b/vendor/github.com/ulikunitz/xz/lzma/writer.go @@ -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 } } diff --git a/vendor/modules.txt b/vendor/modules.txt index d897647d3a..524b14bc52 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -899,7 +899,7 @@ github.com/tklauser/numcpus # github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 ## explicit; go 1.21 github.com/u-root/uio/ulog -# github.com/ulikunitz/xz v0.5.12 +# github.com/ulikunitz/xz v0.5.15 ## explicit; go 1.12 github.com/ulikunitz/xz github.com/ulikunitz/xz/internal/hash