Merge branch 'staging-23.11' into staging-next-23.11

This commit is contained in:
Vladimír Čunát
2024-04-04 06:57:20 +02:00
33 changed files with 3068 additions and 70 deletions

View File

@@ -86,9 +86,9 @@ One advantage is that when `pkgs.zlib` is updated, it will automatically update
postPatch = pkgs.lib.optionalString pkgs.stdenv.isDarwin ''
substituteInPlace configure \
--replace '/usr/bin/libtool' 'ar' \
--replace 'AR="libtool"' 'AR="ar"' \
--replace 'ARFLAGS="-o"' 'ARFLAGS="-r"'
--replace-fail '/usr/bin/libtool' 'ar' \
--replace-fail 'AR="libtool"' 'AR="ar"' \
--replace-fail 'ARFLAGS="-o"' 'ARFLAGS="-r"'
'';
})
```

View File

@@ -700,7 +700,7 @@ with import <nixpkgs> {};
hello = attrs: lib.optionalAttrs (lib.versionAtLeast attrs.version "1.0") {
postPatch = ''
substituteInPlace lib/zoneinfo.rs \
--replace "/usr/share/zoneinfo" "${tzdata}/share/zoneinfo"
--replace-fail "/usr/share/zoneinfo" "${tzdata}/share/zoneinfo"
'';
};
};

View File

@@ -54,7 +54,7 @@ Some common issues when packaging software for Darwin:
# ...
prePatch = ''
substituteInPlace Makefile \
--replace '/usr/bin/xcrun clang' clang
--replace-fail '/usr/bin/xcrun clang' clang
'';
}
```

View File

