Compare commits

...

14 Commits

Author SHA1 Message Date
internal-nix-ci[bot]
2531dcad75 Merge pull request #14238 from NixOS/backport-14237-to-2.32-maintenance
[Backport 2.32-maintenance] Remove validation of URLs passed to FileTransferRequest verbatim
2025-10-13 21:23:05 +00:00
Sergei Zimmerman
11f9c59140 Remove validation of URLs passed to FileTransferRequest verbatim
CURL is not very strict about validation of URLs passed to it. We
should reflect this in our handling of URLs that we get from the user
in <nix/fetchurl.nix> or builtins.fetchurl. ValidURL was an attempt to
rectify this, but it turned out to be too strict. The only good way to
resolve this is to pass (in some cases) the user-provided string verbatim
to CURL. Other usages in libfetchers still benefit from using structured
ParsedURL and validation though.

nix store prefetch-file --name foo 'https://cdn.skypack.dev/big.js@^5.2.2'
error: 'https://cdn.skypack.dev/big.js@^5.2.2' is not a valid URL: leftover

(cherry picked from commit 47f427a172)
2025-10-13 20:48:15 +00:00
internal-nix-ci[bot]
a25a219e79 Merge pull request #14213 from NixOS/backport-14194-to-2.32-maintenance
[Backport 2.32-maintenance] libutil: Print stack trace on assertion failure
2025-10-11 00:40:50 +00:00
Sergei Zimmerman
f07486b205 libutil: Print stack trace on assertion failure
This change overrides __assert_fail on glibc/musl
to instead call std::terminate that we have a custom
handler for. This ensures that we have more context
to diagnose issues encountered by users in the wild.

(cherry picked from commit 46382ade74)
2025-10-11 00:08:36 +00:00
internal-nix-ci[bot]
9ec98f7844 Merge pull request #14212 from NixOS/backport-14210-to-2.32-maintenance
[Backport 2.32-maintenance] libstore: Fix double-quoting of paths in logs
2025-10-10 23:26:18 +00:00
Sergei Zimmerman
634e1d3b65 libstore: Fix double-quoting of paths in logs
std::filesystem::path is already quoted by boost::format with double quotes (").
(cherry picked from commit f30cb8667b)
2025-10-10 22:54:22 +00:00
internal-nix-ci[bot]
70655061e3 Merge pull request #14202 from NixOS/backport-14199-to-2.32-maintenance
[Backport 2.32-maintenance] packaging: only override `toml11` when necessary
2025-10-09 18:04:05 +00:00
Seth Flynn
da328e6004 packaging: only override toml11 when necessary
v4.4.0 hit Nixpkgs in https://github.com/NixOS/nixpkgs/pull/442682.
Ideally we'd just use that, but this keeps the fallback behavior until
it's more widespread

(cherry picked from commit 0f016f9bf5)
2025-10-09 17:56:10 +00:00
internal-nix-ci[bot]
6b16af8c0e Merge pull request #14197 from NixOS/backport-14191-to-2.32-maintenance
[Backport 2.32-maintenance] libutil: Fix renderAuthorityAndPath unreachable for path:/ URLs
2025-10-09 00:00:28 +00:00
internal-nix-ci[bot]
010b78e0cf Merge pull request #14196 from NixOS/backport-14189-to-2.32-maintenance
[Backport 2.32-maintenance] exportReferencesGraph: Handle heterogeneous arrays
2025-10-08 23:43:51 +00:00
Sergei Zimmerman
98b7654390 libutil: Fix renderAuthorityAndPath unreachable for path:/ URLs
This was mistakenly triggered by path:/ URL, since the `//` would
correspond to 3 empty segments.

(cherry picked from commit 1d8dd77e1d)
2025-10-08 23:24:01 +00:00
Eelco Dolstra
c5799aa62c exportReferencesGraph: Handle heterogeneous arrays
This barfed with

   error: [json.exception.type_error.302] type must be string, but is array

on `nix build github:malt3/bazel-env#bazel-env` because it has a `exportReferencesGraph` with a value like `["string",...["string"]]`.

(cherry picked from commit 94f410b628)
2025-10-08 23:13:09 +00:00
Eelco Dolstra
72e3dd396c Bump version 2025-10-07 17:14:49 +02:00
Eelco Dolstra
d069633b3d Mark official release 2025-10-07 13:30:16 +02:00
19 changed files with 154 additions and 67 deletions

View File

@@ -1 +1 @@
2.32.0
2.32.1

View File

@@ -32,7 +32,7 @@
let
inherit (nixpkgs) lib;
officialRelease = false;
officialRelease = true;
linux32BitSystems = [ "i686-linux" ];
linux64BitSystems = [

View File

@@ -0,0 +1,32 @@
can_wrap_assert_fail_test_code = '''
#include <cstdlib>
#include <cassert>
int main()
{
assert(0);
}
extern "C" void * __real___assert_fail(const char *, const char *, unsigned int, const char *);
extern "C" void *
__wrap___assert_fail(const char *, const char *, unsigned int, const char *)
{
return __real___assert_fail(nullptr, nullptr, 0, nullptr);
}
'''
wrap_assert_fail_args = [ '-Wl,--wrap=__assert_fail' ]
can_wrap_assert_fail = cxx.links(
can_wrap_assert_fail_test_code,
args : wrap_assert_fail_args,
name : 'linker can wrap __assert_fail',
)
if can_wrap_assert_fail
deps_other += declare_dependency(
sources : 'wrap-assert-fail.cc',
link_args : wrap_assert_fail_args,
)
endif

View File

@@ -0,0 +1,17 @@
#include "nix/util/error.hh"
#include <cstdio>
#include <cstdlib>
#include <cinttypes>
#include <string_view>
extern "C" [[noreturn]] void __attribute__((weak))
__wrap___assert_fail(const char * assertion, const char * file, unsigned int line, const char * function)
{
char buf[512];
int n =
snprintf(buf, sizeof(buf), "Assertion '%s' failed in %s at %s:%" PRIuLEAST32, assertion, function, file, line);
if (n < 0)
nix::panic("Assertion failed and could not format error message");
nix::panic(std::string_view(buf, std::min(static_cast<int>(sizeof(buf)), n)));
}

View File

@@ -44,3 +44,5 @@ endif
# Darwin ld doesn't like "X.Y.Zpre"
nix_soversion = meson.project_version().split('pre')[0]
subdir('assert-fail')

View File

@@ -57,15 +57,20 @@ scope: {
prevAttrs.postInstall;
});
toml11 = pkgs.toml11.overrideAttrs rec {
version = "4.4.0";
src = pkgs.fetchFromGitHub {
owner = "ToruNiina";
repo = "toml11";
tag = "v${version}";
hash = "sha256-sgWKYxNT22nw376ttGsTdg0AMzOwp8QH3E8mx0BZJTQ=";
};
};
# TODO: Remove this when https://github.com/NixOS/nixpkgs/pull/442682 is included in a stable release
toml11 =
if lib.versionAtLeast pkgs.toml11.version "4.4.0" then
pkgs.toml11
else
pkgs.toml11.overrideAttrs rec {
version = "4.4.0";
src = pkgs.fetchFromGitHub {
owner = "ToruNiina";
repo = "toml11";
tag = "v${version}";
hash = "sha256-sgWKYxNT22nw376ttGsTdg0AMzOwp8QH3E8mx0BZJTQ=";
};
};
# TODO Hack until https://github.com/NixOS/nixpkgs/issues/45462 is fixed.
boost =

View File

@@ -42,7 +42,7 @@ DownloadFileResult downloadFile(
if (cached && !cached->expired)
return useCached();
FileTransferRequest request(ValidURL{url});
FileTransferRequest request(VerbatimURL{url});
request.headers = headers;
if (cached)
request.expectedETag = getStrAttr(cached->value, "etag");
@@ -107,13 +107,13 @@ DownloadFileResult downloadFile(
static DownloadTarballResult downloadTarball_(
const Settings & settings, const std::string & urlS, const Headers & headers, const std::string & displayPrefix)
{
ValidURL url = urlS;
ParsedURL url = parseURL(urlS);
// Some friendly error messages for common mistakes.
// Namely lets catch when the url is a local file path, but
// it is not in fact a tarball.
if (url.scheme() == "file") {
std::filesystem::path localPath = renderUrlPathEnsureLegal(url.path());
if (url.scheme == "file") {
std::filesystem::path localPath = renderUrlPathEnsureLegal(url.path);
if (!exists(localPath)) {
throw Error("tarball '%s' does not exist.", localPath);
}
@@ -164,7 +164,7 @@ static DownloadTarballResult downloadTarball_(
/* Note: if the download is cached, `importTarball()` will receive
no data, which causes it to import an empty tarball. */
auto archive = !url.path().empty() && hasSuffix(toLower(url.path().back()), ".zip") ? ({
auto archive = !url.path.empty() && hasSuffix(toLower(url.path.back()), ".zip") ? ({
/* In streaming mode, libarchive doesn't handle
symlinks in zip files correctly (#10649). So write
the entire file to disk so libarchive can access it
@@ -178,7 +178,7 @@ static DownloadTarballResult downloadTarball_(
}
TarArchive{path};
})
: TarArchive{*source};
: TarArchive{*source};
auto tarballCache = getTarballCache();
auto parseSink = tarballCache->getFileSystemObjectSink();
auto lastModified = unpackTarfileToSink(archive, *parseSink);

View File

@@ -37,7 +37,7 @@ static void builtinFetchurl(const BuiltinBuilderContext & ctx)
auto fetch = [&](const std::string & url) {
auto source = sinkToSource([&](Sink & sink) {
FileTransferRequest request(ValidURL{url});
FileTransferRequest request(VerbatimURL{url});
request.decompress = false;
auto decompressor = makeDecompressionSink(unpack && hasSuffix(mainUrl, ".xz") ? "xz" : "none", sink);

View File

@@ -99,6 +99,17 @@ DerivationOptions DerivationOptions::fromStructuredAttrs(
return fromStructuredAttrs(env, parsed ? &*parsed : nullptr);
}
static void flatten(const nlohmann::json & value, StringSet & res)
{
if (value.is_array())
for (auto & v : value)
flatten(v, res);
else if (value.is_string())
res.insert(value);
else
throw Error("'exportReferencesGraph' value is not an array or a string");
}
DerivationOptions
DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn)
{
@@ -219,12 +230,9 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
if (!e || !e->is_object())
return ret;
for (auto & [key, value] : getObject(*e)) {
if (value.is_array())
ret.insert_or_assign(key, value);
else if (value.is_string())
ret.insert_or_assign(key, StringSet{value});
else
throw Error("'exportReferencesGraph' value is not an array or a string");
StringSet ss;
flatten(value, ss);
ret.insert_or_assign(key, std::move(ss));
}
} else {
auto s = getOr(env, "exportReferencesGraph", "");

View File

@@ -79,7 +79,7 @@ extern const unsigned int RETRY_TIME_MS_DEFAULT;
struct FileTransferRequest
{
ValidURL uri;
VerbatimURL uri;
Headers headers;
std::string expectedETag;
bool verifyTLS = true;
@@ -93,7 +93,7 @@ struct FileTransferRequest
std::string mimeType;
std::function<void(std::string_view data)> dataCallback;
FileTransferRequest(ValidURL uri)
FileTransferRequest(VerbatimURL uri)
: uri(std::move(uri))
, parentAct(getCurActivity())
{

View File

@@ -1383,7 +1383,7 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
for (auto & link : DirectoryIterator{linksDir}) {
checkInterrupt();
auto name = link.path().filename();
printMsg(lvlTalkative, "checking contents of '%s'", name);
printMsg(lvlTalkative, "checking contents of %s", name);
PosixSourceAccessor accessor;
std::string hash = hashPath(
PosixSourceAccessor::createAtRoot(link.path()),
@@ -1391,10 +1391,10 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
HashAlgorithm::SHA256)
.first.to_string(HashFormat::Nix32, false);
if (hash != name.string()) {
printError("link '%s' was modified! expected hash '%s', got '%s'", link.path(), name, hash);
printError("link %s was modified! expected hash %s, got '%s'", link.path(), name, hash);
if (repair) {
std::filesystem::remove(link.path());
printInfo("removed link '%s'", link.path());
printInfo("removed link %s", link.path());
} else {
errors = true;
}

View File

@@ -202,7 +202,7 @@ void LocalStore::optimisePath_(
full. When that happens, it's fine to ignore it: we
just effectively disable deduplication of this
file. */
printInfo("cannot link '%s' to '%s': %s", linkPath, path, strerror(errno));
printInfo("cannot link %s to '%s': %s", linkPath, path, strerror(errno));
return;
}
@@ -216,11 +216,11 @@ void LocalStore::optimisePath_(
auto stLink = lstat(linkPath.string());
if (st.st_ino == stLink.st_ino) {
debug("'%1%' is already linked to '%2%'", path, linkPath);
debug("'%1%' is already linked to %2%", path, linkPath);
return;
}
printMsg(lvlTalkative, "linking '%1%' to '%2%'", path, linkPath);
printMsg(lvlTalkative, "linking '%1%' to %2%", path, linkPath);
/* Make the containing directory writable, but only if it's not
the store itself (we don't want or need to mess with its
@@ -245,7 +245,7 @@ void LocalStore::optimisePath_(
systems). This is likely to happen with empty files.
Just shrug and ignore. */
if (st.st_size)
printInfo("'%1%' has maximum number of links", linkPath);
printInfo("%1% has maximum number of links", linkPath);
return;
}
throw;
@@ -256,13 +256,13 @@ void LocalStore::optimisePath_(
std::filesystem::rename(tempLink, path);
} catch (std::filesystem::filesystem_error & e) {
std::filesystem::remove(tempLink);
printError("unable to unlink '%1%'", tempLink);
printError("unable to unlink %1%", tempLink);
if (e.code() == std::errc::too_many_links) {
/* Some filesystems generate too many links on the rename,
rather than on the original link. (Probably it
temporarily increases the st_nlink field before
decreasing it again.) */
debug("'%s' has reached maximum number of links", linkPath);
debug("%s has reached maximum number of links", linkPath);
return;
}
throw;

View File

@@ -868,6 +868,12 @@ TEST_P(ParsedURLPathSegmentsTest, segmentsAreCorrect)
EXPECT_EQ(encodeUrlPath(segments), testCase.path);
}
TEST_P(ParsedURLPathSegmentsTest, to_string)
{
const auto & testCase = GetParam();
EXPECT_EQ(testCase.url, parseURL(testCase.url).to_string());
}
INSTANTIATE_TEST_SUITE_P(
ParsedURL,
ParsedURLPathSegmentsTest,
@@ -886,6 +892,13 @@ INSTANTIATE_TEST_SUITE_P(
.skipEmpty = false,
.description = "empty_authority_empty_path",
},
ParsedURLPathSegmentsTestCase{
.url = "path:/",
.segments = {"", ""},
.path = "/",
.skipEmpty = false,
.description = "empty_authority_root_path",
},
ParsedURLPathSegmentsTestCase{
.url = "scheme:///",
.segments = {"", ""},

View File

@@ -6,6 +6,9 @@
#include "nix/util/error.hh"
#include "nix/util/canon-path.hh"
#include "nix/util/split.hh"
#include "nix/util/util.hh"
#include "nix/util/variant-wrapper.hh"
namespace nix {
@@ -342,8 +345,7 @@ ParsedURL fixGitURL(const std::string & url);
bool isValidSchemeName(std::string_view scheme);
/**
* Either a ParsedURL or a verbatim string, but the string must be a valid
* ParsedURL. This is necessary because in certain cases URI must be passed
* Either a ParsedURL or a verbatim string. This is necessary because in certain cases URI must be passed
* verbatim (e.g. in builtin fetchers), since those are specified by the user.
* In those cases normalizations performed by the ParsedURL might be surprising
* and undesirable, since Nix must be a universal client that has to work with
@@ -354,23 +356,23 @@ bool isValidSchemeName(std::string_view scheme);
*
* Though we perform parsing and validation for internal needs.
*/
struct ValidURL : private ParsedURL
struct VerbatimURL
{
std::optional<std::string> encoded;
using Raw = std::variant<std::string, ParsedURL>;
Raw raw;
ValidURL(std::string str)
: ParsedURL(parseURL(str, /*lenient=*/false))
, encoded(std::move(str))
VerbatimURL(std::string_view s)
: raw(std::string{s})
{
}
ValidURL(std::string_view str)
: ValidURL(std::string{str})
VerbatimURL(std::string s)
: raw(std::move(s))
{
}
ValidURL(ParsedURL parsed)
: ParsedURL{std::move(parsed)}
VerbatimURL(ParsedURL url)
: raw(std::move(url))
{
}
@@ -379,25 +381,35 @@ struct ValidURL : private ParsedURL
*/
std::string to_string() const
{
return encoded.or_else([&]() -> std::optional<std::string> { return ParsedURL::to_string(); }).value();
return std::visit(
overloaded{
[](const std::string & str) { return str; }, [](const ParsedURL & url) { return url.to_string(); }},
raw);
}
const ParsedURL & parsed() const &
const ParsedURL parsed() const
{
return *this;
return std::visit(
overloaded{
[](const std::string & str) { return parseURL(str); }, [](const ParsedURL & url) { return url; }},
raw);
}
std::string_view scheme() const &
{
return ParsedURL::scheme;
}
const auto & path() const &
{
return ParsedURL::path;
return std::visit(
overloaded{
[](std::string_view str) {
auto scheme = splitPrefixTo(str, ':');
if (!scheme)
throw BadURL("URL '%s' doesn't have a scheme", str);
return *scheme;
},
[](const ParsedURL & url) -> std::string_view { return url.scheme; }},
raw);
}
};
std::ostream & operator<<(std::ostream & os, const ValidURL & url);
std::ostream & operator<<(std::ostream & os, const VerbatimURL & url);
} // namespace nix

View File

@@ -350,7 +350,7 @@ std::string ParsedURL::renderAuthorityAndPath() const
must either be empty or begin with a slash ("/") character. */
assert(path.empty() || path.front().empty());
res += authority->to_string();
} else if (std::ranges::equal(std::views::take(path, 2), std::views::repeat("", 2))) {
} else if (std::ranges::equal(std::views::take(path, 3), std::views::repeat("", 3))) {
/* If a URI does not contain an authority component, then the path cannot begin
with two slash characters ("//") */
unreachable();
@@ -434,7 +434,7 @@ bool isValidSchemeName(std::string_view s)
return std::regex_match(s.begin(), s.end(), regex, std::regex_constants::match_default);
}
std::ostream & operator<<(std::ostream & os, const ValidURL & url)
std::ostream & operator<<(std::ostream & os, const VerbatimURL & url)
{
os << url.to_string();
return os;

View File

@@ -105,7 +105,7 @@ std::tuple<StorePath, Hash> prefetchFile(
FdSink sink(fd.get());
FileTransferRequest req(ValidURL{url});
FileTransferRequest req(VerbatimURL{url});
req.decompress = false;
getFileTransfer()->download(std::move(req), sink);
}

View File

@@ -88,8 +88,3 @@ requireDaemonNewerThan "2.20"
expected=100
if [[ -v NIX_DAEMON_PACKAGE ]]; then expected=1; fi # work around the daemon not returning a 100 status correctly
expectStderr $expected nix-build --expr '{ url }: builtins.derivation { name = "nix-cache-info"; system = "x86_64-linux"; builder = "builtin:fetchurl"; inherit url; outputHashMode = "flat"; }' --argstr url "file://$narxz" 2>&1 | grep 'must be a fixed-output or impure derivation'
requireDaemonNewerThan "2.32.0pre20250831"
expect 1 nix-build --expr 'import <nix/fetchurl.nix>' --argstr name 'name' --argstr url "file://authority.not.allowed/fetchurl.sh?a=1&a=2" --no-out-link |&
grepQuiet "error: file:// URL 'file://authority.not.allowed/fetchurl.sh?a=1&a=2' has unexpected authority 'authority.not.allowed'"

View File

@@ -82,4 +82,8 @@ mkDerivation {
"foo$" = "BAD";
exportReferencesGraph.refs = [ dep ];
exportReferencesGraph.refs2 = [
dep
[ dep ]
]; # regression test for heterogeneous arrays
}

View File

@@ -2,9 +2,8 @@
source common.sh
# 27ce722638 required some incompatible changes to the nix file, so skip this
# tests for the older versions
requireDaemonNewerThan "2.4pre20210712"
# https://github.com/NixOS/nix/pull/14189
requireDaemonNewerThan "2.33"
clearStoreIfPossible