@@ -230,9 +230,9 @@ stdenv.mkDerivation rec {
postInstall = ''
substituteInPlace $out/bin/solo5-virtio-mkimage \
--replace "/usr/lib/syslinux" "${syslinux}/share/syslinux" \
--replace "/usr/share/syslinux" "${syslinux}/share/syslinux" \
--replace "cp " "cp --no-preserve=mode "
--replace-fail "/usr/lib/syslinux" "${syslinux}/share/syslinux" \
--replace-fail "/usr/share/syslinux" "${syslinux}/share/syslinux" \
--replace-fail "cp " "cp --no-preserve=mode "
wrapProgram $out/bin/solo5-virtio-mkimage \
--prefix PATH : ${lib.makeBinPath [ dosfstools mtools parted syslinux ]}
@@ -1217,9 +1217,20 @@ postInstall = ''
Performs string substitution on the contents of \<infile\>, writing the result to \<outfile\>. The substitutions in \<subs\> are of the following form:
#### `--replace` \<s1\> \<s2\> {#fun-substitute-replace}
#### `--replace-fail` \<s1\> \<s2\> {#fun-substitute-replace-fail}
Replace every occurrence of the string \<s1\> by \<s2\>.
Will error if no change is made.
#### `--replace-warn` \<s1\> \<s2\> {#fun-substitute-replace-warn}
Replace every occurrence of the string \<s1\> by \<s2\>.
Will print a warning if no change is made.
#### `--replace-quiet` \<s1\> \<s2\> {#fun-substitute-replace-quiet}
Replace every occurrence of the string \<s1\> by \<s2\>.
Will do nothing if no change can be made.
#### `--subst-var` \<varName\> {#fun-substitute-subst-var}
@@ -1233,8 +1244,8 @@ Example:
```shell
substitute ./foo.in ./foo.out \
--replace /usr/bin/bar $bar/bin/bar \
--replace "a string containing spaces" "some other text" \
--replace-fail /usr/bin/bar $bar/bin/bar \
--replace-fail "a string containing spaces" "some other text" \
--subst-var someVar
```

View File

@@ -73,6 +73,27 @@ in
variant = "mainline";
rev = "28.2";
hash = "sha256-4oSLcUDR0MOEt53QOiZSVU8kPJ67GwugmBxdX3F15Ag=";
patches = fetchpatch: [
# CVE-2022-45939
(fetchpatch {
url = "https://git.savannah.gnu.org/cgit/emacs.git/patch/?id=d48bb4874bc6cd3e69c7a15fc3c91cc141025c51";
hash = "sha256-TiBQkexn/eb6+IqJNDqR/Rn7S7LVdHmL/21A5tGsyJs=";
})
# https://lists.gnu.org/archive/html/emacs-devel/2024-03/msg00611.html
(fetchpatch {
url = "https://gitweb.gentoo.org/proj/emacs-patches.git/plain/emacs/28.2/10_all_org-macro-eval.patch?id=af40e12cb742510e5d40a06ffc6dfca97e340dd6";
hash = "sha256-OdGt4e9JGjWJPkfJhbYsmQQc6jart4BH5aIKPIbWKFs=";
})
(fetchpatch {
url = "https://gitweb.gentoo.org/proj/emacs-patches.git/plain/emacs/28.2/11_all_untrusted-content.patch?id=af40e12cb742510e5d40a06ffc6dfca97e340dd6";
hash = "sha256-wa2bsnCt5yFx0+RAFZGBPI+OoKkbrfkkMer/KBEc/wA=";
})
(fetchpatch {
url = "https://gitweb.gentoo.org/proj/emacs-patches.git/plain/emacs/28.2/12_all_org-remote-unsafe.patch?id=af40e12cb742510e5d40a06ffc6dfca97e340dd6";
hash = "sha256-b6WU1o3PfDV/6BTPfPNUFny6oERJCNsDrvflxX3Yvek=";
})
];
});
emacs29 = import ./make-emacs.nix (mkArgs {

View File

@@ -46,11 +46,11 @@ let
in
stdenv.mkDerivation (finalAttrs: {
pname = "go";
version = "1.21.8";
version = "1.21.9";
src = fetchurl {
url = "https://go.dev/dl/go${finalAttrs.version}.src.tar.gz";
hash = "sha256-3IBs91qH4UFLW0w9y53T6cyY9M/M7EK3r2F9WmWKPEM=";
hash = "sha256-WPDFztRaABK84v96nfA+Eoq8yIGOur5QJ7uSuv4g5CE=";
};
strictDeps = true;

View File

@@ -20,10 +20,10 @@
sourceVersion = {
major = "3";
minor = "10";
patch = "13";
patch = "14";
suffix = "";
};
hash = "sha256-XIiEhmhkDT4VKzW0U27xwjsspL0slX7x7LsFP1cd0/Y=";
hash = "sha256-nFBIH6qMKDIym6D8iGjQpgamgPxPYOxI0mzo4HZ1H9o=";
};
python311 = {

View File

@@ -23,13 +23,13 @@
stdenv.mkDerivation rec {
pname = "at-spi2-core";
version = "2.50.0";
version = "2.50.2";
outputs = [ "out" "dev" ];
src = fetchurl {
url = "mirror://gnome/sources/${pname}/${lib.versions.majorMinor version}/${pname}-${version}.tar.xz";
sha256 = "6fWoyCNcndljshcd6RIDARKcZ33ekzlV4d9hi5ScStw=";
hash = "sha256-W4GxRhpi3Y++0aJ2+p71txEvmuX/huHjKtlkS2VP94w=";
};
nativeBuildInputs = [

View File

@@ -16,13 +16,13 @@
stdenv.mkDerivation rec {
pname = "libopenmpt";
version = "0.7.3";
version = "0.7.6";
outputs = [ "out" "dev" "bin" ];
src = fetchurl {
url = "https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-${version}+release.autotools.tar.gz";
hash = "sha256-LPg2m3kWsJJk8/FLn7bO81pum+4DKN7E9J2YIRzP1yI=";
hash = "sha256-Fi1yowa7LhFMJPolJn0NCgrBbzn9laXA38daZm7l5PU=";
};
enableParallelBuilding = true;

View File

@@ -51,6 +51,74 @@ stdenv.mkDerivation rec {
url = "https://github.com/lathiat/avahi/commit/a2696da2f2c50ac43b6c4903f72290d5c3fa9f6f.patch";
sha256 = "sha256-BEYFGCnQngp+OpiKIY/oaKygX7isAnxJpUPCUvg+efc=";
})
# CVE-2023-38470
# https://github.com/lathiat/avahi/pull/457 merged Sep 19
(fetchpatch {
name = "CVE-2023-38470.patch";
url = "https://github.com/lathiat/avahi/commit/94cb6489114636940ac683515417990b55b5d66c.patch";
sha256 = "sha256-Fanh9bvz+uknr5pAmltqijuUAZIG39JR2Lyq5zGKJ58=";
})
# https://github.com/avahi/avahi/pull/480 merged Sept 19
(fetchpatch {
name = "bail-out-unless-escaped-labels-fit.patch";
url = "https://github.com/avahi/avahi/commit/20dec84b2480821704258bc908e7b2bd2e883b24.patch";
sha256 = "sha256-p/dOuQ/GInIcUwuFhQR3mGc5YBL5J8ho+1gvzcqEN0c=";
})
# CVE-2023-38473
# https://github.com/lathiat/avahi/pull/486 merged Oct 18
(fetchpatch {
name = "CVE-2023-38473.patch";
url = "https://github.com/lathiat/avahi/commit/b448c9f771bada14ae8de175695a9729f8646797.patch";
sha256 = "sha256-/ZVhsBkf70vjDWWG5KXxvGXIpLOZUXdRkn3413iSlnI=";
})
# CVE-2023-38472
# https://github.com/lathiat/avahi/pull/490 merged Oct 19
(fetchpatch {
name = "CVE-2023-38472.patch";
url = "https://github.com/lathiat/avahi/commit/b024ae5749f4aeba03478e6391687c3c9c8dee40.patch";
sha256 = "sha256-FjR8fmhevgdxR9JQ5iBLFXK0ILp2OZQ8Oo9IKjefCqk=";
})
# CVE-2023-38471
# https://github.com/lathiat/avahi/pull/494 merged Oct 24
(fetchpatch {
name = "CVE-2023-38471.patch";
url = "https://github.com/lathiat/avahi/commit/894f085f402e023a98cbb6f5a3d117bd88d93b09.patch";
sha256 = "sha256-4dG+5ZHDa+A4/CszYS8uXWlpmA89m7/jhbZ7rheMs7U=";
})
# https://github.com/lathiat/avahi/pull/499 merged Oct 25
(fetchpatch {
name = "CVE-2023-38471-2.patch";
url = "https://github.com/avahi/avahi/commit/b675f70739f404342f7f78635d6e2dcd85a13460.patch";
sha256 = "sha256-uDtMPWuz1lsu7n0Co/Gpyh369miQ6GWGyC0UPQB/yI8=";
})
# CVE-2023-38469
# https://github.com/lathiat/avahi/pull/500 merged Oct 25
(fetchpatch {
name = "CVE-2023-38469.patch";
url = "https://github.com/avahi/avahi/commit/61b9874ff91dd20a12483db07df29fe7f35db77f.patch";
sha256 = "sha256-qR7scfQqhRGxg2n4HQsxVxCLkXbwZi+PlYxrOSEPsL0=";
excludes = [ ".github/workflows/smoke-tests.sh" ];
})
# https://github.com/avahi/avahi/pull/515 merged Nov 3
(fetchpatch {
name = "fix-compare-rrs-with-zero-length-rdata.patch";
url = "https://github.com/avahi/avahi/commit/177d75e8c43be45a8383d794ce4084dd5d600a9e.patch";
sha256 = "sha256-uwIyruAWgiWt0yakRrvMdYjjhEhUk5cIGKt6twyXbHw=";
})
# https://github.com/avahi/avahi/pull/519 merged Nov 8
(fetchpatch {
name = "reject-non-utf-8-service-names.patch";
url = "https://github.com/avahi/avahi/commit/2b6d3e99579e3b6e9619708fad8ad8e07ada8218.patch";
sha256 = "sha256-lwSA3eEQgH0g51r0i9/HJMJPRXrhQnTIEDxcYqUuLdI=";
excludes = [ "fuzz/fuzz-domain.c" ];
})
# https://github.com/avahi/avahi/pull/523 merged Nov 12
(fetchpatch {
name = "core-no-longer-supply-bogus-services-to-callbacks.patch";
url = "https://github.com/avahi/avahi/commit/93b14365c1c1e04efd1a890e8caa01a2a514bfd8.patch";
sha256 = "sha256-VBm8vsBZkTbbWAK8FI71SL89lZuYd1yFNoB5o+FvlEU=";
excludes = [ ".github/workflows/smoke-tests.sh" "fuzz/fuzz-packet.c" ];
})
];
depsBuildBuild = [

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
From c645f5996f3a8cff10606182a8031d3c3ade6ea3 Mon Sep 17 00:00:00 2001
From: Sebastian Pipping <sebastian@pipping.org>
Date: Sun, 3 Mar 2024 02:19:58 +0100
Subject: [PATCH 1/2] lib/xmlparse.c: Reject directly recursive parameter
entities
(cherry picked from commit a4c86a395ee447c59175c762af3d17f7107b2261)
---
lib/xmlparse.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/xmlparse.c b/lib/xmlparse.c
index b6c2eca9..2858946d 100644
--- a/lib/xmlparse.c
+++ b/lib/xmlparse.c
@@ -6139,7 +6139,7 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
dtd->keepProcessing = dtd->standalone;
goto endEntityValue;
}
- if (entity->open) {
+ if (entity->open || (entity == parser->m_declEntity)) {
if (enc == parser->m_encoding)
parser->m_eventPtr = entityTextPtr;
result = XML_ERROR_RECURSIVE_ENTITY_REF;
--
2.44.0
From b2170839eab38624df1d9842d738e4a2b6a08a2b Mon Sep 17 00:00:00 2001
From: Sebastian Pipping <sebastian@pipping.org>
Date: Mon, 4 Mar 2024 23:49:06 +0100
Subject: [PATCH 2/2] lib/xmlparse.c: Detect billion laughs attack with
isolated external parser
When parsing DTD content with code like ..
XML_Parser parser = XML_ParserCreate(NULL);
XML_Parser ext_parser = XML_ExternalEntityParserCreate(parser, NULL, NULL);
enum XML_Status status = XML_Parse(ext_parser, doc, (int)strlen(doc), XML_TRUE);
.. there are 0 bytes accounted as direct input and all input from `doc` accounted
as indirect input. Now function accountingGetCurrentAmplification cannot calculate
the current amplification ratio as "(direct + indirect) / direct", and it did refuse
to divide by 0 as one would expect, but it returned 1.0 for this case to indicate
no amplification over direct input. As a result, billion laughs attacks from
DTD-only input were not detected with this isolated way of using an external parser.
The new approach is to assume direct input of length not 0 but 22 -- derived from
ghost input "<!ENTITY a SYSTEM 'b'>", the shortest possible way to include an external
DTD --, and do the usual "(direct + indirect) / direct" math with "direct := 22".
GitHub issue #839 has more details on this issue and its origin in ClusterFuzz
finding 66812.
(cherry picked from commit 1d50b80cf31de87750103656f6eb693746854aa8)
---
lib/xmlparse.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/lib/xmlparse.c b/lib/xmlparse.c
index 2858946d..9458b092 100644
--- a/lib/xmlparse.c
+++ b/lib/xmlparse.c
@@ -7655,6 +7655,8 @@ copyString(const XML_Char *s, const XML_Memory_Handling_Suite *memsuite) {
static float
accountingGetCurrentAmplification(XML_Parser rootParser) {
+ // 1.........1.........12 => 22
+ const size_t lenOfShortestInclude = sizeof("<!ENTITY a SYSTEM 'b'>") - 1;
const XmlBigCount countBytesOutput
= rootParser->m_accounting.countBytesDirect
+ rootParser->m_accounting.countBytesIndirect;
@@ -7662,7 +7664,9 @@ accountingGetCurrentAmplification(XML_Parser rootParser) {
= rootParser->m_accounting.countBytesDirect
? (countBytesOutput
/ (float)(rootParser->m_accounting.countBytesDirect))
- : 1.0f;
+ : ((lenOfShortestInclude
+ + rootParser->m_accounting.countBytesIndirect)
+ / (float)lenOfShortestInclude);
assert(! rootParser->m_parentParser);
return amplificationFactor;
}
--
2.44.0

View File

@@ -23,6 +23,11 @@ stdenv.mkDerivation rec {
sha256 = "1gnwihpfz4x18rwd6cbrdggmfqjzwsdfh1gpmc0ph21c4gq2097g";
};
patches = [
./CVE-2023-52425.patch
./CVE-2024-28757.patch
];
strictDeps = true;
outputs = [ "out" "dev" ]; # TODO: fix referrers

View File

@@ -1,21 +0,0 @@
{lib, stdenv, fetchurl}:
stdenv.mkDerivation rec {
pname = "giflib";
version = "4.1.6";
src = fetchurl {
url = "mirror://sourceforge/giflib/giflib-${version}.tar.bz2";
sha256 = "1v9b7ywz7qg8hli0s9vv1b8q9xxb2xvqq2mg1zpr73xwqpcwxhg1";
};
hardeningDisable = [ "format" ];
meta = with lib; {
description = "A library for reading and writing gif images";
branch = "4.1";
license = licenses.mit;
platforms = platforms.unix;
};
}

View File

@@ -0,0 +1,26 @@
From ccbc956432650734c91acb3fc88837f7b81267ff Mon Sep 17 00:00:00 2001
From: "Eric S. Raymond" <esr@thyrsus.com>
Date: Wed, 21 Feb 2024 18:55:00 -0500
Subject: [PATCH] Clean up memory better at end of run (CVE-2021-40633)
---
gif2rgb.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/gif2rgb.c b/gif2rgb.c
index d51226d..fc2e683 100644
--- a/gif2rgb.c
+++ b/gif2rgb.c
@@ -517,6 +517,9 @@ static void GIF2RGB(int NumFiles, char *FileName, bool OneFileFlag,
DumpScreen2RGB(OutFileName, OneFileFlag, ColorMap, ScreenBuffer,
GifFile->SWidth, GifFile->SHeight);
+ for (i = 0; i < GifFile->SHeight; i++) {
+ (void)free(ScreenBuffer[i]);
+ }
(void)free(ScreenBuffer);
{
--
2.44.0

View File

@@ -4,31 +4,20 @@
, fetchpatch
, fixDarwinDylibNames
, pkgsStatic
, imagemagick_light
}:
stdenv.mkDerivation rec {
pname = "giflib";
version = "5.2.1";
version = "5.2.2";
src = fetchurl {
url = "mirror://sourceforge/giflib/giflib-${version}.tar.gz";
sha256 = "1gbrg03z1b6rlrvjyc6d41bc8j1bsr7rm8206gb1apscyii5bnii";
hash = "sha256-vn/70FfK3r4qoURUL9kMaDjGoIO16KkEi47jtmsp1fs=";
};
patches = [
(fetchpatch {
name = "CVE-2022-28506.patch";
url = "https://src.fedoraproject.org/rpms/giflib/raw/2e9917bf13df114354163f0c0211eccc00943596/f/CVE-2022-28506.patch";
sha256 = "sha256-TBemEXkuox8FdS9RvjnWcTWPaHRo4crcwSR9czrUwBY=";
})
] ++ lib.optionals stdenv.hostPlatform.isDarwin [
# https://sourceforge.net/p/giflib/bugs/133/
(fetchpatch {
name = "darwin-soname.patch";
url = "https://sourceforge.net/p/giflib/bugs/_discuss/thread/4e811ad29b/c323/attachment/Makefile.patch";
sha256 = "12afkqnlkl3n1hywwgx8sqnhp3bz0c5qrwcv8j9hifw1lmfhv67r";
extraPrefix = "./";
})
./CVE-2021-40633.patch
] ++ lib.optionals stdenv.hostPlatform.isMinGW [
# Build dll libraries.
(fetchurl {
@@ -40,7 +29,9 @@ stdenv.mkDerivation rec {
./mingw-install-exes.patch
];
nativeBuildInputs = lib.optionals stdenv.isDarwin [
nativeBuildInputs = [
imagemagick_light
] ++ lib.optionals stdenv.isDarwin [
fixDarwinDylibNames
];
@@ -50,10 +41,11 @@ stdenv.mkDerivation rec {
postPatch = lib.optionalString stdenv.hostPlatform.isStatic ''
# Upstream build system does not support NOT building shared libraries.
sed -i '/all:/ s/libgif.so//' Makefile
sed -i '/all:/ s/libutil.so//' Makefile
sed -i '/-m 755 libgif.so/ d' Makefile
sed -i '/ln -sf libgif.so/ d' Makefile
sed -i '/all:/ s/$(LIBGIFSO)//' Makefile
sed -i '/all:/ s/$(LIBUTILSO)//' Makefile
sed -i '/-m 755 $(LIBGIFSO)/ d' Makefile
sed -i '/ln -sf $(LIBGIFSOVER)/ d' Makefile
sed -i '/ln -sf $(LIBGIFSOMAJOR)/ d' Makefile
'';
passthru.tests = {

View File

@@ -1,4 +1,4 @@
{ config, lib, stdenv, fetchurl, zlib, lzo, libtasn1, nettle, pkg-config, lzip
{ config, lib, stdenv, fetchurl, fetchpatch, zlib, lzo, libtasn1, nettle, pkg-config, lzip
, perl, gmp, autoconf, automake, libidn2, libiconv
, texinfo
, unbound, dns-root-data, gettext, util-linux
@@ -49,6 +49,16 @@ stdenv.mkDerivation rec {
patches = [
./nix-ssl-cert-file.patch
(fetchpatch {
name = "CVE-2024-28834.patch";
url = "https://gitlab.com/gnutls/gnutls/-/commit/1c4701ffc342259fc5965d5a0de90d87f780e3e5.patch";
hash = "sha256-QMqeEdpNy5MCuZwTPRVKnWMvGkZvIlumigvH3JgcjE4=";
})
(fetchpatch {
name = "CVE-2024-28835.patch";
url = "https://gitlab.com/gnutls/gnutls/-/commit/e369e67a62f44561d417cb233acc566cc696d82d.patch";
hash = "sha256-K8wqypFKfoB7hiFWJPSVVEC7Aei7NGRijorK/tkou9o=";
})
];
# Skip some tests:

View File

@@ -44,6 +44,11 @@ stdenv.mkDerivation (finalAttrs: {
url = "https://github.com/libarchive/libarchive/commit/3bd918d92f8c34ba12de9c6604d96f9e262a59fc.patch";
hash = "sha256-RM3xFM6S2DkM5DJ0kAba8eLzEXuY5/7AaU06maHJ6rM=";
})
(fetchpatch {
name = "fix-suspicious-commit-from-known-bad-actor.patch";
url = "https://github.com/libarchive/libarchive/commit/6110e9c82d8ba830c3440f36b990483ceaaea52c.patch";
hash = "sha256-/j6rJ0xWhtXU0YCu1LOokxxNppy5Of6Q0XyO4U6la7M=";
})
];
outputs = [ "out" "lib" "dev" ];

View File

@@ -0,0 +1,826 @@
Based on upstream cb0f390cc1f98a8e82b27259f8f3cd5f162992eb, adjusted to
apply to 11.6.1
diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc
index f8fd689a..7a712010 100644
--- a/libqpdf/QPDF_json.cc
+++ b/libqpdf/QPDF_json.cc
@@ -14,7 +14,7 @@
// This chart shows an example of the state transitions that would occur in parsing a minimal file.
-// | st_initial
+// |
// { | -> st_top
// "qpdf": [ | -> st_qpdf
// { | -> st_qpdf_meta
@@ -47,7 +47,7 @@
// } | <- st_objects
// } | <- st_qpdf
// ] | <- st_top
-// } | <- st_initial
+// } |
static char const* JSON_PDF = (
// force line break
@@ -99,7 +99,7 @@ is_indirect_object(std::string const& v, int& obj, int& gen)
}
obj = QUtil::string_to_int(o_str.c_str());
gen = QUtil::string_to_int(g_str.c_str());
- return true;
+ return obj > 0;
}
static bool
@@ -250,7 +250,6 @@ class QPDF::JSONReactor: public JSON::Reactor
private:
enum state_e {
- st_initial,
st_top,
st_qpdf,
st_qpdf_meta,
@@ -262,28 +261,35 @@ class QPDF::JSONReactor: public JSON::Reactor
st_ignore,
};
+ struct StackFrame
+ {
+ StackFrame(state_e state) :
+ state(state){};
+ StackFrame(state_e state, QPDFObjectHandle&& object) :
+ state(state),
+ object(object){};
+ state_e state;
+ QPDFObjectHandle object;
+ };
+
void containerStart();
- void nestedState(std::string const& key, JSON const& value, state_e);
+ bool setNextStateIfDictionary(std::string const& key, JSON const& value, state_e);
void setObjectDescription(QPDFObjectHandle& oh, JSON const& value);
QPDFObjectHandle makeObject(JSON const& value);
void error(qpdf_offset_t offset, std::string const& message);
- void
- replaceObject(QPDFObjectHandle to_replace, QPDFObjectHandle replacement, JSON const& value);
+ void replaceObject(QPDFObjectHandle&& replacement, JSON const& value);
QPDF& pdf;
std::shared_ptr<InputSource> is;
bool must_be_complete{true};
std::shared_ptr<QPDFValue::Description> descr;
bool errors{false};
- bool parse_error{false};
bool saw_qpdf{false};
bool saw_qpdf_meta{false};
bool saw_objects{false};
bool saw_json_version{false};
bool saw_pdf_version{false};
bool saw_trailer{false};
- state_e state{st_initial};
- state_e next_state{st_top};
std::string cur_object;
bool saw_value{false};
bool saw_stream{false};
@@ -291,9 +297,10 @@ class QPDF::JSONReactor: public JSON::Reactor
bool saw_data{false};
bool saw_datafile{false};
bool this_stream_needs_data{false};
- std::vector<state_e> state_stack{st_initial};
- std::vector<QPDFObjectHandle> object_stack;
std::set<QPDFObjGen> reserved;
+ std::vector<StackFrame> stack;
+ QPDFObjectHandle next_obj;
+ state_e next_state{st_top};
};
void
@@ -316,8 +323,12 @@ QPDF::JSONReactor::anyErrors() const
void
QPDF::JSONReactor::containerStart()
{
- state_stack.push_back(state);
- state = next_state;
+ if (next_obj.isInitialized()) {
+ stack.emplace_back(next_state, std::move(next_obj));
+ next_obj = QPDFObjectHandle();
+ } else {
+ stack.emplace_back(next_state);
+ }
}
void
@@ -329,20 +340,19 @@ QPDF::JSONReactor::dictionaryStart()
void
QPDF::JSONReactor::arrayStart()
{
- containerStart();
- if (state == st_top) {
+ if (stack.empty()) {
QTC::TC("qpdf", "QPDF_json top-level array");
throw std::runtime_error("QPDF JSON must be a dictionary");
}
+ containerStart();
}
void
QPDF::JSONReactor::containerEnd(JSON const& value)
{
- auto from_state = state;
- state = state_stack.back();
- state_stack.pop_back();
- if (state == st_initial) {
+ auto from_state = stack.back().state;
+ stack.pop_back();
+ if (stack.empty()) {
if (!this->saw_qpdf) {
QTC::TC("qpdf", "QPDF_json missing qpdf");
error(0, "\"qpdf\" object was not seen");
@@ -365,26 +375,16 @@ QPDF::JSONReactor::containerEnd(JSON const& value)
}
}
}
- } else if (state == st_objects) {
- if (parse_error) {
- QTC::TC("qpdf", "QPDF_json don't check object after parse error");
- } else if (cur_object == "trailer") {
- if (!saw_value) {
- QTC::TC("qpdf", "QPDF_json trailer no value");
- error(value.getStart(), "\"trailer\" is missing \"value\"");
- }
- } else if (saw_value == saw_stream) {
+ } else if (from_state == st_trailer) {
+ if (!saw_value) {
+ QTC::TC("qpdf", "QPDF_json trailer no value");
+ error(value.getStart(), "\"trailer\" is missing \"value\"");
+ }
+ } else if (from_state == st_object_top) {
+ if (saw_value == saw_stream) {
QTC::TC("qpdf", "QPDF_json value stream both or neither");
error(value.getStart(), "object must have exactly one of \"value\" or \"stream\"");
}
- object_stack.clear();
- this->cur_object = "";
- this->saw_dict = false;
- this->saw_data = false;
- this->saw_datafile = false;
- this->saw_value = false;
- this->saw_stream = false;
- } else if (state == st_object_top) {
if (saw_stream) {
if (!saw_dict) {
QTC::TC("qpdf", "QPDF_json stream no dict");
@@ -408,11 +408,7 @@ QPDF::JSONReactor::containerEnd(JSON const& value)
}
}
}
- } else if ((state == st_stream) || (state == st_object)) {
- if (!parse_error) {
- object_stack.pop_back();
- }
- } else if ((state == st_top) && (from_state == st_qpdf)) {
+ } else if (from_state == st_qpdf) {
// Handle dangling indirect object references which the PDF spec says to treat as nulls.
// It's tempting to make this an error, but that would be wrong since valid input files may
// have these.
@@ -423,16 +419,27 @@ QPDF::JSONReactor::containerEnd(JSON const& value)
}
}
}
+ if (!stack.empty()) {
+ auto state = stack.back().state;
+ if (state == st_objects) {
+ this->cur_object = "";
+ this->saw_dict = false;
+ this->saw_data = false;
+ this->saw_datafile = false;
+ this->saw_value = false;
+ this->saw_stream = false;
+ }
+ }
}
void
-QPDF::JSONReactor::replaceObject(
- QPDFObjectHandle to_replace, QPDFObjectHandle replacement, JSON const& value)
+QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& value)
{
- auto og = to_replace.getObjGen();
+ auto& tos = stack.back();
+ auto og = tos.object.getObjGen();
this->pdf.replaceObject(og, replacement);
- auto oh = pdf.getObject(og);
- setObjectDescription(oh, value);
+ next_obj = pdf.getObject(og);
+ setObjectDescription(tos.object, value);
}
void
@@ -442,22 +449,26 @@ QPDF::JSONReactor::topLevelScalar()
throw std::runtime_error("QPDF JSON must be a dictionary");
}
-void
-QPDF::JSONReactor::nestedState(std::string const& key, JSON const& value, state_e next)
+bool
+QPDF::JSONReactor::setNextStateIfDictionary(std::string const& key, JSON const& value, state_e next)
{
// Use this method when the next state is for processing a nested dictionary.
if (value.isDictionary()) {
this->next_state = next;
- } else {
- error(value.getStart(), "\"" + key + "\" must be a dictionary");
- this->next_state = st_ignore;
- this->parse_error = true;
+ return true;
}
+ error(value.getStart(), "\"" + key + "\" must be a dictionary");
+ return false;
}
bool
QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
{
+ if (stack.empty()) {
+ throw std::logic_error("stack is empty in dictionaryItem");
+ }
+ next_state = st_ignore;
+ auto state = stack.back().state;
if (state == st_ignore) {
QTC::TC("qpdf", "QPDF_json ignoring in st_ignore");
// ignore
@@ -467,51 +478,48 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
if (!value.isArray()) {
QTC::TC("qpdf", "QPDF_json qpdf not array");
error(value.getStart(), "\"qpdf\" must be an array");
- next_state = st_ignore;
- parse_error = true;
} else {
next_state = st_qpdf;
}
} else {
// Ignore all other fields.
QTC::TC("qpdf", "QPDF_json ignoring unknown top-level key");
- next_state = st_ignore;
}
} else if (state == st_qpdf_meta) {
if (key == "pdfversion") {
this->saw_pdf_version = true;
- bool version_okay = false;
std::string v;
+ bool okay = false;
if (value.getString(v)) {
std::string version;
char const* p = v.c_str();
if (QPDF::validatePDFVersion(p, version) && (*p == '\0')) {
- version_okay = true;
this->pdf.m->pdf_version = version;
+ okay = true;
}
}
- if (!version_okay) {
+ if (!okay) {
QTC::TC("qpdf", "QPDF_json bad pdf version");
- error(value.getStart(), "invalid PDF version (must be x.y)");
+ error(value.getStart(), "invalid PDF version (must be \"x.y\")");
}
} else if (key == "jsonversion") {
this->saw_json_version = true;
- bool version_okay = false;
std::string v;
+ bool okay = false;
if (value.getNumber(v)) {
std::string version;
if (QUtil::string_to_int(v.c_str()) == 2) {
- version_okay = true;
+ okay = true;
}
}
- if (!version_okay) {
+ if (!okay) {
QTC::TC("qpdf", "QPDF_json bad json version");
- error(value.getStart(), "invalid JSON version (must be 2)");
+ error(value.getStart(), "invalid JSON version (must be numeric value 2)");
}
} else if (key == "pushedinheritedpageresources") {
bool v;
if (value.getBool(v)) {
- if ((!this->must_be_complete) && v) {
+ if (!this->must_be_complete && v) {
this->pdf.pushInheritedAttributesToPage();
}
} else {
@@ -521,7 +529,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
} else if (key == "calledgetallpages") {
bool v;
if (value.getBool(v)) {
- if ((!this->must_be_complete) && v) {
+ if (!this->must_be_complete && v) {
this->pdf.getAllPages();
}
} else {
@@ -532,103 +540,95 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
// ignore unknown keys for forward compatibility and to skip keys we don't care about
// like "maxobjectid".
QTC::TC("qpdf", "QPDF_json ignore second-level key");
- next_state = st_ignore;
}
} else if (state == st_objects) {
int obj = 0;
int gen = 0;
if (key == "trailer") {
this->saw_trailer = true;
- nestedState(key, value, st_trailer);
this->cur_object = "trailer";
+ setNextStateIfDictionary(key, value, st_trailer);
} else if (is_obj_key(key, obj, gen)) {
this->cur_object = key;
- auto oh = pdf.reserveObjectIfNotExists(QPDFObjGen(obj, gen));
- object_stack.push_back(oh);
- nestedState(key, value, st_object_top);
+ if (setNextStateIfDictionary(key, value, st_object_top)) {
+ next_obj = pdf.reserveObjectIfNotExists(QPDFObjGen(obj, gen));
+ }
} else {
QTC::TC("qpdf", "QPDF_json bad object key");
error(value.getStart(), "object key should be \"trailer\" or \"obj:n n R\"");
- next_state = st_ignore;
- parse_error = true;
}
} else if (state == st_object_top) {
- if (object_stack.size() == 0) {
- throw std::logic_error("no object on stack in st_object_top");
+ if (stack.empty()) {
+ throw std::logic_error("stack empty in st_object_top");
+ }
+ auto& tos = stack.back();
+ if (!tos.object.isInitialized()) {
+ throw std::logic_error("current object uninitialized in st_object_top");
}
- auto tos = object_stack.back();
- QPDFObjectHandle replacement;
if (key == "value") {
- // Don't use nestedState since this can have any type.
+ // Don't use setNextStateIfDictionary since this can have any type.
this->saw_value = true;
+ replaceObject(makeObject(value), value);
next_state = st_object;
- replacement = makeObject(value);
- replaceObject(tos, replacement, value);
} else if (key == "stream") {
this->saw_stream = true;
- nestedState(key, value, st_stream);
- this->this_stream_needs_data = false;
- if (tos.isStream()) {
- QTC::TC("qpdf", "QPDF_json updating existing stream");
+ if (setNextStateIfDictionary(key, value, st_stream)) {
+ this->this_stream_needs_data = false;
+ if (tos.object.isStream()) {
+ QTC::TC("qpdf", "QPDF_json updating existing stream");
+ } else {
+ this->this_stream_needs_data = true;
+ replaceObject(pdf.reserveStream(tos.object.getObjGen()), value);
+ }
+ next_obj = tos.object;
} else {
- this->this_stream_needs_data = true;
- replacement = pdf.reserveStream(tos.getObjGen());
- replaceObject(tos, replacement, value);
+ // Error message already given above
+ QTC::TC("qpdf", "QPDF_json stream not a dictionary");
}
} else {
// Ignore unknown keys for forward compatibility
QTC::TC("qpdf", "QPDF_json ignore unknown key in object_top");
- next_state = st_ignore;
- }
- if (replacement.isInitialized()) {
- object_stack.pop_back();
- object_stack.push_back(replacement);
}
} else if (state == st_trailer) {
if (key == "value") {
this->saw_value = true;
- // The trailer must be a dictionary, so we can use nestedState.
- nestedState("trailer.value", value, st_object);
- this->pdf.m->trailer = makeObject(value);
- setObjectDescription(this->pdf.m->trailer, value);
+ // The trailer must be a dictionary, so we can use setNextStateIfDictionary.
+ if (setNextStateIfDictionary("trailer.value", value, st_object)) {
+ this->pdf.m->trailer = makeObject(value);
+ setObjectDescription(this->pdf.m->trailer, value);
+ }
} else if (key == "stream") {
// Don't need to set saw_stream here since there's already an error.
QTC::TC("qpdf", "QPDF_json trailer stream");
error(value.getStart(), "the trailer may not be a stream");
- next_state = st_ignore;
- parse_error = true;
} else {
// Ignore unknown keys for forward compatibility
QTC::TC("qpdf", "QPDF_json ignore unknown key in trailer");
- next_state = st_ignore;
}
} else if (state == st_stream) {
- if (object_stack.size() == 0) {
- throw std::logic_error("no object on stack in st_stream");
+ if (stack.empty()) {
+ throw std::logic_error("stack empty in st_stream");
}
- auto tos = object_stack.back();
- if (!tos.isStream()) {
- throw std::logic_error("top of stack is not stream in st_stream");
+ auto& tos = stack.back();
+ if (!tos.object.isStream()) {
+ throw std::logic_error("current object is not stream in st_stream");
}
auto uninitialized = QPDFObjectHandle();
if (key == "dict") {
this->saw_dict = true;
- // Since a stream dictionary must be a dictionary, we can use nestedState to transition
- // to st_value.
- nestedState("stream.dict", value, st_object);
- auto dict = makeObject(value);
- if (dict.isDictionary()) {
- tos.replaceDict(dict);
+ if (setNextStateIfDictionary("stream.dict", value, st_object)) {
+ tos.object.replaceDict(makeObject(value));
} else {
- // An error had already been given by nestedState
+ // An error had already been given by setNextStateIfDictionary
QTC::TC("qpdf", "QPDF_json stream dict not dict");
- parse_error = true;
}
} else if (key == "data") {
this->saw_data = true;
std::string v;
if (!value.getString(v)) {
+ QTC::TC("qpdf", "QPDF_json stream data not string");
error(value.getStart(), "\"stream.data\" must be a string");
+ tos.object.replaceStreamData("", uninitialized, uninitialized);
} else {
// The range includes the quotes.
auto start = value.getStart() + 1;
@@ -636,32 +636,40 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
if (end < start) {
throw std::logic_error("QPDF_json: JSON string length < 0");
}
- tos.replaceStreamData(provide_data(is, start, end), uninitialized, uninitialized);
+ tos.object.replaceStreamData(
+ provide_data(is, start, end), uninitialized, uninitialized);
}
} else if (key == "datafile") {
this->saw_datafile = true;
std::string filename;
- if (value.getString(filename)) {
- tos.replaceStreamData(QUtil::file_provider(filename), uninitialized, uninitialized);
- } else {
+ if (!value.getString(filename)) {
+ QTC::TC("qpdf", "QPDF_json stream datafile not string");
error(
value.getStart(),
- "\"stream.datafile\" must be a string containing a file "
- "name");
+ "\"stream.datafile\" must be a string containing a file name");
+ tos.object.replaceStreamData("", uninitialized, uninitialized);
+ } else {
+ tos.object.replaceStreamData(
+ QUtil::file_provider(filename), uninitialized, uninitialized);
}
} else {
// Ignore unknown keys for forward compatibility.
QTC::TC("qpdf", "QPDF_json ignore unknown key in stream");
- next_state = st_ignore;
}
} else if (state == st_object) {
- if (!parse_error) {
- auto dict = object_stack.back();
- if (dict.isStream()) {
- dict = dict.getDict();
- }
- dict.replaceKey(key, makeObject(value));
+ if (stack.empty()) {
+ throw std::logic_error("stack empty in st_object");
+ }
+ auto& tos = stack.back();
+ auto dict = tos.object;
+ if (dict.isStream()) {
+ dict = dict.getDict();
+ }
+ if (!dict.isDictionary()) {
+ throw std::logic_error(
+ "current object is not stream or dictionary in st_object dictionary item");
}
+ dict.replaceKey(key, makeObject(value));
} else {
throw std::logic_error("QPDF_json: unknown state " + std::to_string(state));
}
@@ -671,25 +679,24 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
bool
QPDF::JSONReactor::arrayItem(JSON const& value)
{
+ if (stack.empty()) {
+ throw std::logic_error("stack is empty in arrayItem");
+ }
+ next_state = st_ignore;
+ auto state = stack.back().state;
if (state == st_qpdf) {
if (!this->saw_qpdf_meta) {
this->saw_qpdf_meta = true;
- nestedState("qpdf[0]", value, st_qpdf_meta);
+ setNextStateIfDictionary("qpdf[0]", value, st_qpdf_meta);
} else if (!this->saw_objects) {
this->saw_objects = true;
- nestedState("qpdf[1]", value, st_objects);
+ setNextStateIfDictionary("qpdf[1]", value, st_objects);
} else {
QTC::TC("qpdf", "QPDF_json more than two qpdf elements");
error(value.getStart(), "\"qpdf\" must have two elements");
- next_state = st_ignore;
- parse_error = true;
- }
- }
- if (state == st_object) {
- if (!parse_error) {
- auto tos = object_stack.back();
- tos.appendItem(makeObject(value));
}
+ } else if (state == st_object) {
+ stack.back().object.appendItem(makeObject(value));
}
return true;
}
@@ -714,10 +721,12 @@ QPDF::JSONReactor::makeObject(JSON const& value)
bool bool_v = false;
if (value.isDictionary()) {
result = QPDFObjectHandle::newDictionary();
- object_stack.push_back(result);
+ next_obj = result;
+ next_state = st_object;
} else if (value.isArray()) {
result = QPDFObjectHandle::newArray();
- object_stack.push_back(result);
+ next_obj = result;
+ next_state = st_object;
} else if (value.isNull()) {
result = QPDFObjectHandle::newNull();
} else if (value.getBool(bool_v)) {
diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test
index 2867f8a7..4982d440 100644
--- a/qpdf/qtest/qpdf-json.test
+++ b/qpdf/qtest/qpdf-json.test
@@ -37,6 +37,8 @@ my @badfiles = (
'obj-key-errors',
'bad-data',
'bad-datafile',
+ 'bad-data2',
+ 'bad-datafile2',
);
$n_tests += scalar(@badfiles);
diff --git a/qpdf/qtest/qpdf/qjson-bad-data2.json b/qpdf/qtest/qpdf/qjson-bad-data2.json
new file mode 100644
index 00000000..80206086
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-bad-data2.json
@@ -0,0 +1,71 @@
+{
+ "qpdf": [
+ {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "maxobjectid": 6
+ },
+ {
+ "obj:1 0 R": {
+ "value": {
+ "/Pages": "2 0 R",
+ "/Type": "/Catalog"
+ }
+ },
+ "obj:2 0 R": {
+ "value": {
+ "/Count": 1,
+ "/Kids": [
+ "3 0 R"
+ ],
+ "/Type": "/Pages"
+ }
+ },
+ "obj:3 0 R": {
+ "value": {
+ "/Contents": ["4 0 R", "7 0 R"],
+ "/MediaBox": [
+ 0,
+ 0,
+ 612,
+ 792
+ ],
+ "/Parent": "2 0 R",
+ "/Resources": {
+ "/Font": {
+ "/F1": "6 0 R"
+ },
+ "/ProcSet": "5 0 R"
+ },
+ "/Type": "/Page"
+ }
+ },
+ "obj:4 0 R": {
+ "stream": {
+ "data": [[]],
+ "dict": {}
+ }
+ },
+ "obj:5 0 R": {
+ "value": [
+ "/PDF",
+ "/Text"
+ ]
+ },
+ "obj:6 0 R": {
+ "value": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ }
+ },
+ "trailer": {
+ "value": {
+ "/Root": "1 0 R",
+ "/Size": 7
+ }
+ }
+ }
+ ]
+}
diff --git a/qpdf/qtest/qpdf/qjson-bad-data2.out b/qpdf/qtest/qpdf/qjson-bad-data2.out
new file mode 100644
index 00000000..47c83c8e
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-bad-data2.out
@@ -0,0 +1,2 @@
+WARNING: qjson-bad-data2.json (obj:4 0 R, offset 846): "stream.data" must be a string
+qpdf: qjson-bad-data2.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-bad-datafile2.json b/qpdf/qtest/qpdf/qjson-bad-datafile2.json
new file mode 100644
index 00000000..b5c99820
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-bad-datafile2.json
@@ -0,0 +1,71 @@
+{
+ "qpdf": [
+ {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "maxobjectid": 6
+ },
+ {
+ "obj:1 0 R": {
+ "value": {
+ "/Pages": "2 0 R",
+ "/Type": "/Catalog"
+ }
+ },
+ "obj:2 0 R": {
+ "value": {
+ "/Count": 1,
+ "/Kids": [
+ "3 0 R"
+ ],
+ "/Type": "/Pages"
+ }
+ },
+ "obj:3 0 R": {
+ "value": {
+ "/Contents": ["4 0 R", "7 0 R"],
+ "/MediaBox": [
+ 0,
+ 0,
+ 612,
+ 792
+ ],
+ "/Parent": "2 0 R",
+ "/Resources": {
+ "/Font": {
+ "/F1": "6 0 R"
+ },
+ "/ProcSet": "5 0 R"
+ },
+ "/Type": "/Page"
+ }
+ },
+ "obj:4 0 R": {
+ "stream": {
+ "datafile": [[]],
+ "dict": {}
+ }
+ },
+ "obj:5 0 R": {
+ "value": [
+ "/PDF",
+ "/Text"
+ ]
+ },
+ "obj:6 0 R": {
+ "value": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ }
+ },
+ "trailer": {
+ "value": {
+ "/Root": "1 0 R",
+ "/Size": 7
+ }
+ }
+ }
+ ]
+}
diff --git a/qpdf/qtest/qpdf/qjson-bad-datafile2.out b/qpdf/qtest/qpdf/qjson-bad-datafile2.out
new file mode 100644
index 00000000..41949a21
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-bad-datafile2.out
@@ -0,0 +1,2 @@
+WARNING: qjson-bad-datafile2.json (obj:4 0 R, offset 850): "stream.datafile" must be a string containing a file name
+qpdf: qjson-bad-datafile2.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out
index f364f1a6..476128e7 100644
--- a/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out
+++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out
@@ -1,3 +1,3 @@
-WARNING: qjson-bad-pdf-version1.json (offset 41): invalid JSON version (must be 2)
-WARNING: qjson-bad-pdf-version1.json (offset 70): invalid PDF version (must be x.y)
+WARNING: qjson-bad-pdf-version1.json (offset 41): invalid JSON version (must be numeric value 2)
+WARNING: qjson-bad-pdf-version1.json (offset 70): invalid PDF version (must be "x.y")
qpdf: qjson-bad-pdf-version1.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out
index 9bc88ff4..cb914414 100644
--- a/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out
+++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out
@@ -1,5 +1,5 @@
-WARNING: qjson-bad-pdf-version2.json (offset 41): invalid JSON version (must be 2)
-WARNING: qjson-bad-pdf-version2.json (offset 66): invalid PDF version (must be x.y)
+WARNING: qjson-bad-pdf-version2.json (offset 41): invalid JSON version (must be numeric value 2)
+WARNING: qjson-bad-pdf-version2.json (offset 66): invalid PDF version (must be "x.y")
WARNING: qjson-bad-pdf-version2.json (offset 97): calledgetallpages must be a boolean
WARNING: qjson-bad-pdf-version2.json (offset 138): pushedinheritedpageresources must be a boolean
qpdf: qjson-bad-pdf-version2.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-obj-key-errors.out b/qpdf/qtest/qpdf/qjson-obj-key-errors.out
index 0263f294..f1f0a369 100644
--- a/qpdf/qtest/qpdf/qjson-obj-key-errors.out
+++ b/qpdf/qtest/qpdf/qjson-obj-key-errors.out
@@ -1,7 +1,7 @@
WARNING: qjson-obj-key-errors.json (obj:2 0 R, offset 244): object must have exactly one of "value" or "stream"
WARNING: qjson-obj-key-errors.json (obj:3 0 R, offset 542): object must have exactly one of "value" or "stream"
-WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 710): "stream" is missing "dict"
-WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 710): new "stream" must have exactly one of "data" or "datafile"
-WARNING: qjson-obj-key-errors.json (obj:5 0 R, offset 800): new "stream" must have exactly one of "data" or "datafile"
+WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 690): "stream" is missing "dict"
+WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 690): new "stream" must have exactly one of "data" or "datafile"
+WARNING: qjson-obj-key-errors.json (obj:5 0 R, offset 780): new "stream" must have exactly one of "data" or "datafile"
WARNING: qjson-obj-key-errors.json (trailer, offset 1178): "trailer" is missing "value"
qpdf: qjson-obj-key-errors.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out
index a264839f..04df1518 100644
--- a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out
+++ b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out
@@ -1,5 +1,4 @@
WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 142): "stream.dict" must be a dictionary
-WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 142): unrecognized string value
-WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 122): new "stream" must have exactly one of "data" or "datafile"
+WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 102): new "stream" must have exactly one of "data" or "datafile"
WARNING: qjson-stream-dict-not-dict.json: "qpdf[1].trailer" was not seen
qpdf: qjson-stream-dict-not-dict.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-stream-not-dict.out b/qpdf/qtest/qpdf/qjson-stream-not-dict.out
index fbd953c6..db775b59 100644
--- a/qpdf/qtest/qpdf/qjson-stream-not-dict.out
+++ b/qpdf/qtest/qpdf/qjson-stream-not-dict.out
@@ -1,3 +1,4 @@
WARNING: qjson-stream-not-dict.json (obj:1 0 R, offset 122): "stream" must be a dictionary
+WARNING: qjson-stream-not-dict.json (obj:1 0 R, offset 102): "stream" is missing "dict"
WARNING: qjson-stream-not-dict.json: "qpdf[1].trailer" was not seen
qpdf: qjson-stream-not-dict.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-trailer-stream.out b/qpdf/qtest/qpdf/qjson-trailer-stream.out
index a625cd6d..fccb2a39 100644
--- a/qpdf/qtest/qpdf/qjson-trailer-stream.out
+++ b/qpdf/qtest/qpdf/qjson-trailer-stream.out
@@ -1,2 +1,3 @@
WARNING: qjson-trailer-stream.json (trailer, offset 1269): the trailer may not be a stream
+WARNING: qjson-trailer-stream.json (trailer, offset 1249): "trailer" is missing "value"
qpdf: qjson-trailer-stream.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/update-from-json-errors.out b/qpdf/qtest/qpdf/update-from-json-errors.out
index 530d707d..5e136c55 100644
--- a/qpdf/qtest/qpdf/update-from-json-errors.out
+++ b/qpdf/qtest/qpdf/update-from-json-errors.out
@@ -1,4 +1,4 @@
-WARNING: good13.pdf (obj:4 0 R from qpdf-json-update-errors.json, offset 95): existing "stream" may at most one of "data" or "datafile"
+WARNING: good13.pdf (obj:4 0 R from qpdf-json-update-errors.json, offset 75): existing "stream" may at most one of "data" or "datafile"
WARNING: good13.pdf (obj:20 0 R from qpdf-json-update-errors.json, offset 335): unrecognized string value
-WARNING: good13.pdf (obj:20 0 R from qpdf-json-update-errors.json, offset 293): new "stream" must have exactly one of "data" or "datafile"
+WARNING: good13.pdf (obj:20 0 R from qpdf-json-update-errors.json, offset 273): new "stream" must have exactly one of "data" or "datafile"
qpdf: qpdf-json-update-errors.json: errors found in JSON

View File

@@ -11,6 +11,10 @@ stdenv.mkDerivation rec {
hash = "sha256-QXRzvSMi6gKISJo44KIjTYENNqxh1yDhUUhEZa8uz6Q=";
};
patches = [
./11.6.1-CVE-2024-24246.patch
];
nativeBuildInputs = [ cmake perl ];
buildInputs = [ zlib libjpeg ];

View File

@@ -0,0 +1,203 @@
Based on upstream 1c335944d6a8b1298baf179b7c0b3069f10c514b with a couple
of type hints removed from the added tests because they refer to type
aliases that don't exist in 3.8.6
diff --git a/CHANGES/8079.bugfix.rst b/CHANGES/8079.bugfix.rst
new file mode 100644
index 00000000..57bc8bfe
--- /dev/null
+++ b/CHANGES/8079.bugfix.rst
@@ -0,0 +1 @@
+Improved validation of paths for static resources -- by :user:`bdraco`.
diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py
index 5942e355..e8a8023e 100644
--- a/aiohttp/web_urldispatcher.py
+++ b/aiohttp/web_urldispatcher.py
@@ -593,9 +593,14 @@ class StaticResource(PrefixResource):
url = url / filename
if append_version:
+ unresolved_path = self._directory.joinpath(filename)
try:
- filepath = self._directory.joinpath(filename).resolve()
- if not self._follow_symlinks:
+ if self._follow_symlinks:
+ normalized_path = Path(os.path.normpath(unresolved_path))
+ normalized_path.relative_to(self._directory)
+ filepath = normalized_path.resolve()
+ else:
+ filepath = unresolved_path.resolve()
filepath.relative_to(self._directory)
except (ValueError, FileNotFoundError):
# ValueError for case when path point to symlink
@@ -660,8 +665,13 @@ class StaticResource(PrefixResource):
# /static/\\machine_name\c$ or /static/D:\path
# where the static dir is totally different
raise HTTPForbidden()
- filepath = self._directory.joinpath(filename).resolve()
- if not self._follow_symlinks:
+ unresolved_path = self._directory.joinpath(filename)
+ if self._follow_symlinks:
+ normalized_path = Path(os.path.normpath(unresolved_path))
+ normalized_path.relative_to(self._directory)
+ filepath = normalized_path.resolve()
+ else:
+ filepath = unresolved_path.resolve()
filepath.relative_to(self._directory)
except (ValueError, FileNotFoundError) as error:
# relatively safe
diff --git a/docs/web_advanced.rst b/docs/web_advanced.rst
index 3a98b78a..51293970 100644
--- a/docs/web_advanced.rst
+++ b/docs/web_advanced.rst
@@ -136,12 +136,22 @@ instead could be enabled with ``show_index`` parameter set to ``True``::
web.static('/prefix', path_to_static_folder, show_index=True)
-When a symlink from the static directory is accessed, the server responses to
-client with ``HTTP/404 Not Found`` by default. To allow the server to follow
-symlinks, parameter ``follow_symlinks`` should be set to ``True``::
+When a symlink that leads outside the static directory is accessed, the server
+responds to the client with ``HTTP/404 Not Found`` by default. To allow the server to
+follow symlinks that lead outside the static root, the parameter ``follow_symlinks``
+should be set to ``True``::
web.static('/prefix', path_to_static_folder, follow_symlinks=True)
+.. caution::
+
+ Enabling ``follow_symlinks`` can be a security risk, and may lead to
+ a directory transversal attack. You do NOT need this option to follow symlinks
+ which point to somewhere else within the static directory, this option is only
+ used to break out of the security sandbox. Enabling this option is highly
+ discouraged, and only expected to be used for edge cases in a local
+ development setting where remote users do not have access to the server.
+
When you want to enable cache busting,
parameter ``append_version`` can be set to ``True``
diff --git a/docs/web_reference.rst b/docs/web_reference.rst
index a156f47d..b1006764 100644
--- a/docs/web_reference.rst
+++ b/docs/web_reference.rst
@@ -1836,9 +1836,15 @@ Router is any object that implements :class:`~aiohttp.abc.AbstractRouter` interf
by default it's not allowed and HTTP/403 will
be returned on directory access.
- :param bool follow_symlinks: flag for allowing to follow symlinks from
- a directory, by default it's not allowed and
- HTTP/404 will be returned on access.
+ :param bool follow_symlinks: flag for allowing to follow symlinks that lead
+ outside the static root directory, by default it's not allowed and
+ HTTP/404 will be returned on access. Enabling ``follow_symlinks``
+ can be a security risk, and may lead to a directory transversal attack.
+ You do NOT need this option to follow symlinks which point to somewhere
+ else within the static directory, this option is only used to break out
+ of the security sandbox. Enabling this option is highly discouraged,
+ and only expected to be used for edge cases in a local development
+ setting where remote users do not have access to the server.
:param bool append_version: flag for adding file version (hash)
to the url query string, this value will
diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py
index f24f451e..6b8381c0 100644
--- a/tests/test_web_urldispatcher.py
+++ b/tests/test_web_urldispatcher.py
@@ -123,6 +123,97 @@ async def test_follow_symlink(tmp_dir_path, aiohttp_client) -> None:
assert (await r.text()) == data
+async def test_follow_symlink_directory_traversal(
+ tmp_path: pathlib.Path, aiohttp_client
+) -> None:
+ # Tests that follow_symlinks does not allow directory transversal
+ data = "private"
+
+ private_file = tmp_path / "private_file"
+ private_file.write_text(data)
+
+ safe_path = tmp_path / "safe_dir"
+ safe_path.mkdir()
+
+ app = web.Application()
+
+ # Register global static route:
+ app.router.add_static("/", str(safe_path), follow_symlinks=True)
+ client = await aiohttp_client(app)
+
+ await client.start_server()
+ # We need to use a raw socket to test this, as the client will normalize
+ # the path before sending it to the server.
+ reader, writer = await asyncio.open_connection(client.host, client.port)
+ writer.write(b"GET /../private_file HTTP/1.1\r\n\r\n")
+ response = await reader.readuntil(b"\r\n\r\n")
+ assert b"404 Not Found" in response
+ writer.close()
+ await writer.wait_closed()
+ await client.close()
+
+
+async def test_follow_symlink_directory_traversal_after_normalization(
+ tmp_path: pathlib.Path, aiohttp_client
+) -> None:
+ # Tests that follow_symlinks does not allow directory transversal
+ # after normalization
+ #
+ # Directory structure
+ # |-- secret_dir
+ # | |-- private_file (should never be accessible)
+ # | |-- symlink_target_dir
+ # | |-- symlink_target_file (should be accessible via the my_symlink symlink)
+ # | |-- sandbox_dir
+ # | |-- my_symlink -> symlink_target_dir
+ #
+ secret_path = tmp_path / "secret_dir"
+ secret_path.mkdir()
+
+ # This file is below the symlink target and should not be reachable
+ private_file = secret_path / "private_file"
+ private_file.write_text("private")
+
+ symlink_target_path = secret_path / "symlink_target_dir"
+ symlink_target_path.mkdir()
+
+ sandbox_path = symlink_target_path / "sandbox_dir"
+ sandbox_path.mkdir()
+
+ # This file should be reachable via the symlink
+ symlink_target_file = symlink_target_path / "symlink_target_file"
+ symlink_target_file.write_text("readable")
+
+ my_symlink_path = sandbox_path / "my_symlink"
+ pathlib.Path(str(my_symlink_path)).symlink_to(str(symlink_target_path), True)
+
+ app = web.Application()
+
+ # Register global static route:
+ app.router.add_static("/", str(sandbox_path), follow_symlinks=True)
+ client = await aiohttp_client(app)
+
+ await client.start_server()
+ # We need to use a raw socket to test this, as the client will normalize
+ # the path before sending it to the server.
+ reader, writer = await asyncio.open_connection(client.host, client.port)
+ writer.write(b"GET /my_symlink/../private_file HTTP/1.1\r\n\r\n")
+ response = await reader.readuntil(b"\r\n\r\n")
+ assert b"404 Not Found" in response
+ writer.close()
+ await writer.wait_closed()
+
+ reader, writer = await asyncio.open_connection(client.host, client.port)
+ writer.write(b"GET /my_symlink/symlink_target_file HTTP/1.1\r\n\r\n")
+ response = await reader.readuntil(b"\r\n\r\n")
+ assert b"200 OK" in response
+ response = await reader.readuntil(b"readable")
+ assert response == b"readable"
+ writer.close()
+ await writer.wait_closed()
+ await client.close()
+
+
@pytest.mark.parametrize(
"dir_name,filename,data",
[

View File

@@ -48,6 +48,7 @@ buildPythonPackage rec {
url = "https://github.com/aio-libs/aiohttp/commit/7dcc235cafe0c4521bbbf92f76aecc82fee33e8b.patch";
hash = "sha256-ZzhlE50bmA+e2XX2RH1FuWQHZIAa6Dk/hZjxPoX5t4g=";
})
./3.8.6-CVE-2024-23334.patch
];
postPatch = ''

View File

@@ -0,0 +1,30 @@
commit 166afaf144d6473464975438353257359dd51708
Author: Andreas Wiese <andreas.wiese@kernkonzept.com>
Date: Thu Feb 1 11:35:02 2024 +0100
aa-remove-unknown: fix readability check
This check is intended for ensuring that the profiles file can actually
be opened. The *actual* check is performed by the shell, not the read
utility, which won't even be executed if the input redirection (and
hence the test) fails.
If the test succeeds, though, using `read` here might actually
jeopardize the test result if there are no profiles loaded and the file
is empty.
This commit fixes that case by simply using `true` instead of `read`.
diff --git a/utils/aa-remove-unknown b/utils/aa-remove-unknown
index 0e00d6a0..3351feef 100755
--- a/utils/aa-remove-unknown
+++ b/utils/aa-remove-unknown
@@ -63,7 +63,7 @@ fi
# We have to do this check because error checking awk's getline() below is
# tricky and, as is, results in an infinite loop when apparmorfs returns an
# error from open().
-if ! IFS= read -r _ < "$PROFILES" ; then
+if ! true < "$PROFILES" ; then
echo "ERROR: Unable to read apparmorfs profiles file" 1>&2
exit 1
elif [ ! -w "$REMOVE" ] ; then

View File

@@ -56,7 +56,9 @@ let
--replace "/usr/include/linux/capability.h" "${linuxHeaders}/include/linux/capability.h"
'';
patches = lib.optionals stdenv.hostPlatform.isMusl [
patches = [
./0001-aa-remove-unknown_empty-ruleset.patch
] ++ lib.optionals stdenv.hostPlatform.isMusl [
(fetchpatch {
url = "https://git.alpinelinux.org/aports/plain/testing/apparmor/0003-Added-missing-typedef-definitions-on-parser.patch?id=74b8427cc21f04e32030d047ae92caa618105b53";
name = "0003-Added-missing-typedef-definitions-on-parser.patch";

View File

@@ -159,7 +159,7 @@ assert !withPasswordQuality;
let
wantCurl = withRemote || withImportd;
wantGcrypt = withResolved || withImportd;
version = "254.6";
version = "254.10";
# Bump this variable on every (major) version change. See below (in the meson options list) for why.
# command:
@@ -176,7 +176,7 @@ stdenv.mkDerivation (finalAttrs: {
owner = "systemd";
repo = "systemd-stable";
rev = "v${version}";
hash = "sha256-Ku24ecDeQt0t7A8/adR3Jm47QZ19+wdMPyJRzCxU4uU=";
hash = "sha256-+8v1LLvXoK0tMOfB7m9+H+HIHpZuYrIyWwuz7rphbmA=";
};
# On major changes, or when otherwise required, you *must* reformat the patches,

View File

@@ -815,15 +815,18 @@ fi
######################################################################
# Textual substitution functions.
substituteStream() {
local var=$1
local description=$2
shift 2
while (( "$#" )); do
local replace_mode="$1"
case "$1" in
--replace)
replace_mode='--replace-warn'
;&
--replace-quiet|--replace-warn|--replace-fail)
pattern="$2"
replacement="$3"
shift 3
@@ -832,7 +835,12 @@ substituteStream() {
eval "$var"'=${'"$var"'//"$pattern"/"$replacement"}'
if [ "$pattern" != "$replacement" ]; then
if [ "${!var}" == "$savedvar" ]; then
echo "substituteStream(): WARNING: pattern '$pattern' doesn't match anything in $description" >&2
if [ "$replace_mode" == --replace-warn ]; then
printf "substituteStream(): WARNING: pattern %q doesn't match anything in %s\n" "$pattern" "$description" >&2
elif [ "$replace_mode" == --replace-fail ]; then
printf "substituteStream(): ERROR: pattern %q doesn't match anything in %s\n" "$pattern" "$description" >&2
return 1
fi
fi
fi
;;

View File

@@ -0,0 +1,31 @@
From c4c5ed8f4e9cd55a12966d4f520e3a13101637d9 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Tue, 16 Jan 2024 13:48:32 -0800
Subject: [PATCH] split: do not shrink hold buffer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* src/split.c (line_bytes_split): Do not shrink hold buffer.
If its large for this batch its likely to be large for the next
batch, and for split its not worth the complexity/CPU hassle to
shrink it. Do not assume hold_size can be bufsize.
---
src/split.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/split.c b/src/split.c
index 64020c859..037960a59 100644
--- a/src/split.c
+++ b/src/split.c
@@ -809,10 +809,7 @@ line_bytes_split (intmax_t n_bytes, char *buf, idx_t bufsize)
{
cwrite (n_out == 0, hold, n_hold);
n_out += n_hold;
- if (n_hold > bufsize)
- hold = xirealloc (hold, bufsize);
n_hold = 0;
- hold_size = bufsize;
}
/* Output to eol if present. */

View File

@@ -39,6 +39,11 @@ stdenv.mkDerivation rec {
hash = "sha256-rbz8/omSNbceh2jc8HzVMlILf1T5qAZIQ/jRmakEu6o=";
};
patches = [
# https://www.openwall.com/lists/oss-security/2024/01/18/2
./CVE-2024-0684.patch
];
postPatch = ''
# The test tends to fail on btrfs, f2fs and maybe other unusual filesystems.
sed '2i echo Skipping dd sparse test && exit 77' -i ./tests/dd/sparse.sh

View File

@@ -0,0 +1,94 @@
From b1b7ab02e055249c02eac8c2a80295701f2530a2 Mon Sep 17 00:00:00 2001
From: Stefan Eissing <stefan@eissing.org>
Date: Wed, 6 Mar 2024 09:36:08 +0100
Subject: [PATCH 3/4] http2: push headers better cleanup
- provide common cleanup method for push headers
Closes #13054
(cherry picked from commit deca8039991886a559b67bcd6701db800a5cf764)
---
lib/http2.c | 34 +++++++++++++++-------------------
1 file changed, 15 insertions(+), 19 deletions(-)
diff --git a/lib/http2.c b/lib/http2.c
index c8b059498..8c422d156 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -271,6 +271,15 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf,
return CURLE_OK;
}
+static void free_push_headers(struct stream_ctx *stream)
+{
+ size_t i;
+ for(i = 0; i<stream->push_headers_used; i++)
+ free(stream->push_headers[i]);
+ Curl_safefree(stream->push_headers);
+ stream->push_headers_used = 0;
+}
+
static void http2_data_done(struct Curl_cfilter *cf,
struct Curl_easy *data, bool premature)
{
@@ -318,15 +327,7 @@ static void http2_data_done(struct Curl_cfilter *cf,
Curl_bufq_free(&stream->recvbuf);
Curl_h1_req_parse_free(&stream->h1);
Curl_dynhds_free(&stream->resp_trailers);
- if(stream->push_headers) {
- /* if they weren't used and then freed before */
- for(; stream->push_headers_used > 0; --stream->push_headers_used) {
- free(stream->push_headers[stream->push_headers_used - 1]);
- }
- free(stream->push_headers);
- stream->push_headers = NULL;
- }
-
+ free_push_headers(stream);
free(stream);
H2_STREAM_LCTX(data) = NULL;
}
@@ -865,7 +866,6 @@ static int push_promise(struct Curl_cfilter *cf,
struct curl_pushheaders heads;
CURLMcode rc;
CURLcode result;
- size_t i;
/* clone the parent */
struct Curl_easy *newhandle = h2_duphandle(cf, data);
if(!newhandle) {
@@ -910,11 +910,7 @@ static int push_promise(struct Curl_cfilter *cf,
Curl_set_in_callback(data, false);
/* free the headers again */
- for(i = 0; i<stream->push_headers_used; i++)
- free(stream->push_headers[i]);
- free(stream->push_headers);
- stream->push_headers = NULL;
- stream->push_headers_used = 0;
+ free_push_headers(stream);
if(rv) {
DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT));
@@ -1455,14 +1451,14 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
if(stream->push_headers_alloc > 1000) {
/* this is beyond crazy many headers, bail out */
failf(data_s, "Too many PUSH_PROMISE headers");
- Curl_safefree(stream->push_headers);
+ free_push_headers(stream);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
stream->push_headers_alloc *= 2;
- headp = Curl_saferealloc(stream->push_headers,
- stream->push_headers_alloc * sizeof(char *));
+ headp = realloc(stream->push_headers,
+ stream->push_headers_alloc * sizeof(char *));
if(!headp) {
- stream->push_headers = NULL;
+ free_push_headers(stream);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
stream->push_headers = headp;
--
2.44.0

View File

@@ -0,0 +1,137 @@
From b091564d9fd187c6dc6410e3badf4621df8f4e0a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Tue, 27 Feb 2024 15:43:56 +0100
Subject: [PATCH] setopt: Fix disabling all protocols
When disabling all protocols without enabling any, the resulting
set of allowed protocols remained the default set. Clearing the
allowed set before inspecting the passed value from --proto make
the set empty even in the errorpath of no protocols enabled.
Co-authored-by: Dan Fandrich <dan@telarity.com>
Reported-by: Dan Fandrich <dan@telarity.com>
Reviewed-by: Daniel Stenberg <daniel@haxx.se>
Closes: #13004
(cherry picked from commit 17d302e56221f5040092db77d4f85086e8a20e0e)
---
lib/setopt.c | 16 ++++++++--------
tests/data/Makefile.inc | 2 +-
tests/data/test1475 | 42 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 51 insertions(+), 9 deletions(-)
create mode 100644 tests/data/test1475
diff --git a/lib/setopt.c b/lib/setopt.c
index 0d399adfe..e022096f6 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -154,6 +154,12 @@ static CURLcode setstropt_userpwd(char *option, char **userp, char **passwdp)
static CURLcode protocol2num(const char *str, curl_prot_t *val)
{
+ /*
+ * We are asked to cherry-pick protocols, so play it safe and disallow all
+ * protocols to start with, and re-add the wanted ones back in.
+ */
+ *val = 0;
+
if(!str)
return CURLE_BAD_FUNCTION_ARGUMENT;
@@ -162,8 +168,6 @@ static CURLcode protocol2num(const char *str, curl_prot_t *val)
return CURLE_OK;
}
- *val = 0;
-
do {
const char *token = str;
size_t tlen;
@@ -2690,22 +2694,18 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
break;
case CURLOPT_PROTOCOLS_STR: {
- curl_prot_t prot;
argptr = va_arg(param, char *);
- result = protocol2num(argptr, &prot);
+ result = protocol2num(argptr, &data->set.allowed_protocols);
if(result)
return result;
- data->set.allowed_protocols = prot;
break;
}
case CURLOPT_REDIR_PROTOCOLS_STR: {
- curl_prot_t prot;
argptr = va_arg(param, char *);
- result = protocol2num(argptr, &prot);
+ result = protocol2num(argptr, &data->set.redir_protocols);
if(result)
return result;
- data->set.redir_protocols = prot;
break;
}
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index 1472b1954..bdb2ef742 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -185,7 +185,7 @@ test1439 test1440 test1441 test1442 test1443 test1444 test1445 test1446 \
test1447 test1448 test1449 test1450 test1451 test1452 test1453 test1454 \
test1455 test1456 test1457 test1458 test1459 test1460 test1461 test1462 \
test1463 test1464 test1465 test1466 test1467 test1468 test1469 test1470 \
-test1471 test1472 test1473 test1474 \
+test1471 test1472 test1473 test1474 test1475 \
\
test1500 test1501 test1502 test1503 test1504 test1505 test1506 test1507 \
test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \
diff --git a/tests/data/test1475 b/tests/data/test1475
new file mode 100644
index 000000000..c66fa2810
--- /dev/null
+++ b/tests/data/test1475
@@ -0,0 +1,42 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+--proto
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+none
+</server>
+<features>
+http
+</features>
+<name>
+--proto -all disables all protocols
+</name>
+<command>
+--proto -all http://%HOSTIP:%NOLISTENPORT/%TESTNUMBER
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+# 1 - Protocol "http" disabled
+<errorcode>
+1
+</errorcode>
+</verify>
+</testcase>
--
2.44.0

View File

@@ -65,6 +65,10 @@ stdenv.mkDerivation (finalAttrs: {
./0001-CVE-2023-42619.patch
# https://curl.se/docs/CVE-2023-46218.html
./0002-CVE-2023-42618.patch
# https://curl.se/docs/CVE-2024-2398.html
./0003-CVE-2024-2398.patch
# https://curl.se/docs/CVE-2024-2004.html
./0004-CVE-2024-2004.patch
];
outputs = [ "bin" "dev" "out" "man" "devdoc" ];

View File

@@ -57,11 +57,11 @@ let
in
stdenv.mkDerivation rec {
pname = "networkmanager";
version = "1.44.2";
version = "1.44.4";
src = fetchurl {
url = "mirror://gnome/sources/NetworkManager/${lib.versions.majorMinor version}/NetworkManager-${version}.tar.xz";
sha256 = "sha256-S1i/OsV+LO+1ZS79CUXrC0vDamPZKmGrRx2LssmkIOE=";
hash = "sha256-Aq+4X1qKPT1w0LahLIttst4uSWt3evW1cYLmC4CgNIk=";
};
outputs = [ "out" "dev" "devdoc" "man" "doc" ];

View File

@@ -49,11 +49,11 @@
stdenv.mkDerivation (finalAttrs: {
pname = "unbound";
version = "1.19.1";
version = "1.19.2";
src = fetchurl {
url = "https://nlnetlabs.nl/downloads/unbound/unbound-${finalAttrs.version}.tar.gz";
hash = "sha256-vB1Xbz3YRqBzmtxB/6pwJATGdn0rYILeufL5fLsko6k=";
hash = "sha256-zFYNNFc0ImwbOecadpeX5/3eImXLt3685UJwS7pInlU=";
};
outputs = [ "out" "lib" "man" ]; # "dev" would only split ~20 kB