diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03e7cca91f6c99..a5410529fcd757 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,7 +41,8 @@ release.
-26.4.0
+26.5.0
+26.4.0
26.3.1
26.3.0
26.2.0
diff --git a/README.md b/README.md
index dc175eadf5bb6a..45a01ca66fef4a 100644
--- a/README.md
+++ b/README.md
@@ -319,8 +319,6 @@ For information about the governance of the Node.js project, see
**Dario Piotrowicz** <> (he/him)
* [deokjinkim](https://github.com/deokjinkim) -
**Deokjin Kim** <> (he/him)
-* [edsadr](https://github.com/edsadr) -
- **Adrian Estrada** <> (he/him)
* [ErickWendel](https://github.com/ErickWendel) -
**Erick Wendel** <> (he/him)
* [Ethan-Arrowood](https://github.com/Ethan-Arrowood) -
@@ -513,6 +511,8 @@ For information about the governance of the Node.js project, see
**Xu Meng** <> (he/him)
* [dnlup](https://github.com/dnlup) -
**dnlup** <>
+* [edsadr](https://github.com/edsadr) -
+ **Adrian Estrada** <> (he/him)
* [eljefedelrodeodeljefe](https://github.com/eljefedelrodeodeljefe) -
**Robert Jefe Lindstaedt** <>
* [estliberitas](https://github.com/estliberitas) -
diff --git a/benchmark/webstreams/readable-async-iterator.js b/benchmark/webstreams/readable-async-iterator.js
index 8cdc4785db3dac..73ff3e1ef2b165 100644
--- a/benchmark/webstreams/readable-async-iterator.js
+++ b/benchmark/webstreams/readable-async-iterator.js
@@ -6,23 +6,40 @@ const {
const bench = common.createBenchmark(main, {
n: [1e5],
+ type: ['normal', 'bytes'],
});
-async function main({ n }) {
- const rs = new ReadableStream({
- pull: function(controller) {
- controller.enqueue(1);
- },
- });
+async function main({ n, type }) {
+ const rs = type === 'bytes' ?
+ new ReadableStream({
+ type: 'bytes',
+ pull: function(controller) {
+ controller.enqueue(new Uint8Array(1));
+ },
+ }) :
+ new ReadableStream({
+ pull: function(controller) {
+ controller.enqueue(1);
+ },
+ });
let x = 0;
bench.start();
- for await (const chunk of rs) {
- x += chunk;
- if (x > n) {
- break;
+ if (type === 'bytes') {
+ for await (const chunk of rs) {
+ x += chunk.byteLength;
+ if (x > n) {
+ break;
+ }
+ }
+ } else {
+ for await (const chunk of rs) {
+ x += chunk;
+ if (x > n) {
+ break;
+ }
}
}
// Use x to ensure V8 does not optimize away the loop as a noop.
diff --git a/common.gypi b/common.gypi
index bae09d8a5d6300..214615c3e085d9 100644
--- a/common.gypi
+++ b/common.gypi
@@ -24,6 +24,7 @@
'node_module_version%': '',
'node_with_ltcg%': '',
'node_shared_openssl%': 'false',
+ 'openssl_is_boringssl%': 'false',
'node_tag%': '',
'uv_library%': 'static_library',
@@ -41,7 +42,7 @@
# Reset this number to 0 on major V8 upgrades.
# Increment by one for each non-official patch applied to deps/v8.
- 'v8_embedder_string': '-node.24',
+ 'v8_embedder_string': '-node.25',
##### V8 defaults for Node.js #####
diff --git a/configure.py b/configure.py
index 2dc6d72d159cc4..04ffff0db81982 100755
--- a/configure.py
+++ b/configure.py
@@ -1440,6 +1440,48 @@ def get_gas_version(cc):
warn(f'Could not recognize `gas`: {gas_ret}')
return '0.0'
+def get_openssl_macros(o):
+ """Extract OpenSSL preprocessor macros from the configured headers."""
+
+ # Use the C compiler to extract preprocessor macros from OpenSSL headers.
+ # crypto.h is included because BoringSSL declares OPENSSL_IS_BORINGSSL there.
+ args = ['-E', '-dM',
+ '-include', 'openssl/opensslv.h',
+ '-include', 'openssl/crypto.h',
+ '-']
+ if not options.shared_openssl:
+ args = ['-I', 'deps/openssl/openssl/include'] + args
+ elif options.shared_openssl_includes:
+ args = ['-I', options.shared_openssl_includes] + args
+ else:
+ for dir in o['include_dirs']:
+ args = ['-I', dir] + args
+
+ proc = subprocess.Popen(
+ shlex.split(CC) + args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE
+ )
+ with proc:
+ proc.stdin.write(b'\n')
+ out = to_utf8(proc.communicate()[0])
+
+ if proc.returncode != 0:
+ warn('Failed to extract OpenSSL macros from headers')
+ return {}
+
+ macros = {}
+ for line in out.split('\n'):
+ if line.startswith('#define OPENSSL_'):
+ parts = line.split()
+ if len(parts) >= 2:
+ macro_name = parts[1]
+ macro_value = parts[2] if len(parts) >= 3 else '1'
+ macros[macro_name] = macro_value
+
+ return macros
+
def get_openssl_version(o):
"""Parse OpenSSL version from opensslv.h header file.
@@ -1449,39 +1491,7 @@ def get_openssl_version(o):
"""
try:
- # Use the C compiler to extract preprocessor macros from opensslv.h
- args = ['-E', '-dM', '-include', 'openssl/opensslv.h', '-']
- if not options.shared_openssl:
- args = ['-I', 'deps/openssl/openssl/include'] + args
- elif options.shared_openssl_includes:
- args = ['-I', options.shared_openssl_includes] + args
- else:
- for dir in o['include_dirs']:
- args = ['-I', dir] + args
-
- proc = subprocess.Popen(
- shlex.split(CC) + args,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE
- )
- with proc:
- proc.stdin.write(b'\n')
- out = to_utf8(proc.communicate()[0])
-
- if proc.returncode != 0:
- warn('Failed to extract OpenSSL version from opensslv.h header')
- return 0
-
- # Parse the macro definitions
- macros = {}
- for line in out.split('\n'):
- if line.startswith('#define OPENSSL_VERSION_'):
- parts = line.split()
- if len(parts) >= 3:
- macro_name = parts[1]
- macro_value = parts[2]
- macros[macro_name] = macro_value
+ macros = get_openssl_macros(o)
# Extract version components
major = int(macros.get('OPENSSL_VERSION_MAJOR', '0'))
@@ -1511,6 +1521,13 @@ def get_openssl_version(o):
warn(f'Failed to determine OpenSSL version from header: {e}')
return 0
+def get_openssl_is_boringssl(o):
+ try:
+ return b('OPENSSL_IS_BORINGSSL' in get_openssl_macros(o))
+ except (OSError, ValueError, subprocess.SubprocessError) as e:
+ warn(f'Failed to determine whether OpenSSL headers are BoringSSL: {e}')
+ return 'false'
+
def get_cargo_version(cargo):
try:
proc = subprocess.Popen(shlex.split(cargo) + ['--version'],
@@ -2315,6 +2332,7 @@ def without_ssl_error(option):
configure_library('openssl', o)
o['variables']['openssl_version'] = get_openssl_version(o)
+ o['variables']['openssl_is_boringssl'] = get_openssl_is_boringssl(o)
def configure_lief(o):
if options.without_lief:
diff --git a/deps/cares/CMakeLists.txt b/deps/cares/CMakeLists.txt
index a2a9c19329e09b..a9ae8fb952b72a 100644
--- a/deps/cares/CMakeLists.txt
+++ b/deps/cares/CMakeLists.txt
@@ -12,7 +12,7 @@ INCLUDE (CheckCSourceCompiles)
INCLUDE (CheckStructHasMember)
INCLUDE (CheckLibraryExists)
-PROJECT (c-ares LANGUAGES C VERSION "1.34.6" )
+PROJECT (c-ares LANGUAGES C VERSION "1.34.8" )
# Set this version before release
SET (CARES_VERSION "${PROJECT_VERSION}")
@@ -30,7 +30,7 @@ INCLUDE (GNUInstallDirs) # include this *AFTER* PROJECT(), otherwise paths are w
# For example, a version of 4:0:2 would generate output such as:
# libname.so -> libname.so.2
# libname.so.2 -> libname.so.2.2.0
-SET (CARES_LIB_VERSIONINFO "21:5:19")
+SET (CARES_LIB_VERSIONINFO "21:7:19")
OPTION (CARES_STATIC "Build as a static library" OFF)
@@ -43,6 +43,7 @@ OPTION (CARES_BUILD_TOOLS "Build tools"
OPTION (CARES_SYMBOL_HIDING "Hide private symbols in shared libraries" OFF)
OPTION (CARES_THREADS "Build with thread-safety support" ON)
OPTION (CARES_COVERAGE "Build for code coverage" OFF)
+OPTION (CARES_WERROR "Treat compiler warnings on the c-ares library as errors" OFF)
SET (CARES_RANDOM_FILE "/dev/urandom" CACHE STRING "Suitable File / Device Path for entropy, such as /dev/urandom")
# Tests require static to be enabled on Windows to be able to access otherwise hidden symbols
@@ -54,6 +55,30 @@ ENDIF ()
INCLUDE (EnableWarnings)
+# Optionally treat warnings as errors for the c-ares library itself. This is
+# applied per-target (see cares_set_werror() usage) rather than globally so it
+# does not affect the test/fuzz harnesses (which carry their own warnings) or
+# CMake's own compiler feature checks. Enabled in CI only on toolchains known
+# to be free of spurious warnings at our high warning levels.
+FUNCTION (cares_set_werror target)
+ IF (NOT CARES_WERROR)
+ RETURN ()
+ ENDIF ()
+ IF (MSVC)
+ TARGET_COMPILE_OPTIONS (${target} PRIVATE /WX)
+ ELSE ()
+ TARGET_COMPILE_OPTIONS (${target} PRIVATE -Werror)
+ # Modern libc headers (e.g. glibc ) use the C11 _Generic
+ # keyword for const-correctness of strchr()/memchr()/etc. Clang flags
+ # that as a C11 extension under our C90 + -Wpedantic build even though it
+ # originates in a system header at our call sites. It is not something
+ # we can fix in our own code, so warn but do not fail on it.
+ IF (CMAKE_C_COMPILER_ID MATCHES "Clang")
+ TARGET_COMPILE_OPTIONS (${target} PRIVATE -Wno-error=c11-extensions)
+ ENDIF ()
+ ENDIF ()
+ENDFUNCTION ()
+
IF (MSVC)
# allow linking against the static runtime library in msvc
OPTION (CARES_MSVC_STATIC_RUNTIME "Link against the static runtime library" OFF)
diff --git a/deps/cares/README.md b/deps/cares/README.md
index 6566c9fe6aa18e..7e818df8fa5b12 100644
--- a/deps/cares/README.md
+++ b/deps/cares/README.md
@@ -1,7 +1,7 @@
# [](https://c-ares.org/)
-[](https://cirrus-ci.com/github/c-ares/c-ares)
-[](https://ci.appveyor.com/project/c-ares/c-ares/branch/main)
+[](https://github.com/c-ares/c-ares/actions/workflows/ubuntu-latest.yml)
+[](https://github.com/c-ares/c-ares/actions/workflows/windows.yml)
[](https://coveralls.io/github/c-ares/c-ares?branch=main)
[](https://bestpractices.coreinfrastructure.org/projects/291)
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:c-ares)
diff --git a/deps/cares/RELEASE-NOTES.md b/deps/cares/RELEASE-NOTES.md
index ded75001b54fa4..93cc10fc74bd8e 100644
--- a/deps/cares/RELEASE-NOTES.md
+++ b/deps/cares/RELEASE-NOTES.md
@@ -1,3 +1,113 @@
+## c-ares version 1.34.8 - July 7 2026
+
+This is a bugfix release.
+
+Bugfixes:
+* Revert "Mark parameters in callbacks as const" which shipped in 1.34.7.
+ Changing the parameter types of the `ares_callback`, `ares_host_callback`,
+ and `ares_nameinfo_callback` function pointer typedefs was an unintended
+ API break: existing applications with the historical non-const callback
+ signatures no longer compiled, particularly in C++ where function pointer
+ types must match exactly. The read-only nature of the callback parameters
+ is now documented instead.
+ [PR #1244](https://github.com/c-ares/c-ares/pull/1244)
+
+Thanks go to these friendly people for their efforts and contributions for this
+release:
+
+* Brad House (@bradh352)
+
+
+## c-ares version 1.34.7 - July 6 2026
+
+This is a security release.
+
+Security:
+* CVE-2026-33630. Use-after-free / double-free in c-ares' query-completion
+ handling, remotely triggerable via ares_getaddrinfo() over TCP. Please see
+ https://github.com/c-ares/c-ares/security/advisories/GHSA-6wfj-rwm7-3542
+* CPU-exhaustion denial of service via unbounded DNS name compression pointer
+ chains. Please see
+ https://github.com/c-ares/c-ares/security/advisories/GHSA-pjmc-gx33-gc76
+* Memory-amplification denial of service via unvalidated DNS header record
+ counts. Please see
+ https://github.com/c-ares/c-ares/security/advisories/GHSA-jv8r-gqr9-68wj
+
+Changes:
+* `ares_getaddrinfo()`: handle a NULL node per POSIX and implement
+ `ARES_AI_PASSIVE`. [PR #1186](https://github.com/c-ares/c-ares/pull/1186)
+* Mark parameters in callbacks as const.
+ [PR #1060](https://github.com/c-ares/c-ares/pull/1060)
+* Correct `ARES_CLASS_HESOID` misspelling to `ARES_CLASS_HESIOD`.
+ [PR #1092](https://github.com/c-ares/c-ares/pull/1092)
+
+Bugfixes:
+* Fix sticky server recovery when all servers have failures. [PR #1192](https://github.com/c-ares/c-ares/pull/1192)
+* Fix UDP socket exhaustion regression: retire connections per-connection, not via server failure count. [PR #1197](https://github.com/c-ares/c-ares/pull/1197)
+* Guard DNS record binary length overflow. [PR #1168](https://github.com/c-ares/c-ares/pull/1168)
+* Prevent integer overflow in allocation size calculations. [PR #1147](https://github.com/c-ares/c-ares/pull/1147)
+* Prevent overflow in ares_array allocation size calculations. [PR #1117](https://github.com/c-ares/c-ares/pull/1117)
+* Prevent integer overflow in buffer size calculation. [PR #1116](https://github.com/c-ares/c-ares/pull/1116)
+* Add overflow checks to ares_buf_ensure_space(). [PR #1094](https://github.com/c-ares/c-ares/pull/1094)
+* Skip name compression offsets beyond the 14-bit pointer limit. [PR #1159](https://github.com/c-ares/c-ares/pull/1159)
+* ares_dns_parse: reject name compression in RDATA where not permitted (RFC 3597). [PR #1190](https://github.com/c-ares/c-ares/pull/1190)
+* ares_dns_parse: reject responses with more than one OPT record (RFC 6891). [PR #1189](https://github.com/c-ares/c-ares/pull/1189)
+* Discard oversized UDP datagrams instead of truncating the length frame. [PR #1161](https://github.com/c-ares/c-ares/pull/1161)
+* Route numeric config parsing through range-checked ares_str_parse_uint. [PR #1158](https://github.com/c-ares/c-ares/pull/1158)
+* Replace atoi-based port parsing with validated helper. [PR #1097](https://github.com/c-ares/c-ares/pull/1097)
+* Defer TCP connection error handling until DNS responses are parsed. [PR #1138](https://github.com/c-ares/c-ares/pull/1138)
+* Prevent undefined-behavior left shift in ares_calc_query_timeout(). [PR #1151](https://github.com/c-ares/c-ares/pull/1151)
+* Use unsigned type for ares_round_up_pow2_u64 to avoid undefined behavior. [PR #1107](https://github.com/c-ares/c-ares/pull/1107)
+* Fix undefined behavior in ares_buf_replace() pointer arithmetic. [PR #1099](https://github.com/c-ares/c-ares/pull/1099)
+* ares_array: reset offset when array becomes empty. [PR #1165](https://github.com/c-ares/c-ares/pull/1165)
+* ares_iface_ips: add ARES_IFACE_IP_NONE zero enum value. [PR #1187](https://github.com/c-ares/c-ares/pull/1187)
+* ares_sysconfig_files: recognize AIX netsvc.conf bind4/local4 tokens. [PR #1188](https://github.com/c-ares/c-ares/pull/1188)
+* Use safe string construction in Windows sysconfig join path. [PR #1143](https://github.com/c-ares/c-ares/pull/1143)
+* Fix zero-length RAW_RR losing type metadata during parsing. [PR #1129](https://github.com/c-ares/c-ares/pull/1129)
+* Fix RAW_RR type tostr/fromstr roundtrip mismatch. [PR #1123](https://github.com/c-ares/c-ares/pull/1123)
+* Fix NULL dereference for ifa netmask. [PR #1120](https://github.com/c-ares/c-ares/pull/1120)
+* adig: fix negated option prefix parsing. [PR #1135](https://github.com/c-ares/c-ares/pull/1135)
+* Use UnregisterWaitEx to prevent use-after-free on Win32. [PR #1111](https://github.com/c-ares/c-ares/pull/1111)
+* Add NULL check after ares_malloc_zero in Windows UTF8 conversion. [PR #1121](https://github.com/c-ares/c-ares/pull/1121)
+* Fix NULL dereference after ares_malloc_zero in Win32 IOCP event add. [PR #1132](https://github.com/c-ares/c-ares/pull/1132)
+* Fix microsecond overflow in ares_queue_wait_empty timeout. [PR #1122](https://github.com/c-ares/c-ares/pull/1122)
+* Fix NULL dereference in ares_buf_replace() on NULL buf. [PR #1124](https://github.com/c-ares/c-ares/pull/1124)
+* Initialize *read_bytes in ares_socket_recvfrom(). [PR #1125](https://github.com/c-ares/c-ares/pull/1125)
+* Fix memory leak of binbuf in ares_buf_parse_dns_binstr_int. [PR #1126](https://github.com/c-ares/c-ares/pull/1126)
+* Fix memory leak of qcache entry on key allocation failure. [PR #1127](https://github.com/c-ares/c-ares/pull/1127)
+* Fix memory leak of buf in ares_dns_multistring_combined() on OOM. [PR #1110](https://github.com/c-ares/c-ares/pull/1110)
+* Fix memory leaks on error paths in two functions. [PR #1109](https://github.com/c-ares/c-ares/pull/1109)
+* Fix memory leak of buckets in ares_htable_dict_keys() error path. [PR #1108](https://github.com/c-ares/c-ares/pull/1108)
+* Fix memory leak of bucket->key in ares_htable_dict_insert error path. [PR #1105](https://github.com/c-ares/c-ares/pull/1105)
+* Fix additional memory leaks. [PR #1091](https://github.com/c-ares/c-ares/pull/1091) [PR #1078](https://github.com/c-ares/c-ares/pull/1078)
+* Prevent corrupt addrinfo nodes on sockaddr allocation failure. [PR #1112](https://github.com/c-ares/c-ares/pull/1112)
+* Fix two logic bugs in DNS cookie handling. [PR #1103](https://github.com/c-ares/c-ares/pull/1103)
+* Fix linked list INSERT_BEFORE corruption and array insertdata_first ordering. [PR #1101](https://github.com/c-ares/c-ares/pull/1101)
+* Fix HASH_IDX macro missing parentheses. [PR #1128](https://github.com/c-ares/c-ares/pull/1128)
+* Fix malloc(0) in ares_htable_all_buckets on empty table. [PR #1131](https://github.com/c-ares/c-ares/pull/1131)
+* Fix wrong sizeof in QNX confstr() call truncating domain names. [PR #1130](https://github.com/c-ares/c-ares/pull/1130)
+* Clear probe pending flag after timeout. [PR #1059](https://github.com/c-ares/c-ares/pull/1059)
+* Fix incorrect check for empty wide string. [PR #1064](https://github.com/c-ares/c-ares/pull/1064)
+* Handle strdup failure. [PR #1077](https://github.com/c-ares/c-ares/pull/1077)
+* doc: reference ares_free_string(), not ares_free(). [PR #1084](https://github.com/c-ares/c-ares/pull/1084)
+
+Thanks go to these friendly people for their efforts and contributions for this
+release:
+
+* (@Alb3e3)
+* Brad House (@bradh352)
+* (@dankmeme01)
+* David Hotham (@dimbleby)
+* Tom Flynn (@Flynnzaa)
+* (@jmestwa-coder)
+* (@kodareef5)
+* Kaixuan Li (@MarkLee131)
+* (@lifenjoiner)
+* (@metsw24-max)
+* Song Li (@SongTonyLi)
+* (@uwezkhan)
+
+
## c-ares version 1.34.6 - December 8 2025
This is a security release.
diff --git a/deps/cares/aminclude_static.am b/deps/cares/aminclude_static.am
index 07239563e6a6d2..9a00ba789172eb 100644
--- a/deps/cares/aminclude_static.am
+++ b/deps/cares/aminclude_static.am
@@ -1,6 +1,6 @@
# aminclude_static.am generated automatically by Autoconf
-# from AX_AM_MACROS_STATIC on Mon Dec 8 16:21:41 UTC 2025
+# from AX_AM_MACROS_STATIC on Tue Jul 7 16:14:13 UTC 2026
# Code coverage
diff --git a/deps/cares/configure b/deps/cares/configure
index f91e2c956862e7..c2318a1c3bf07a 100755
--- a/deps/cares/configure
+++ b/deps/cares/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for c-ares 1.34.6.
+# Generated by GNU Autoconf 2.71 for c-ares 1.34.8.
#
# Report bugs to .
#
@@ -621,8 +621,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='c-ares'
PACKAGE_TARNAME='c-ares'
-PACKAGE_VERSION='1.34.6'
-PACKAGE_STRING='c-ares 1.34.6'
+PACKAGE_VERSION='1.34.8'
+PACKAGE_STRING='c-ares 1.34.8'
PACKAGE_BUGREPORT='c-ares mailing list: http://lists.haxx.se/listinfo/c-ares'
PACKAGE_URL=''
@@ -1430,7 +1430,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures c-ares 1.34.6 to adapt to many kinds of systems.
+\`configure' configures c-ares 1.34.8 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1501,7 +1501,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of c-ares 1.34.6:";;
+ short | recursive ) echo "Configuration of c-ares 1.34.8:";;
esac
cat <<\_ACEOF
@@ -1647,7 +1647,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-c-ares configure 1.34.6
+c-ares configure 1.34.8
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -2271,7 +2271,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by c-ares $as_me 1.34.6, which was
+It was created by c-ares $as_me 1.34.8, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -3245,7 +3245,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
-CARES_VERSION_INFO="21:5:19"
+CARES_VERSION_INFO="21:7:19"
@@ -6818,7 +6818,7 @@ fi
# Define the identity of the package.
PACKAGE='c-ares'
- VERSION='1.34.6'
+ VERSION='1.34.8'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -28238,7 +28238,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by c-ares $as_me 1.34.6, which was
+This file was extended by c-ares $as_me 1.34.8, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -28306,7 +28306,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
-c-ares config.status 1.34.6
+c-ares config.status 1.34.8
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"
diff --git a/deps/cares/configure.ac b/deps/cares/configure.ac
index 744a99be56ab2f..637dfb153b1f72 100644
--- a/deps/cares/configure.ac
+++ b/deps/cares/configure.ac
@@ -2,10 +2,10 @@ dnl Copyright (C) The c-ares project and its contributors
dnl SPDX-License-Identifier: MIT
AC_PREREQ([2.69])
-AC_INIT([c-ares], [1.34.6],
+AC_INIT([c-ares], [1.34.8],
[c-ares mailing list: http://lists.haxx.se/listinfo/c-ares])
-CARES_VERSION_INFO="21:5:19"
+CARES_VERSION_INFO="21:7:19"
dnl This flag accepts an argument of the form current[:revision[:age]]. So,
dnl passing -version-info 3:12:1 sets current to 3, revision to 12, and age to
dnl 1.
diff --git a/deps/cares/docs/ares_dns_record.3 b/deps/cares/docs/ares_dns_record.3
index 47ca95b057a246..723f9ab4b98c6a 100644
--- a/deps/cares/docs/ares_dns_record.3
+++ b/deps/cares/docs/ares_dns_record.3
@@ -140,7 +140,7 @@ DNS Classes for requests and responses:
.B ARES_CLASS_CHAOS
- CHAOS
.br
-.B ARES_CLASS_HESOID
+.B ARES_CLASS_HESIOD
- Hesoid [Dyer 87]
.br
.B ARES_CLASS_NONE
diff --git a/deps/cares/docs/ares_getaddrinfo.3 b/deps/cares/docs/ares_getaddrinfo.3
index 5544ce20224eba..661381e0926e8c 100644
--- a/deps/cares/docs/ares_getaddrinfo.3
+++ b/deps/cares/docs/ares_getaddrinfo.3
@@ -27,6 +27,19 @@ The
and
.I service
parameters give the hostname and service as NULL-terminated C strings.
+Either
+.I name
+or
+.I service
+may be NULL, but not both. If
+.I name
+is NULL, the returned addresses are synthesized from
+.IR service :
+the wildcard address (0.0.0.0 or ::) is returned when
+.B ARES_AI_PASSIVE
+is set in
+.IR ai_flags ,
+otherwise the loopback address (127.0.0.1 or ::1) is returned.
The
.I hints
parameter is an
@@ -63,6 +76,16 @@ If this option is set
.I service
field will be treated as a numeric value.
.TP 19
+.B ARES_AI_PASSIVE
+If this option is set and
+.I name
+is NULL, the returned addresses will use the wildcard address (0.0.0.0 or ::),
+suitable for
+.BR bind (2)ing
+a socket that will accept connections. If not set and
+.I name
+is NULL, the loopback address (127.0.0.1 or ::1) is returned instead.
+.TP 19
.B ARES_AI_CANONNAME
The ares_addrinfo structure will return a canonical names list.
.TP 19
diff --git a/deps/cares/docs/ares_gethostbyaddr.3 b/deps/cares/docs/ares_gethostbyaddr.3
index cc4092285c0168..f5b5f0864fcff1 100644
--- a/deps/cares/docs/ares_gethostbyaddr.3
+++ b/deps/cares/docs/ares_gethostbyaddr.3
@@ -80,7 +80,16 @@ points to a
containing the name of the host returned by the query. The callback
need not and should not attempt to free the memory pointed to by
.IR hostent ;
-the ares library will free it when the callback returns. If the query
+the ares library will free it when the callback returns. The callback
+.B must not
+modify the
+.B struct hostent
+or retain a reference to it after returning; it is owned by c-ares and is only
+valid for the duration of the callback. The parameter is intentionally not
+declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
+If the query
did not complete successfully,
.I hostent
will be
diff --git a/deps/cares/docs/ares_gethostbyname.3 b/deps/cares/docs/ares_gethostbyname.3
index 06d075ca6c5136..40d340343114f1 100644
--- a/deps/cares/docs/ares_gethostbyname.3
+++ b/deps/cares/docs/ares_gethostbyname.3
@@ -88,7 +88,16 @@ points to a
containing the name of the host returned by the query. The callback
need not and should not attempt to free the memory pointed to by
.IR hostent ;
-the ares library will free it when the callback returns. If the query
+the ares library will free it when the callback returns. The callback
+.B must not
+modify the
+.B struct hostent
+or retain a reference to it after returning; it is owned by c-ares and is only
+valid for the duration of the callback. The parameter is intentionally not
+declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
+If the query
did not complete successfully,
.I hostent
will be
diff --git a/deps/cares/docs/ares_getnameinfo.3 b/deps/cares/docs/ares_getnameinfo.3
index 66b04f9efc11a7..800b57b31f3f4c 100644
--- a/deps/cares/docs/ares_getnameinfo.3
+++ b/deps/cares/docs/ares_getnameinfo.3
@@ -24,7 +24,7 @@ function is defined for protocol-independent address translation. The function
is a combination of \fIares_gethostbyaddr(3)\fP and \fIgetservbyport(3)\fP. The function will
translate the address either by executing a host query on the name service channel
identified by
-.IR channel
+.IR channel
or it will attempt to resolve it locally if possible.
The parameters
.I sa
@@ -68,8 +68,8 @@ A hostname lookup is being requested.
A service name lookup is being requested.
.PP
When the query
-is complete or has
-failed, the ares library will invoke \fIcallback\fP. Completion or failure of
+is complete or has
+failed, the ares library will invoke \fIcallback\fP. Completion or failure of
the query may happen immediately, or may happen during a later call to
\fIares_process(3)\fP, \fIares_destroy(3)\fP or \fIares_cancel(3)\fP.
.PP
@@ -129,19 +129,31 @@ given request.
.PP
On successful completion of the query, the callback argument
.I node
-contains a string representing the hostname (assuming
+contains a string representing the hostname (assuming
.B ARES_NI_LOOKUPHOST
-was specified). Additionally,
+was specified). Additionally,
.I service
contains a string representing the service name (assuming
.B ARES_NI_LOOKUPSERVICE
was specified).
If the query did not complete successfully, or one of the values
-was not requested,
+was not requested,
.I node
or
.I service
-will be
+will be
.BR NULL .
+.PP
+The
+.I node
+and
+.I service
+strings are owned by the c-ares library and are only valid for the duration of
+the callback. The callback
+.B must not
+modify or free them, and must not retain references to them after returning;
+treat them as read-only. The parameters are intentionally not declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
.SH SEE ALSO
.BR ares_process (3),
diff --git a/deps/cares/docs/ares_query.3 b/deps/cares/docs/ares_query.3
index 3aa428b00bb813..401d700a44ccd9 100644
--- a/deps/cares/docs/ares_query.3
+++ b/deps/cares/docs/ares_query.3
@@ -146,6 +146,16 @@ or
.I abuf
will be non-NULL, otherwise they will be NULL.
+The
+.I abuf
+buffer is owned by the c-ares library and is only valid for the duration of
+the callback. The callback
+.B must not
+modify or free it, and must not retain a reference to it after returning; treat
+it as read-only. The parameter is intentionally not declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
+
.SH AVAILABILITY
\fBares_query_dnsrec(3)\fP was introduced in c-ares 1.28.0.
diff --git a/deps/cares/docs/ares_search.3 b/deps/cares/docs/ares_search.3
index 66791b47e908fb..12d1dede09e874 100644
--- a/deps/cares/docs/ares_search.3
+++ b/deps/cares/docs/ares_search.3
@@ -152,6 +152,15 @@ will usually be NULL and
will usually be 0, but in some cases an unsuccessful query result may
be placed in
.IR abuf .
+The
+.I abuf
+buffer is owned by the c-ares library and is only valid for the duration of
+the callback. The callback
+.B must not
+modify or free it, and must not retain a reference to it after returning; treat
+it as read-only. The parameter is intentionally not declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
The \fIares_search_dnsrec(3)\fP function behaves identically to
\fIares_search(3)\fP, but takes an initialized and filled DNS record object to
diff --git a/deps/cares/docs/ares_send.3 b/deps/cares/docs/ares_send.3
index df3e3bbe4136b0..cf669a001db295 100644
--- a/deps/cares/docs/ares_send.3
+++ b/deps/cares/docs/ares_send.3
@@ -131,6 +131,16 @@ and
.IR alen
for \fIares_send(3)\fP will be non-NULL.
+The
+.I abuf
+buffer is owned by the c-ares library and is only valid for the duration of
+the callback. The callback
+.B must not
+modify or free it, and must not retain a reference to it after returning; treat
+it as read-only. The parameter is intentionally not declared
+.B const
+solely to preserve API/ABI compatibility with existing applications.
+
Unless the flag
.B ARES_FLAG_NOCHECKRESP
was set at channel initialization time, \fIares_send_dnsrec(3)\fP and
diff --git a/deps/cares/include/ares.h b/deps/cares/include/ares.h
index 7fe3ec78f4e651..1e38463a044a22 100644
--- a/deps/cares/include/ares.h
+++ b/deps/cares/include/ares.h
@@ -434,6 +434,14 @@ struct ares_addr {
/* DNS record parser, writer, and helpers */
#include "ares_dns_record.h"
+/* IMPORTANT: the pointer parameters below (abuf, hostent, node, service) are
+ * intentionally NOT const. Marking them const changes the function-pointer
+ * type and breaks API/ABI compatibility for existing C and (especially) C++
+ * applications that declare their callbacks with the historical non-const
+ * signature. This was tried in PR #1060 and reverted for that reason. These
+ * buffers are owned by c-ares and MUST be treated as read-only by the callback
+ * (do not modify or free them); that contract is documented in the man pages,
+ * not enforced via const, to preserve compatibility. Do not re-add const. */
typedef void (*ares_callback)(void *arg, int status, int timeouts,
unsigned char *abuf, int alen);
diff --git a/deps/cares/include/ares_dns_record.h b/deps/cares/include/ares_dns_record.h
index cec9f47f63d8f5..1e47a78e143c4a 100644
--- a/deps/cares/include/ares_dns_record.h
+++ b/deps/cares/include/ares_dns_record.h
@@ -76,7 +76,8 @@ typedef enum {
typedef enum {
ARES_CLASS_IN = 1, /*!< Internet */
ARES_CLASS_CHAOS = 3, /*!< CHAOS */
- ARES_CLASS_HESOID = 4, /*!< Hesoid [Dyer 87] */
+ ARES_CLASS_HESIOD = 4, /*!< Hesiod [Dyer 87] */
+ ARES_CLASS_HESOID = 4, /*!< typo from older c-ares version for Hesiod */
ARES_CLASS_NONE = 254, /*!< RFC 2136 */
ARES_CLASS_ANY = 255 /*!< Any class (requests only) */
} ares_dns_class_t;
@@ -1092,7 +1093,7 @@ CARES_EXTERN ares_status_t ares_dns_parse(const unsigned char *buf,
*
* \param[in] dnsrec Pointer to initialized and filled DNS record object.
* \param[out] buf Pointer passed by reference to be filled in with with
- * DNS message. Must be ares_free()'d by caller.
+ * DNS message. Must be ares_free_string()'d by caller.
* \param[out] buf_len Length of returned buffer containing DNS message.
* \return ARES_SUCCESS on success
*/
diff --git a/deps/cares/include/ares_version.h b/deps/cares/include/ares_version.h
index 006029c12dfe91..f836d5f9186d1c 100644
--- a/deps/cares/include/ares_version.h
+++ b/deps/cares/include/ares_version.h
@@ -32,8 +32,8 @@
#define ARES_VERSION_MAJOR 1
#define ARES_VERSION_MINOR 34
-#define ARES_VERSION_PATCH 6
-#define ARES_VERSION_STR "1.34.6"
+#define ARES_VERSION_PATCH 8
+#define ARES_VERSION_STR "1.34.8"
/* NOTE: We cannot make the version string a C preprocessor stringify operation
* due to assumptions made by integrators that aren't properly using
diff --git a/deps/cares/src/lib/CMakeLists.txt b/deps/cares/src/lib/CMakeLists.txt
index bdb2690fb673f9..a4c1e73f1bd5ad 100644
--- a/deps/cares/src/lib/CMakeLists.txt
+++ b/deps/cares/src/lib/CMakeLists.txt
@@ -66,6 +66,8 @@ IF (CARES_SHARED)
TARGET_COMPILE_DEFINITIONS (${PROJECT_NAME} PRIVATE HAVE_CONFIG_H=1 CARES_BUILDING_LIBRARY)
+ cares_set_werror (${PROJECT_NAME})
+
TARGET_LINK_LIBRARIES (${PROJECT_NAME}
PUBLIC ${CARES_DEPENDENT_LIBS}
PRIVATE ${CMAKE_THREAD_LIBS_INIT}
@@ -136,6 +138,8 @@ IF (CARES_STATIC)
TARGET_COMPILE_DEFINITIONS (${LIBNAME} PRIVATE HAVE_CONFIG_H=1 CARES_BUILDING_LIBRARY)
+ cares_set_werror (${LIBNAME})
+
# Only matters on Windows
IF (WIN32 OR CYGWIN)
TARGET_COMPILE_DEFINITIONS (${LIBNAME} PUBLIC CARES_STATICLIB)
diff --git a/deps/cares/src/lib/Makefile.in b/deps/cares/src/lib/Makefile.in
index e92732eaf72c8d..a9630300c0848b 100644
--- a/deps/cares/src/lib/Makefile.in
+++ b/deps/cares/src/lib/Makefile.in
@@ -15,7 +15,7 @@
@SET_MAKE@
# aminclude_static.am generated automatically by Autoconf
-# from AX_AM_MACROS_STATIC on Mon Dec 8 16:21:41 UTC 2025
+# from AX_AM_MACROS_STATIC on Tue Jul 7 16:14:13 UTC 2026
# Copyright (C) The c-ares project and its contributors
# SPDX-License-Identifier: MIT
diff --git a/deps/cares/src/lib/ares_addrinfo_localhost.c b/deps/cares/src/lib/ares_addrinfo_localhost.c
index 2abb0c48a6f601..b65cf48a2f6a75 100644
--- a/deps/cares/src/lib/ares_addrinfo_localhost.c
+++ b/deps/cares/src/lib/ares_addrinfo_localhost.c
@@ -67,13 +67,8 @@ ares_status_t ares_append_ai_node(int aftype, unsigned short port,
struct ares_addrinfo_node **nodes)
{
struct ares_addrinfo_node *node;
-
- node = ares_append_addrinfo_node(nodes);
- if (!node) {
- return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
- }
-
- memset(node, 0, sizeof(*node));
+ struct sockaddr *sa;
+ socklen_t salen;
if (aftype == AF_INET) {
struct sockaddr_in *sin = ares_malloc(sizeof(*sin));
@@ -86,14 +81,9 @@ ares_status_t ares_append_ai_node(int aftype, unsigned short port,
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
- node->ai_addr = (struct sockaddr *)sin;
- node->ai_family = AF_INET;
- node->ai_addrlen = sizeof(*sin);
- node->ai_addr = (struct sockaddr *)sin;
- node->ai_ttl = (int)ttl;
- }
-
- if (aftype == AF_INET6) {
+ sa = (struct sockaddr *)sin;
+ salen = sizeof(*sin);
+ } else if (aftype == AF_INET6) {
struct sockaddr_in6 *sin6 = ares_malloc(sizeof(*sin6));
if (!sin6) {
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
@@ -104,13 +94,23 @@ ares_status_t ares_append_ai_node(int aftype, unsigned short port,
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons(port);
- node->ai_addr = (struct sockaddr *)sin6;
- node->ai_family = AF_INET6;
- node->ai_addrlen = sizeof(*sin6);
- node->ai_addr = (struct sockaddr *)sin6;
- node->ai_ttl = (int)ttl;
+ sa = (struct sockaddr *)sin6;
+ salen = sizeof(*sin6);
+ } else {
+ return ARES_EFORMERR;
+ }
+
+ node = ares_append_addrinfo_node(nodes);
+ if (!node) {
+ ares_free(sa); /* LCOV_EXCL_LINE: OutOfMemory */
+ return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
+ node->ai_addr = sa;
+ node->ai_family = aftype;
+ node->ai_addrlen = salen;
+ node->ai_ttl = (int)ttl;
+
return ARES_SUCCESS;
}
diff --git a/deps/cares/src/lib/ares_close_sockets.c b/deps/cares/src/lib/ares_close_sockets.c
index 347f43e6fcd88d..7bb72c31ec1b2a 100644
--- a/deps/cares/src/lib/ares_close_sockets.c
+++ b/deps/cares/src/lib/ares_close_sockets.c
@@ -112,11 +112,11 @@ void ares_check_cleanup_conns(const ares_channel_t *channel)
do_cleanup = ARES_TRUE;
}
- /* If the associated server has failures, close it out. Resetting the
- * connection (and specifically the source port number) can help resolve
- * situations where packets are being dropped.
+ /* If the connection has been retired for new queries, close it out once
+ * idle. Resetting the connection (and specifically the source port
+ * number) can help resolve situations where packets are being dropped.
*/
- if (conn->server->consec_failures > 0) {
+ if (conn->flags & ARES_CONN_FLAG_NONEW) {
do_cleanup = ARES_TRUE;
}
diff --git a/deps/cares/src/lib/ares_conn.h b/deps/cares/src/lib/ares_conn.h
index 16ecefdd19fa67..c332de14b135d4 100644
--- a/deps/cares/src/lib/ares_conn.h
+++ b/deps/cares/src/lib/ares_conn.h
@@ -43,7 +43,13 @@ typedef enum {
ARES_CONN_FLAG_TFO = 1 << 1,
/*! TCP Fast Open has not yet sent its first packet. Gets unset on first
* write to a connection */
- ARES_CONN_FLAG_TFO_INITIAL = 1 << 2
+ ARES_CONN_FLAG_TFO_INITIAL = 1 << 2,
+ /*! Connection has been retired: it must not be handed any NEW queries (e.g.
+ * it experienced a query timeout, suggesting packets are being dropped on
+ * it). Its in-flight queries continue to drain and it is cleaned up once
+ * idle. This is a per-connection signal so that a transient failure does
+ * not evict otherwise-healthy connections to the same server. */
+ ARES_CONN_FLAG_NONEW = 1 << 3
} ares_conn_flags_t;
typedef enum {
diff --git a/deps/cares/src/lib/ares_cookie.c b/deps/cares/src/lib/ares_cookie.c
index 509e12050e0c00..25960db6022fcc 100644
--- a/deps/cares/src/lib/ares_cookie.c
+++ b/deps/cares/src/lib/ares_cookie.c
@@ -217,7 +217,7 @@ static const unsigned char *
static ares_bool_t timeval_is_set(const ares_timeval_t *tv)
{
- if (tv->sec != 0 && tv->usec != 0) {
+ if (tv->sec != 0 || tv->usec != 0) {
return ARES_TRUE;
}
return ARES_FALSE;
@@ -324,7 +324,7 @@ ares_status_t ares_cookie_apply(ares_dns_record_t *dnsrec, ares_conn_t *conn,
if (cookie->state == ARES_COOKIE_UNSUPPORTED) {
/* If timer hasn't expired, just delete any possible cookie and return */
if (!timeval_expired(&cookie->unsupported_ts, now,
- COOKIE_REGRESSION_TIMEOUT_MS)) {
+ COOKIE_UNSUPPORTED_TIMEOUT_MS)) {
ares_dns_rr_del_opt_byid(rr, ARES_RR_OPT_OPTIONS, ARES_OPT_PARAM_COOKIE);
return ARES_SUCCESS;
}
diff --git a/deps/cares/src/lib/ares_getaddrinfo.c b/deps/cares/src/lib/ares_getaddrinfo.c
index 6009de36a3d02d..6d9e21af4bb9a1 100644
--- a/deps/cares/src/lib/ares_getaddrinfo.c
+++ b/deps/cares/src/lib/ares_getaddrinfo.c
@@ -242,6 +242,11 @@ static ares_bool_t fake_addrinfo(const char *name, unsigned short port,
ares_status_t status = ARES_SUCCESS;
ares_bool_t result = ARES_FALSE;
int family = hints->ai_family;
+
+ if (name == NULL) {
+ return ARES_FALSE; /* LCOV_EXCL_LINE: DefensiveCoding */
+ }
+
if (family == AF_INET || family == AF_INET6 || family == AF_UNSPEC) {
/* It only looks like an IP address if it's all numbers and dots. */
size_t numdots = 0;
@@ -268,6 +273,7 @@ static ares_bool_t fake_addrinfo(const char *name, unsigned short port,
if (result) {
status = ares_append_ai_node(AF_INET, port, 0, &addr4, &ai->nodes);
if (status != ARES_SUCCESS) {
+ ares_freeaddrinfo(ai);
callback(arg, (int)status, 0, NULL); /* LCOV_EXCL_LINE: OutOfMemory */
return ARES_TRUE; /* LCOV_EXCL_LINE: OutOfMemory */
}
@@ -282,6 +288,7 @@ static ares_bool_t fake_addrinfo(const char *name, unsigned short port,
if (result) {
status = ares_append_ai_node(AF_INET6, port, 0, &addr6, &ai->nodes);
if (status != ARES_SUCCESS) {
+ ares_freeaddrinfo(ai);
callback(arg, (int)status, 0, NULL); /* LCOV_EXCL_LINE: OutOfMemory */
return ARES_TRUE; /* LCOV_EXCL_LINE: OutOfMemory */
}
@@ -575,21 +582,55 @@ static void host_callback(void *arg, ares_status_t status, size_t timeouts,
/* at this point we keep on waiting for the next query to finish */
}
-static ares_bool_t numeric_service_to_port(const char *service,
- unsigned short *port)
+/* Per POSIX getaddrinfo(), when no node/hostname is provided the returned
+ * addresses are synthesized from the service: the wildcard address when
+ * ARES_AI_PASSIVE is set (suitable for bind()ing a listening socket),
+ * otherwise the loopback address (suitable for connect()ing to a peer on the
+ * local host). */
+static ares_status_t
+ ares_append_nulladdr(unsigned short port,
+ const struct ares_addrinfo_hints *hints,
+ struct ares_addrinfo *ai)
{
- char *end;
- unsigned long val;
+ int family = hints->ai_family;
+ ares_bool_t passive = ARES_FALSE;
+ ares_status_t status;
+ struct ares_addrinfo_node *node;
- errno = 0;
- val = strtoul(service, &end, 10);
+ if (hints->ai_flags & ARES_AI_PASSIVE) {
+ passive = ARES_TRUE;
+ }
- if (errno == 0 && *end == '\0' && val <= 65535) {
- *port = (unsigned short)val;
- return ARES_TRUE;
+ if (family == AF_INET6 || family == AF_UNSPEC) {
+ struct ares_in6_addr addr6;
+ memset(&addr6, 0, sizeof(addr6)); /* wildcard "::" */
+ if (!passive) {
+ ares_inet_pton(AF_INET6, "::1", &addr6);
+ }
+ status = ares_append_ai_node(AF_INET6, port, 0, &addr6, &ai->nodes);
+ if (status != ARES_SUCCESS) {
+ return status; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
}
- return ARES_FALSE;
+ if (family == AF_INET || family == AF_UNSPEC) {
+ struct in_addr addr4;
+ memset(&addr4, 0, sizeof(addr4)); /* wildcard "0.0.0.0" */
+ if (!passive) {
+ ares_inet_pton(AF_INET, "127.0.0.1", &addr4);
+ }
+ status = ares_append_ai_node(AF_INET, port, 0, &addr4, &ai->nodes);
+ if (status != ARES_SUCCESS) {
+ return status; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ }
+
+ for (node = ai->nodes; node != NULL; node = node->ai_next) {
+ node->ai_socktype = hints->ai_socktype;
+ node->ai_protocol = hints->ai_protocol;
+ }
+
+ return ARES_SUCCESS;
}
static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name,
@@ -616,6 +657,13 @@ static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name,
return;
}
+ /* POSIX getaddrinfo() requires that at least one of node (name) or service
+ * be provided. */
+ if (name == NULL && service == NULL) {
+ callback(arg, ARES_ENOTFOUND, 0, NULL);
+ return;
+ }
+
if (ares_is_onion_domain(name)) {
callback(arg, ARES_ENOTFOUND, 0, NULL);
return;
@@ -623,14 +671,14 @@ static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name,
if (service) {
if (hints->ai_flags & ARES_AI_NUMERICSERV) {
- if (!numeric_service_to_port(service, &port)) {
+ if (!ares_parse_port(service, &port, ARES_TRUE)) {
callback(arg, ARES_ESERVICE, 0, NULL);
return;
}
} else {
port = lookup_service(service, 0);
if (!port) {
- if (!numeric_service_to_port(service, &port)) {
+ if (!ares_parse_port(service, &port, ARES_TRUE)) {
callback(arg, ARES_ESERVICE, 0, NULL);
return;
}
@@ -644,6 +692,19 @@ static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name,
return;
}
+ /* No node/hostname was provided (service-only lookup): synthesize wildcard
+ * or loopback addresses from the resolved port instead of issuing a query. */
+ if (name == NULL) {
+ status = ares_append_nulladdr(port, hints, ai);
+ if (status != ARES_SUCCESS) {
+ ares_freeaddrinfo(ai); /* LCOV_EXCL_LINE: OutOfMemory */
+ callback(arg, (int)status, 0, NULL); /* LCOV_EXCL_LINE: OutOfMemory */
+ return; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ callback(arg, ARES_SUCCESS, 0, ai);
+ return;
+ }
+
if (fake_addrinfo(name, port, hints, ai, callback, arg)) {
return;
}
diff --git a/deps/cares/src/lib/ares_gethostbyname.c b/deps/cares/src/lib/ares_gethostbyname.c
index d451b4685110ec..640df74cfc36c3 100644
--- a/deps/cares/src/lib/ares_gethostbyname.c
+++ b/deps/cares/src/lib/ares_gethostbyname.c
@@ -102,6 +102,10 @@ void ares_gethostbyname(ares_channel_t *channel, const char *name, int family,
struct ares_addrinfo_hints hints;
struct host_query *ghbn_arg;
+ if (channel == NULL) {
+ return;
+ }
+
if (!callback) {
return;
}
diff --git a/deps/cares/src/lib/ares_hosts_file.c b/deps/cares/src/lib/ares_hosts_file.c
index c0fb80766a7e9b..0c8aebaf6bad9d 100644
--- a/deps/cares/src/lib/ares_hosts_file.c
+++ b/deps/cares/src/lib/ares_hosts_file.c
@@ -674,6 +674,9 @@ static ares_status_t ares_hosts_path(const ares_channel_t *channel,
&dwLength);
ExpandEnvironmentStringsA(tmp, PATH_HOSTS, MAX_PATH);
RegCloseKey(hkeyHosts);
+ if (strlen(PATH_HOSTS)+strlen(WIN_PATH_HOSTS) >= MAX_PATH) {
+ return ARES_ENOTFOUND;
+ }
strcat(PATH_HOSTS, WIN_PATH_HOSTS);
#elif defined(WATT32)
const char *PATH_HOSTS = _w32_GetHostsFile();
diff --git a/deps/cares/src/lib/ares_init.c b/deps/cares/src/lib/ares_init.c
index dc094c4df6eb69..dc3c1e4f537e16 100644
--- a/deps/cares/src/lib/ares_init.c
+++ b/deps/cares/src/lib/ares_init.c
@@ -102,6 +102,20 @@ static int server_sort_cb(const void *data1, const void *data2)
if (s1->consec_failures > s2->consec_failures) {
return 1;
}
+
+ /* Among servers with the same failure count, prefer the one that failed
+ * least recently so that a server that went down long ago (and may have
+ * since recovered) is tried before one that just failed. This also keeps
+ * retries rotating across servers once the failure counts saturate at
+ * SERVER_CONSEC_FAILURES_CAP. Healthy servers all have a zeroed retry
+ * time and fall through to configuration order. */
+ if (s1->next_retry_time.sec != s2->next_retry_time.sec) {
+ return s1->next_retry_time.sec < s2->next_retry_time.sec ? -1 : 1;
+ }
+ if (s1->next_retry_time.usec != s2->next_retry_time.usec) {
+ return s1->next_retry_time.usec < s2->next_retry_time.usec ? -1 : 1;
+ }
+
if (s1->idx < s2->idx) {
return -1;
}
diff --git a/deps/cares/src/lib/ares_library_init.c b/deps/cares/src/lib/ares_library_init.c
index 2b91692baec6d5..10c88de0095793 100644
--- a/deps/cares/src/lib/ares_library_init.c
+++ b/deps/cares/src/lib/ares_library_init.c
@@ -105,6 +105,27 @@ void *ares_realloc_zero(void *ptr, size_t orig_size, size_t new_size)
return p;
}
+void *ares_malloc_zero_array(size_t num, size_t size)
+{
+ size_t total;
+ if (ares_size_t_mul_overflow(num, size, &total)) {
+ return NULL;
+ }
+ return ares_malloc_zero(total);
+}
+
+void *ares_realloc_zero_array(void *ptr, size_t orig_num, size_t new_num,
+ size_t size)
+{
+ size_t orig_total;
+ size_t new_total;
+ if (ares_size_t_mul_overflow(orig_num, size, &orig_total) ||
+ ares_size_t_mul_overflow(new_num, size, &new_total)) {
+ return NULL;
+ }
+ return ares_realloc_zero(ptr, orig_total, new_total);
+}
+
int ares_library_init(int flags)
{
if (ares_initialized) {
diff --git a/deps/cares/src/lib/ares_private.h b/deps/cares/src/lib/ares_private.h
index d6bd426d39c180..04d4576ed203f1 100644
--- a/deps/cares/src/lib/ares_private.h
+++ b/deps/cares/src/lib/ares_private.h
@@ -131,6 +131,13 @@ W32_FUNC const char *_w32_GetHostsFile(void);
#define DEFAULT_SERVER_RETRY_CHANCE 10
#define DEFAULT_SERVER_RETRY_DELAY 5000
+/* Upper bound on the consecutive failure count tracked per server. Only the
+ * relative order of the counts is used for server selection, so magnitude
+ * beyond "clearly down" carries no additional signal. Capping it bounds how
+ * long a server that failed for an extended period sorts behind other failed
+ * servers once it comes back online. */
+#define SERVER_CONSEC_FAILURES_CAP 16
+
typedef void (*ares_query_enqueue_cb)(void *data);
struct ares_query;
@@ -418,6 +425,9 @@ ares_status_t ares_parse_sortlist(struct apattern **sortlist, size_t *nsort,
ares_status_t ares_lookup_hostaliases(const ares_channel_t *channel,
const char *name, char **alias);
+ares_bool_t ares_parse_port(const char *str, unsigned short *port,
+ ares_bool_t allow_zero);
+
ares_status_t ares_cat_domain(const char *name, const char *domain, char **s);
ares_status_t ares_sortaddrinfo(ares_channel_t *channel,
struct ares_addrinfo_node *ai_node);
@@ -539,10 +549,15 @@ void ares_gethostbyaddr_nolock(ares_channel_t *channel, const void *addr,
* ares_free()'d by the caller.
* \param[in] is_hostname if ARES_TRUE, will validate the character set for
* a valid hostname or will return error.
+ * \param[in] allow_compression if ARES_FALSE, a compression pointer
+ * encountered while parsing the name will be rejected
+ * with ARES_EBADNAME. Used for RR types (e.g. SRV)
+ * whose RDATA names must not use name compression.
* \return ARES_SUCCESS on success
*/
ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
- ares_bool_t is_hostname);
+ ares_bool_t is_hostname,
+ ares_bool_t allow_compression);
/*! Write the DNS name to the buffer in the DNS domain-name syntax as a
* series of labels. The maximum domain name length is 255 characters with
diff --git a/deps/cares/src/lib/ares_process.c b/deps/cares/src/lib/ares_process.c
index 9352968419ef8e..b2316c4b333cab 100644
--- a/deps/cares/src/lib/ares_process.c
+++ b/deps/cares/src/lib/ares_process.c
@@ -45,7 +45,6 @@
#include
-static void timeadd(ares_timeval_t *now, size_t millisecs);
static ares_status_t process_write(ares_channel_t *channel,
ares_socket_t write_fd);
static ares_status_t process_read(ares_channel_t *channel,
@@ -66,6 +65,11 @@ static void end_query(ares_channel_t *channel, ares_server_t *server,
ares_query_t *query, ares_status_t status,
ares_dns_record_t *dnsrec,
ares_array_t **requeue);
+static ares_status_t ares_send_query_int(ares_server_t *requested_server,
+ ares_query_t *query,
+ const ares_timeval_t *now,
+ ares_array_t **requeue);
+static void ares_detach_query(ares_query_t *query);
static void ares_query_remove_from_conn(ares_query_t *query)
{
@@ -124,13 +128,22 @@ static void server_increment_failures(ares_server_t *server,
return; /* LCOV_EXCL_LINE: DefensiveCoding */
}
- server->consec_failures++;
- ares_slist_node_reinsert(node);
+ /* Cap the failure count. Only the relative order matters for server
+ * selection, and an uncapped count would require an unbounded number of
+ * failures on other servers before a server that failed during an
+ * extended outage could be selected again. */
+ if (server->consec_failures < SERVER_CONSEC_FAILURES_CAP) {
+ server->consec_failures++;
+ }
+ /* Must update the retry time before reinserting since the sort uses it to
+ * order servers with the same failure count */
ares_tvnow(&next_retry_time);
- timeadd(&next_retry_time, channel->server_retry_delay);
+ ares_timeval_add(&next_retry_time, channel->server_retry_delay);
server->next_retry_time = next_retry_time;
+ ares_slist_node_reinsert(node);
+
invoke_server_state_cb(server, ARES_FALSE,
used_tcp == ARES_TRUE ? ARES_SERV_STATE_TCP
: ARES_SERV_STATE_UDP);
@@ -148,12 +161,13 @@ static void server_set_good(ares_server_t *server, ares_bool_t used_tcp)
if (server->consec_failures > 0) {
server->consec_failures = 0;
+ /* Must update the retry time before reinserting since the sort uses it
+ * to order servers with the same failure count */
+ server->next_retry_time.sec = 0;
+ server->next_retry_time.usec = 0;
ares_slist_node_reinsert(node);
}
- server->next_retry_time.sec = 0;
- server->next_retry_time.usec = 0;
-
invoke_server_state_cb(server, ARES_TRUE,
used_tcp == ARES_TRUE ? ARES_SERV_STATE_TCP
: ARES_SERV_STATE_UDP);
@@ -178,18 +192,6 @@ ares_bool_t ares_timedout(const ares_timeval_t *now,
: ARES_FALSE;
}
-/* add the specific number of milliseconds to the time in the first argument */
-static void timeadd(ares_timeval_t *now, size_t millisecs)
-{
- now->sec += (ares_int64_t)millisecs / 1000;
- now->usec += (unsigned int)((millisecs % 1000) * 1000);
-
- if (now->usec >= 1000000) {
- now->sec += now->usec / 1000000;
- now->usec %= 1000000;
- }
-}
-
static ares_status_t ares_process_fds_nolock(ares_channel_t *channel,
const ares_fd_events_t *events,
size_t nevents, unsigned int flags)
@@ -445,12 +447,19 @@ void ares_process_pending_write(ares_channel_t *channel)
ares_channel_unlock(channel);
}
-static ares_status_t read_conn_packets(ares_conn_t *conn)
+static ares_status_t read_conn_packets(ares_conn_t *conn,
+ ares_bool_t *conn_error)
{
ares_bool_t read_again;
ares_conn_err_t err;
const ares_channel_t *channel = conn->server->channel;
+ if (conn_error == NULL) {
+ return ARES_EFORMERR;
+ }
+
+ *conn_error = ARES_FALSE;
+
do {
size_t count;
size_t len = 65535;
@@ -498,6 +507,18 @@ static ares_status_t read_conn_packets(ares_conn_t *conn)
/* If UDP, overwrite length */
if (!(conn->flags & ARES_CONN_FLAG_TCP)) {
+ /* The read buffer is grown in powers of two, so a single recvfrom() can
+ * return more than the 2-byte length prefix is able to represent. A
+ * datagram that large can't be a valid DNS message, and writing the
+ * truncated (unsigned short)count would desync the framing of everything
+ * buffered after it, so discard it. Standard UDP can't actually deliver
+ * a payload this large (max is 65507 IPv4 / 65527 IPv6); the only vector
+ * is IPv6 jumbograms (RFC 2675), which DNS never uses. This is purely
+ * defense-in-depth. */
+ if (count > 65535) {
+ ares_buf_set_length(conn->in_buf, start_len);
+ break;
+ }
len = ares_buf_len(conn->in_buf);
ares_buf_set_length(conn->in_buf, start_len);
ares_buf_append_be16(conn->in_buf, (unsigned short)count);
@@ -508,8 +529,15 @@ static ares_status_t read_conn_packets(ares_conn_t *conn)
} while (read_again);
if (err != ARES_CONN_ERR_SUCCESS && err != ARES_CONN_ERR_WOULDBLOCK) {
- handle_conn_error(conn, ARES_TRUE, ARES_ECONNREFUSED);
- return ARES_ECONNREFUSED;
+ /* If there is no packet data buffered, preserve the historical
+ * immediate connection-failure behavior so retries happen promptly.
+ * Only defer if there is buffered data to parse first. */
+ if (ares_buf_len(conn->in_buf) == 0) {
+ handle_conn_error(conn, ARES_TRUE, ARES_ECONNREFUSED);
+ return ARES_ECONNREFUSED;
+ }
+
+ *conn_error = ARES_TRUE;
}
return ARES_SUCCESS;
@@ -573,6 +601,79 @@ static ares_status_t ares_append_endqueue(ares_array_t **requeue,
dnsrec);
}
+/* Drain the deferred requeue/endqueue list iteratively. All flush sites
+ * (read_answers(), process_timeouts(), and ares_send_query()) funnel through
+ * here so that:
+ * 1. Retries are re-dispatched by appending to this same list and looping,
+ * rather than recursing ares_requeue_query() -> ares_send_query() until
+ * the stack is exhausted (issue #1043).
+ * 2. A query is fully detached from all lookup lists before its callback is
+ * invoked, so a reentrant ares_cancel() from within that callback cannot
+ * find and free the same query, which would otherwise double-free it
+ * (CVE-2026-33630 / GHSA-6wfj-rwm7-3542).
+ *
+ * On return the list has been fully processed, an empty-queue notification has
+ * been sent if appropriate, and *requeue has been destroyed and set to NULL. */
+static ares_status_t ares_flush_requeue(ares_channel_t *channel,
+ const ares_timeval_t *now,
+ ares_array_t **requeue)
+{
+ ares_status_t status = ARES_SUCCESS;
+
+ if (requeue == NULL) {
+ return status;
+ }
+
+ while (*requeue != NULL && ares_array_len(*requeue) > 0) {
+ ares_query_t *query;
+ ares_requeue_t entry;
+ ares_status_t internal_status;
+
+ internal_status = ares_array_claim_at(&entry, sizeof(entry), *requeue, 0);
+ if (internal_status != ARES_SUCCESS) {
+ break; /* LCOV_EXCL_LINE: DefensiveCoding */
+ }
+
+ query = ares_htable_szvp_get_direct(channel->queries_by_qid, entry.qid);
+
+ if (entry.type == REQUEUE_REQUEUE) {
+ /* Query disappeared (e.g. a prior callback in this drain cancelled it) */
+ if (query == NULL) {
+ continue;
+ }
+ /* Re-dispatch via the internal entrypoint so any further requeues are
+ * appended back onto this same list and drained by the loop above,
+ * rather than recursing. */
+ internal_status = ares_send_query_int(entry.server, query, now, requeue);
+ /* We only care about ARES_ENOMEM */
+ if (internal_status == ARES_ENOMEM) {
+ status = ARES_ENOMEM;
+ }
+ } else { /* REQUEUE_ENDQUERY */
+ if (query != NULL) {
+ /* Detach the query from all lookup lists BEFORE invoking the callback.
+ * Otherwise a reentrant ares_cancel() from within the callback would
+ * find this query still linked in all_queries/queries_by_qid, free it,
+ * and the ares_free_query() below would then double-free it. */
+ ares_detach_query(query);
+ query->callback(query->arg, entry.status, query->timeouts,
+ entry.dnsrec);
+ ares_free_query(query);
+ }
+ ares_dns_record_destroy(entry.dnsrec);
+ }
+ }
+
+ /* Don't forget to send notification if queue emptied */
+ if (*requeue != NULL) {
+ ares_queue_notify_empty(channel);
+ }
+ ares_array_destroy(*requeue);
+ *requeue = NULL;
+
+ return status;
+}
+
static ares_status_t read_answers(ares_conn_t *conn, const ares_timeval_t *now)
{
ares_status_t status;
@@ -625,43 +726,11 @@ static ares_status_t read_answers(ares_conn_t *conn, const ares_timeval_t *now)
}
cleanup:
-
- /* Flush requeue */
- while (ares_array_len(requeue) > 0) {
- ares_query_t *query;
- ares_requeue_t entry;
- ares_status_t internal_status;
-
- internal_status = ares_array_claim_at(&entry, sizeof(entry), requeue, 0);
- if (internal_status != ARES_SUCCESS) {
- break;
- }
-
- query = ares_htable_szvp_get_direct(channel->queries_by_qid, entry.qid);
-
- if (entry.type == REQUEUE_REQUEUE) {
- /* query disappeared */
- if (query == NULL) {
- continue;
- }
- internal_status = ares_send_query(entry.server, query, now);
- /* We only care about ARES_ENOMEM */
- if (internal_status == ARES_ENOMEM) {
- status = ARES_ENOMEM;
- }
- } else { /* REQUEUE_ENDQUERY */
- if (query != NULL) {
- query->callback(query->arg, entry.status, query->timeouts, entry.dnsrec);
- ares_free_query(query);
- }
- ares_dns_record_destroy(entry.dnsrec);
- }
- }
- /* Don't forget to send notification if queue emptied */
- if (requeue != NULL) {
- ares_queue_notify_empty(channel);
+ /* Flush requeue - re-dispatch retries and invoke deferred callbacks
+ * iteratively and safely */
+ if (ares_flush_requeue(channel, now, &requeue) == ARES_ENOMEM) {
+ status = ARES_ENOMEM;
}
- ares_array_destroy(requeue);
return status;
}
@@ -671,24 +740,32 @@ static ares_status_t process_read(ares_channel_t *channel,
const ares_timeval_t *now)
{
ares_conn_t *conn = ares_conn_from_fd(channel, read_fd);
+ ares_bool_t conn_error;
ares_status_t status;
if (conn == NULL) {
return ARES_SUCCESS;
}
- /* TODO: There might be a potential issue here where there was a read that
- * read some data, then looped and read again and got a disconnect.
- * Right now, that would cause a resend instead of processing the data
- * we have. This is fairly unlikely to occur due to only looping if
- * a full buffer of 65535 bytes was read. */
- status = read_conn_packets(conn);
+ status = read_conn_packets(conn, &conn_error);
if (status != ARES_SUCCESS) {
return status;
}
- return read_answers(conn, now);
+ status = read_answers(conn, now);
+ if (status != ARES_SUCCESS) {
+ return status;
+ }
+
+ if (conn_error) {
+ conn = ares_conn_from_fd(channel, read_fd);
+ if (conn != NULL) {
+ handle_conn_error(conn, ARES_TRUE, ARES_ECONNREFUSED);
+ }
+ }
+
+ return ARES_SUCCESS;
}
/* If any queries have timed out, note the timeout and move them on. */
@@ -696,7 +773,8 @@ static ares_status_t process_timeouts(ares_channel_t *channel,
const ares_timeval_t *now)
{
ares_slist_node_t *node;
- ares_status_t status = ARES_SUCCESS;
+ ares_status_t status = ARES_SUCCESS;
+ ares_array_t *requeue = NULL;
/* Just keep popping off the first as this list will re-sort as things come
* and go. We don't want to try to rely on 'next' as some operation might
@@ -714,14 +792,26 @@ static ares_status_t process_timeouts(ares_channel_t *channel,
query->timeouts++;
conn = query->conn;
+ /* Retire this connection for NEW queries. A timeout suggests packets are
+ * being dropped on it, so route new queries to a fresh source port while
+ * the in-flight queries here drain (it is cleaned up once idle). This is
+ * per-connection so a transient failure doesn't stop reuse of healthy
+ * connections to the same server. */
+ conn->flags |= ARES_CONN_FLAG_NONEW;
server_increment_failures(conn->server, query->using_tcp);
- status = ares_requeue_query(query, now, ARES_ETIMEOUT, ARES_TRUE, NULL,
- NULL);
+ status =
+ ares_requeue_query(query, now, ARES_ETIMEOUT, ARES_TRUE, NULL, &requeue);
if (status == ARES_ENOMEM) {
goto done;
}
}
done:
+ /* Flush requeue - re-dispatch retries and invoke deferred callbacks
+ * iteratively and safely */
+ if (ares_flush_requeue(channel, now, &requeue) == ARES_ENOMEM) {
+ status = ARES_ENOMEM;
+ }
+
if (status == ARES_ENOMEM) {
return ARES_ENOMEM;
}
@@ -871,7 +961,7 @@ static ares_status_t process_answer(ares_channel_t *channel,
if (issue_might_be_edns(query->query, rdnsrec)) {
status = rewrite_without_edns(query);
if (status != ARES_SUCCESS) {
- end_query(channel, server, query, status, NULL, NULL);
+ end_query(channel, server, query, status, NULL, requeue);
goto cleanup;
}
@@ -975,6 +1065,11 @@ ares_status_t ares_requeue_query(ares_query_t *query, const ares_timeval_t *now,
{
ares_channel_t *channel = query->channel;
size_t max_tries = ares_slist_len(channel->servers) * channel->tries;
+ ares_server_t *server = NULL;
+
+ if (query->conn != NULL) {
+ server = query->conn->server;
+ }
ares_query_remove_from_conn(query);
@@ -999,7 +1094,7 @@ ares_status_t ares_requeue_query(ares_query_t *query, const ares_timeval_t *now,
query->error_status = ARES_ETIMEOUT;
}
- end_query(channel, NULL, query, query->error_status, dnsrec, requeue);
+ end_query(channel, server, query, query->error_status, dnsrec, requeue);
return ARES_ETIMEOUT;
}
@@ -1102,22 +1197,23 @@ static void ares_probe_failed_server(ares_channel_t *channel,
}
/* Select the first server with failures to retry that has passed the retry
- * timeout and doesn't already have a pending probe */
+ * timeout and doesn't already have a pending probe. Skip the server the
+ * triggering query was just sent to since that query is already exercising
+ * it. */
ares_tvnow(&now);
for (node = ares_slist_node_first(channel->servers); node != NULL;
node = ares_slist_node_next(node)) {
ares_server_t *node_val = ares_slist_node_val(node);
- if (node_val != NULL && node_val->consec_failures > 0 &&
- !node_val->probe_pending &&
+ if (node_val != NULL && node_val != server &&
+ node_val->consec_failures > 0 && !node_val->probe_pending &&
ares_timedout(&now, &node_val->next_retry_time)) {
probe_server = node_val;
break;
}
}
- /* Either nothing to probe or the query was enqueud to the same server
- * we were going to probe. Do nothing. */
- if (probe_server == NULL || server == probe_server) {
+ /* Nothing to probe */
+ if (probe_server == NULL) {
return;
}
@@ -1148,7 +1244,12 @@ static size_t ares_calc_query_timeout(const ares_query_t *query,
* retry from the last retry */
rounds = (query->try_count / num_servers);
if (rounds > 0) {
- timeplus <<= rounds;
+ if (rounds >= sizeof(timeplus) * CHAR_BIT ||
+ timeplus > (SIZE_MAX >> rounds)) {
+ timeplus = SIZE_MAX;
+ } else {
+ timeplus <<= rounds;
+ }
}
if (channel->maxtimeout && timeplus > channel->maxtimeout) {
@@ -1205,9 +1306,13 @@ static ares_conn_t *ares_fetch_connection(const ares_channel_t *channel,
return NULL;
}
- /* If the associated server has failures, don't use it. It should be cleaned
- * up later. */
- if (conn->server->consec_failures > 0) {
+ /* Don't hand new queries to a connection that has been retired (e.g. it saw
+ * a timeout). It keeps servicing its in-flight queries and is cleaned up
+ * once idle. Note this is a per-connection check: a server-wide failure
+ * counter must not be used here or a single transient failure would evict
+ * every (including healthy) connection to the server and spawn a new socket
+ * per query. */
+ if (conn->flags & ARES_CONN_FLAG_NONEW) {
return NULL;
}
@@ -1262,8 +1367,44 @@ static ares_status_t ares_conn_query_write(ares_conn_t *conn,
return ares_conn_flush(conn);
}
+/* Public entrypoint. Establishes a requeue list and drives
+ * ares_send_query_int() plus any retries/deferred callbacks it produces
+ * iteratively, so a chain of retryable failures can never recurse until the
+ * stack is exhausted (#1043). */
ares_status_t ares_send_query(ares_server_t *requested_server,
ares_query_t *query, const ares_timeval_t *now)
+{
+ ares_channel_t *channel = query->channel;
+ ares_array_t *requeue = NULL;
+ unsigned short qid = query->qid;
+ ares_status_t status;
+
+ status = ares_send_query_int(requested_server, query, now, &requeue);
+
+ /* Drain any retries/deferred callbacks this send produced.
+ * ares_flush_requeue() always fully processes and destroys the list (even on
+ * ENOMEM), and sends the empty-queue notification if needed. */
+ if (ares_flush_requeue(channel, now, &requeue) == ARES_ENOMEM) {
+ status = ARES_ENOMEM;
+ }
+
+ /* A retry may have been deferred (returning ARES_SUCCESS from the append)
+ * and then terminally failed while draining, in which case the query has
+ * been freed. Do not dereference 'query' here. If it is no longer tracked
+ * it ended, so don't report success to the caller (which would, e.g., cause
+ * ares_send_nolock() to write to a now-freed *qid). */
+ if (status == ARES_SUCCESS &&
+ ares_htable_szvp_get_direct(channel->queries_by_qid, qid) == NULL) {
+ status = ARES_ETIMEOUT;
+ }
+
+ return status;
+}
+
+static ares_status_t ares_send_query_int(ares_server_t *requested_server,
+ ares_query_t *query,
+ const ares_timeval_t *now,
+ ares_array_t **requeue)
{
ares_channel_t *channel = query->channel;
ares_server_t *server;
@@ -1286,14 +1427,16 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
}
if (server == NULL) {
- end_query(channel, server, query, ARES_ENOSERVER /* ? */, NULL, NULL);
+ end_query(channel, server, query, ARES_ENOSERVER /* ? */, NULL, requeue);
return ARES_ENOSERVER;
}
- /* If a query is directed to a specific query, or the server chosen has
- * failures, or the query is being retried, don't probe for downed servers */
- if (requested_server != NULL || server->consec_failures > 0 ||
- query->try_count != 0) {
+ /* If a query is directed to a specific server, or the query is being
+ * retried, don't probe for downed servers. Note that the chosen server
+ * having failures itself does NOT disable probing: when every server has
+ * failures (e.g. during an extended outage), probes are the only way a
+ * recovered server that sorts behind the current best can be noticed. */
+ if (requested_server != NULL || query->try_count != 0) {
probe_downed_server = ARES_FALSE;
}
@@ -1310,11 +1453,11 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
case ARES_ECONNREFUSED:
case ARES_EBADFAMILY:
server_increment_failures(server, query->using_tcp);
- return ares_requeue_query(query, now, status, ARES_TRUE, NULL, NULL);
+ return ares_requeue_query(query, now, status, ARES_TRUE, NULL, requeue);
/* Anything else is not retryable, likely ENOMEM */
default:
- end_query(channel, server, query, status, NULL, NULL);
+ end_query(channel, server, query, status, NULL, requeue);
return status;
}
}
@@ -1328,7 +1471,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
case ARES_ENOMEM:
/* Not retryable */
- end_query(channel, server, query, status, NULL, NULL);
+ end_query(channel, server, query, status, NULL, requeue);
return status;
/* These conditions are retryable as they are server-specific
@@ -1336,7 +1479,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
case ARES_ECONNREFUSED:
case ARES_EBADFAMILY:
handle_conn_error(conn, ARES_TRUE, status);
- status = ares_requeue_query(query, now, status, ARES_TRUE, NULL, NULL);
+ status = ares_requeue_query(query, now, status, ARES_TRUE, NULL, requeue);
if (status == ARES_ETIMEOUT) {
status = ARES_ECONNREFUSED;
}
@@ -1344,7 +1487,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
default:
server_increment_failures(server, query->using_tcp);
- status = ares_requeue_query(query, now, status, ARES_TRUE, NULL, NULL);
+ status = ares_requeue_query(query, now, status, ARES_TRUE, NULL, requeue);
return status;
}
@@ -1355,12 +1498,12 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
ares_slist_node_destroy(query->node_queries_by_timeout);
query->ts = *now;
query->timeout = *now;
- timeadd(&query->timeout, timeplus);
+ ares_timeval_add(&query->timeout, timeplus);
query->node_queries_by_timeout =
ares_slist_insert(channel->queries_by_timeout, query);
if (!query->node_queries_by_timeout) {
/* LCOV_EXCL_START: OutOfMemory */
- end_query(channel, server, query, ARES_ENOMEM, NULL, NULL);
+ end_query(channel, server, query, ARES_ENOMEM, NULL, requeue);
return ARES_ENOMEM;
/* LCOV_EXCL_STOP */
}
@@ -1373,7 +1516,7 @@ ares_status_t ares_send_query(ares_server_t *requested_server,
if (query->node_queries_to_conn == NULL) {
/* LCOV_EXCL_START: OutOfMemory */
- end_query(channel, server, query, ARES_ENOMEM, NULL, NULL);
+ end_query(channel, server, query, ARES_ENOMEM, NULL, requeue);
return ARES_ENOMEM;
/* LCOV_EXCL_STOP */
}
diff --git a/deps/cares/src/lib/ares_qcache.c b/deps/cares/src/lib/ares_qcache.c
index 4050ff54bf1667..ad3bc83505af07 100644
--- a/deps/cares/src/lib/ares_qcache.c
+++ b/deps/cares/src/lib/ares_qcache.c
@@ -371,9 +371,11 @@ static ares_status_t ares_qcache_insert_int(ares_qcache_t *qcache,
/* LCOV_EXCL_START: OutOfMemory */
fail:
- if (entry != NULL && entry->key != NULL) {
- ares_htable_strvp_remove(qcache->cache, entry->key);
- ares_free(entry->key);
+ if (entry != NULL) {
+ if (entry->key != NULL) {
+ ares_htable_strvp_remove(qcache->cache, entry->key);
+ ares_free(entry->key);
+ }
ares_free(entry);
}
return ARES_ENOMEM;
diff --git a/deps/cares/src/lib/ares_socket.c b/deps/cares/src/lib/ares_socket.c
index 516852a84abfb8..4ad3034021a0f7 100644
--- a/deps/cares/src/lib/ares_socket.c
+++ b/deps/cares/src/lib/ares_socket.c
@@ -188,6 +188,8 @@ ares_conn_err_t ares_socket_recvfrom(ares_channel_t *channel, ares_socket_t s,
{
ares_ssize_t rv;
+ *read_bytes = 0;
+
rv = channel->sock_funcs.arecvfrom(s, data, data_len, flags, from, from_len,
channel->sock_func_cb_data);
diff --git a/deps/cares/src/lib/ares_sysconfig.c b/deps/cares/src/lib/ares_sysconfig.c
index 286db60328f45b..9e389c17672a1f 100644
--- a/deps/cares/src/lib/ares_sysconfig.c
+++ b/deps/cares/src/lib/ares_sysconfig.c
@@ -218,13 +218,16 @@ static ares_status_t ares_init_sysconfig_android(const ares_channel_t *channel,
status = ares_sconfig_append_fromstr(channel, &sysconfig->sconfig,
dns_servers[i], ARES_TRUE);
if (status != ARES_SUCCESS) {
- return status;
+ break;
}
}
for (i = 0; i < num_servers; i++) {
ares_free(dns_servers[i]);
}
ares_free(dns_servers);
+ if (status != ARES_SUCCESS) {
+ return status;
+ }
}
domains = ares_get_android_search_domains_list();
@@ -273,7 +276,7 @@ static ares_status_t
* 3. if confstr(_CS_DOMAIN, ...) this is the domain name. Use this as
* preference over anything else found.
*/
- ares_buf_t *buf = ares_buf_create();
+ ares_buf_t *buf = NULL;
unsigned char *data = NULL;
size_t data_size = 0;
ares_bool_t process_resolvconf = ARES_TRUE;
@@ -330,7 +333,7 @@ static ares_status_t
char domain[256];
size_t domain_len;
- domain_len = confstr(_CS_DOMAIN, domain, sizeof(domain_len));
+ domain_len = confstr(_CS_DOMAIN, domain, sizeof(domain));
if (domain_len != 0) {
ares_strsplit_free(sysconfig->domains, sysconfig->ndomains);
sysconfig->domains = ares_strsplit(domain, ", ", &sysconfig->ndomains);
diff --git a/deps/cares/src/lib/ares_sysconfig_files.c b/deps/cares/src/lib/ares_sysconfig_files.c
index a6c2a8e62bb34f..2caa3b200075b8 100644
--- a/deps/cares/src/lib/ares_sysconfig_files.c
+++ b/deps/cares/src/lib/ares_sysconfig_files.c
@@ -27,6 +27,8 @@
#include "ares_private.h"
+#include
+
#ifdef HAVE_SYS_PARAM_H
# include
#endif
@@ -170,8 +172,8 @@ static ares_status_t parse_sort(ares_buf_t *buf, struct apattern *pat)
if (ares_str_isnum(maskstr)) {
/* Numeric mask */
- int mask = atoi(maskstr);
- if (mask < 0 || mask > 128) {
+ unsigned int mask;
+ if (!ares_str_parse_uint(maskstr, 128, &mask)) {
return ARES_EBADSTR;
}
if (pat->addr.family == AF_INET && mask > 32) {
@@ -340,12 +342,17 @@ static ares_status_t config_lookup(ares_sysconfig_t *sysconfig, ares_buf_t *buf,
const char *value = lookups[i];
char ch;
+ /* AIX /etc/netsvc.conf uses address-family-suffixed tokens such as
+ * "bind4"/"bind6" and "local4"/"local6" in addition to the bare forms. */
if (ares_strcaseeq(value, "dns") || ares_strcaseeq(value, "bind") ||
+ ares_strcaseeq(value, "bind4") || ares_strcaseeq(value, "bind6") ||
ares_strcaseeq(value, "resolv") || ares_strcaseeq(value, "resolve")) {
ch = 'b';
} else if (ares_strcaseeq(value, "files") ||
ares_strcaseeq(value, "file") ||
- ares_strcaseeq(value, "local")) {
+ ares_strcaseeq(value, "local") ||
+ ares_strcaseeq(value, "local4") ||
+ ares_strcaseeq(value, "local6")) {
ch = 'f';
} else {
continue;
@@ -401,20 +408,25 @@ static ares_status_t process_option(ares_sysconfig_t *sysconfig,
key = kv[0];
if (num == 2) {
- val = kv[1];
- valint = (unsigned int)strtoul(val, NULL, 10);
+ val = kv[1];
+ if (!ares_str_parse_uint(val, UINT_MAX, &valint)) {
+ status = ARES_EBADSTR;
+ goto done;
+ }
}
if (ares_streq(key, "ndots")) {
sysconfig->ndots = valint;
} else if (ares_streq(key, "retrans") || ares_streq(key, "timeout")) {
if (valint == 0) {
- return ARES_EFORMERR;
+ status = ARES_EFORMERR;
+ goto done;
}
sysconfig->timeout_ms = valint * 1000;
} else if (ares_streq(key, "retry") || ares_streq(key, "attempts")) {
if (valint == 0) {
- return ARES_EFORMERR;
+ status = ARES_EFORMERR;
+ goto done;
}
sysconfig->tries = valint;
} else if (ares_streq(key, "rotate")) {
diff --git a/deps/cares/src/lib/ares_sysconfig_win.c b/deps/cares/src/lib/ares_sysconfig_win.c
index 94a3817de348bb..e0daf1103e2cec 100644
--- a/deps/cares/src/lib/ares_sysconfig_win.c
+++ b/deps/cares/src/lib/ares_sysconfig_win.c
@@ -114,56 +114,69 @@ static ares_bool_t get_REG_SZ(HKEY hKey, const WCHAR *leafKeyName, char **outptr
/* Convert to UTF8 */
len = WideCharToMultiByte(CP_UTF8, 0, val, -1, NULL, 0, NULL, NULL);
if (len == 0) {
+ ares_free(val);
return ARES_FALSE;
}
*outptr = ares_malloc_zero((size_t)len + 1);
+ if (*outptr == NULL) {
+ ares_free(val);
+ return ARES_FALSE;
+ }
if (WideCharToMultiByte(CP_UTF8, 0, val, -1, *outptr, len, NULL, NULL)
== 0) {
ares_free(*outptr);
*outptr = NULL;
+ ares_free(val);
return ARES_FALSE;
}
+ ares_free(val);
return ARES_TRUE;
}
-static void commanjoin(char **dst, const char * const src, const size_t len)
+static ares_status_t sysconfig_commajoin(ares_buf_t *buf, const char *src,
+ ares_bool_t asciionly)
{
- char *newbuf;
- size_t newsize;
+ ares_status_t status;
- /* 1 for terminating 0 and 2 for , and terminating 0 */
- newsize = len + (*dst ? (ares_strlen(*dst) + 2) : 1);
- newbuf = ares_realloc(*dst, newsize);
- if (!newbuf) {
- return;
+ if (buf == NULL) {
+ return ARES_ENOMEM;
}
- if (*dst == NULL) {
- *newbuf = '\0';
+
+ if (src == NULL || *src == '\0') {
+ return ARES_SUCCESS;
}
- *dst = newbuf;
- if (ares_strlen(*dst) != 0) {
- strcat(*dst, ",");
+
+ if (asciionly && !ares_str_isprint(src, ares_strlen(src))) {
+ return ARES_SUCCESS;
}
- strncat(*dst, src, len);
-}
-/*
- * commajoin()
- *
- * RTF code.
- */
-static void commajoin(char **dst, const char *src)
-{
- commanjoin(dst, src, ares_strlen(src));
+ if (ares_buf_len(buf) > 0) {
+ status = ares_buf_append_byte(buf, ',');
+ if (status != ARES_SUCCESS) {
+ return status;
+ }
+ }
+
+ return ares_buf_append_str(buf, src);
}
-static void commajoin_asciionly(char **dst, const char *src)
+/* Read a REG_SZ value and comma-append it (ASCII-only) to *buf.
+ * On allocation failure, destroys *buf and sets it to NULL. */
+static void reg_commajoin(ares_buf_t **buf, HKEY key, const WCHAR *leaf)
{
- if (!ares_str_isprint(src, ares_strlen(src))) {
+ char *p = NULL;
+
+ if (*buf == NULL || !get_REG_SZ(key, leaf, &p)) {
return;
}
- commanjoin(dst, src, ares_strlen(src));
+
+ if (sysconfig_commajoin(*buf, p, ARES_TRUE) != ARES_SUCCESS) {
+ ares_buf_destroy(*buf);
+ *buf = NULL;
+ }
+
+ ares_free(p);
}
/* A structure to hold the string form of IPv4 and IPv6 addresses so we can
@@ -494,8 +507,10 @@ static ares_bool_t get_DNS_Windows(char **outptr)
/* Join them all into a single string, removing duplicates. */
{
- size_t i;
- for (i = 0; i < addressesIndex; ++i) {
+ size_t i;
+ ares_buf_t *buf = ares_buf_create();
+
+ for (i = 0; buf != NULL && i < addressesIndex; ++i) {
size_t j;
/* Look for this address text appearing previously in the results. */
for (j = 0; j < i; ++j) {
@@ -505,10 +520,22 @@ static ares_bool_t get_DNS_Windows(char **outptr)
}
/* Iff we didn't emit this address already, emit it now. */
if (j == i) {
- /* Add that to outptr (if we can). */
- commajoin(outptr, addresses[i].text);
+ /* Add that to buf (if we can). */
+ if (sysconfig_commajoin(buf, addresses[i].text, ARES_FALSE) !=
+ ARES_SUCCESS) {
+ ares_buf_destroy(buf);
+ buf = NULL;
+ break;
+ }
}
}
+
+ if (buf != NULL && ares_buf_len(buf) == 0) {
+ ares_buf_destroy(buf);
+ buf = NULL;
+ }
+
+ *outptr = ares_buf_finish_str(buf, NULL);
}
done:
@@ -540,51 +567,46 @@ static ares_bool_t get_DNS_Windows(char **outptr)
*/
static ares_bool_t get_SuffixList_Windows(char **outptr)
{
- HKEY hKey;
- HKEY hKeyEnum;
- char keyName[256];
- DWORD keyNameBuffSize;
- DWORD keyIdx = 0;
- char *p = NULL;
+ HKEY hKey;
+ HKEY hKeyEnum;
+ char keyName[256];
+ DWORD keyNameBuffSize;
+ DWORD keyIdx = 0;
+ ares_buf_t *buf = ares_buf_create();
*outptr = NULL;
+ if (buf == NULL) {
+ return ARES_FALSE;
+ }
+
/* 1. Global DNS Suffix Search List */
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ, &hKey) ==
ERROR_SUCCESS) {
- get_REG_SZ(hKey, SEARCHLIST_KEY, outptr);
- if (get_REG_SZ(hKey, DOMAIN_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
+ reg_commajoin(&buf, hKey, SEARCHLIST_KEY);
+ reg_commajoin(&buf, hKey, DOMAIN_KEY);
RegCloseKey(hKey);
}
- if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NT_DNSCLIENT, 0, KEY_READ, &hKey) ==
- ERROR_SUCCESS) {
- if (get_REG_SZ(hKey, SEARCHLIST_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
+ if (buf != NULL &&
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NT_DNSCLIENT, 0, KEY_READ, &hKey) ==
+ ERROR_SUCCESS) {
+ reg_commajoin(&buf, hKey, SEARCHLIST_KEY);
RegCloseKey(hKey);
}
/* 2. Connection Specific Search List composed of:
* a. Primary DNS Suffix */
- if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_DNSCLIENT, 0, KEY_READ, &hKey) ==
- ERROR_SUCCESS) {
- if (get_REG_SZ(hKey, PRIMARYDNSSUFFIX_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
+ if (buf != NULL &&
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_DNSCLIENT, 0, KEY_READ, &hKey) ==
+ ERROR_SUCCESS) {
+ reg_commajoin(&buf, hKey, PRIMARYDNSSUFFIX_KEY);
RegCloseKey(hKey);
}
/* b. Interface SearchList, Domain, DhcpDomain */
- if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY "\\" INTERFACES_KEY, 0,
+ if (buf != NULL &&
+ RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY "\\" INTERFACES_KEY, 0,
KEY_READ, &hKey) == ERROR_SUCCESS) {
for (;;) {
keyNameBuffSize = sizeof(keyName);
@@ -596,27 +618,26 @@ static ares_bool_t get_SuffixList_Windows(char **outptr)
ERROR_SUCCESS) {
continue;
}
- /* p can be comma separated (SearchList) */
- if (get_REG_SZ(hKeyEnum, SEARCHLIST_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
- if (get_REG_SZ(hKeyEnum, DOMAIN_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
- if (get_REG_SZ(hKeyEnum, DHCPDOMAIN_KEY, &p)) {
- commajoin_asciionly(outptr, p);
- ares_free(p);
- p = NULL;
- }
+ /* SearchList can be comma separated */
+ reg_commajoin(&buf, hKeyEnum, SEARCHLIST_KEY);
+ reg_commajoin(&buf, hKeyEnum, DOMAIN_KEY);
+ reg_commajoin(&buf, hKeyEnum, DHCPDOMAIN_KEY);
RegCloseKey(hKeyEnum);
+
+ if (buf == NULL) {
+ break;
+ }
}
RegCloseKey(hKey);
}
+ if (ares_buf_len(buf) == 0) {
+ ares_buf_destroy(buf);
+ buf = NULL;
+ }
+
+ *outptr = ares_buf_finish_str(buf, NULL);
+
return *outptr != NULL ? ARES_TRUE : ARES_FALSE;
}
diff --git a/deps/cares/src/lib/ares_timeout.c b/deps/cares/src/lib/ares_timeout.c
index 0d2fdcff21f657..dbd5cf443d68f2 100644
--- a/deps/cares/src/lib/ares_timeout.c
+++ b/deps/cares/src/lib/ares_timeout.c
@@ -32,6 +32,17 @@
#endif
+void ares_timeval_add(ares_timeval_t *now, size_t millisecs)
+{
+ now->sec += (ares_int64_t)millisecs / 1000;
+ now->usec += (unsigned int)((millisecs % 1000) * 1000);
+
+ if (now->usec >= 1000000) {
+ now->sec += now->usec / 1000000;
+ now->usec %= 1000000;
+ }
+}
+
void ares_timeval_remaining(ares_timeval_t *remaining,
const ares_timeval_t *now,
const ares_timeval_t *tout)
diff --git a/deps/cares/src/lib/ares_update_servers.c b/deps/cares/src/lib/ares_update_servers.c
index 70a9381499f8c2..f8d441accf23e1 100644
--- a/deps/cares/src/lib/ares_update_servers.c
+++ b/deps/cares/src/lib/ares_update_servers.c
@@ -27,6 +27,8 @@
*/
#include "ares_private.h"
+#include
+
#ifdef HAVE_ARPA_INET_H
# include
#endif
@@ -234,7 +236,10 @@ static ares_status_t parse_nameserver_uri(ares_buf_t *buf,
sconfig->tcp_port = sconfig->udp_port;
port = ares_uri_get_query_key(uri, "tcpport");
if (port != NULL) {
- sconfig->tcp_port = (unsigned short)atoi(port);
+ if (!ares_parse_port(port, &sconfig->tcp_port, ARES_TRUE)) {
+ status = ARES_EBADSTR;
+ goto done;
+ }
}
done:
@@ -352,7 +357,9 @@ static ares_status_t parse_nameserver(ares_buf_t *buf, ares_sconfig_t *sconfig)
return status;
}
- sconfig->udp_port = (unsigned short)atoi(portstr);
+ if (!ares_parse_port(portstr, &sconfig->udp_port, ARES_TRUE)) {
+ return ARES_EBADSTR;
+ }
sconfig->tcp_port = sconfig->udp_port;
}
@@ -398,7 +405,13 @@ static ares_status_t ares_sconfig_linklocal(const ares_channel_t *channel,
if (ares_str_isnum(ll_iface)) {
char ifname[IF_NAMESIZE] = "";
- ll_scope = (unsigned int)atoi(ll_iface);
+
+ /* The interface identifier is all digits but may not fit in an
+ * unsigned int; parse with the range-checked helper rather than atoi(),
+ * whose behavior on overflow is undefined. */
+ if (!ares_str_parse_uint(ll_iface, UINT_MAX, &ll_scope)) {
+ return ARES_ENOTFOUND;
+ }
if (channel->sock_funcs.aif_indextoname == NULL ||
channel->sock_funcs.aif_indextoname(ll_scope, ifname, sizeof(ifname),
channel->sock_func_cb_data) ==
@@ -967,7 +980,8 @@ ares_status_t ares_in_addr_to_sconfig_llist(const struct in_addr *servers,
sizeof(sconfig->addr.addr.addr4));
if (ares_llist_insert_last(s, sconfig) == NULL) {
- goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ ares_free(sconfig); /* LCOV_EXCL_LINE: OutOfMemory */
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
}
}
diff --git a/deps/cares/src/lib/dsa/ares_array.c b/deps/cares/src/lib/dsa/ares_array.c
index c421d5c5f670bd..e6bce6e41db91e 100644
--- a/deps/cares/src/lib/dsa/ares_array.c
+++ b/deps/cares/src/lib/dsa/ares_array.c
@@ -171,14 +171,19 @@ void *ares_array_finish(ares_array_t *arr, size_t *num_members)
ares_status_t ares_array_set_size(ares_array_t *arr, size_t size)
{
- void *temp;
+ void *temp;
+ size_t rounded_size;
if (arr == NULL || size == 0 || size < arr->cnt) {
return ARES_EFORMERR;
}
/* Always operate on powers of 2 */
- size = ares_round_up_pow2(size);
+ rounded_size = ares_round_up_pow2(size);
+ if (rounded_size < size) {
+ return ARES_ENOMEM; /* rounding wrapped around */
+ }
+ size = rounded_size;
if (size < ARES__ARRAY_MIN) {
size = ARES__ARRAY_MIN;
@@ -189,8 +194,8 @@ ares_status_t ares_array_set_size(ares_array_t *arr, size_t size)
return ARES_SUCCESS;
}
- temp = ares_realloc_zero(arr->arr, arr->alloc_cnt * arr->member_size,
- size * arr->member_size);
+ temp =
+ ares_realloc_zero_array(arr->arr, arr->alloc_cnt, size, arr->member_size);
if (temp == NULL) {
return ARES_ENOMEM;
}
@@ -295,7 +300,7 @@ ares_status_t ares_array_insertdata_first(ares_array_t *arr,
ares_status_t status;
void *ptr = NULL;
- status = ares_array_insert_last(&ptr, arr);
+ status = ares_array_insert_first(&ptr, arr);
if (status != ARES_SUCCESS) {
return status;
}
@@ -362,6 +367,16 @@ ares_status_t ares_array_claim_at(void *dest, size_t dest_size,
}
arr->cnt--;
+
+ /* When empty, reset offset so a later insert doesn't compact from an
+ * out-of-range index (offset can reach alloc_cnt after front claims).
+ * This also protects ares_array_finish(), which has the same
+ * move(0, offset) and would otherwise fail on an offset == alloc_cnt
+ * array. */
+ if (arr->cnt == 0) {
+ arr->offset = 0;
+ }
+
return ARES_SUCCESS;
}
diff --git a/deps/cares/src/lib/dsa/ares_htable.c b/deps/cares/src/lib/dsa/ares_htable.c
index f76b67cae9a668..d6ac7a065a6b02 100644
--- a/deps/cares/src/lib/dsa/ares_htable.c
+++ b/deps/cares/src/lib/dsa/ares_htable.c
@@ -150,6 +150,10 @@ const void **ares_htable_all_buckets(const ares_htable_t *htable, size_t *num)
*num = 0;
+ if (htable->num_keys == 0) {
+ return NULL;
+ }
+
out = ares_malloc_zero(sizeof(*out) * htable->num_keys);
if (out == NULL) {
return NULL; /* LCOV_EXCL_LINE */
@@ -172,7 +176,7 @@ const void **ares_htable_all_buckets(const ares_htable_t *htable, size_t *num)
* We are doing "hash & (size - 1)" since we are guaranteeing a power of
* 2 for size. This is equivalent to "hash % size", but should be more
* efficient */
-#define HASH_IDX(h, key) h->hash(key, h->seed) & (h->size - 1)
+#define HASH_IDX(h, key) ((h)->hash((key), (h)->seed) & ((h)->size - 1))
static ares_llist_node_t *ares_htable_find(const ares_htable_t *htable,
unsigned int idx, const void *key)
diff --git a/deps/cares/src/lib/dsa/ares_htable_dict.c b/deps/cares/src/lib/dsa/ares_htable_dict.c
index 93d7a20137c8db..cda7a49c79d05d 100644
--- a/deps/cares/src/lib/dsa/ares_htable_dict.c
+++ b/deps/cares/src/lib/dsa/ares_htable_dict.c
@@ -132,6 +132,7 @@ ares_bool_t ares_htable_dict_insert(ares_htable_dict_t *htable, const char *key,
fail:
if (bucket) {
+ ares_free(bucket->key);
ares_free(bucket->val);
ares_free(bucket);
}
@@ -223,6 +224,7 @@ char **ares_htable_dict_keys(const ares_htable_dict_t *htable, size_t *num)
fail:
*num = 0;
+ ares_free(buckets);
ares_free_array(out, cnt, ares_free);
return NULL;
}
diff --git a/deps/cares/src/lib/dsa/ares_llist.c b/deps/cares/src/lib/dsa/ares_llist.c
index 6bd7de269a43fb..ce1a31149401f1 100644
--- a/deps/cares/src/lib/dsa/ares_llist.c
+++ b/deps/cares/src/lib/dsa/ares_llist.c
@@ -103,7 +103,10 @@ static void ares_llist_attach_at(ares_llist_t *list,
case ARES__LLIST_INSERT_BEFORE:
node->next = at;
node->prev = at->prev;
- at->prev = node;
+ if (at->prev) {
+ at->prev->next = node;
+ }
+ at->prev = node;
break;
}
if (list->tail == NULL) {
diff --git a/deps/cares/src/lib/event/ares_event_configchg.c b/deps/cares/src/lib/event/ares_event_configchg.c
index add729574e46c3..d73c4862058c97 100644
--- a/deps/cares/src/lib/event/ares_event_configchg.c
+++ b/deps/cares/src/lib/event/ares_event_configchg.c
@@ -207,12 +207,14 @@ void ares_event_configchg_destroy(ares_event_configchg_t *configchg)
# ifdef HAVE_REGISTERWAITFORSINGLEOBJECT
if (configchg->regip4_wait != NULL) {
- UnregisterWait(configchg->regip4_wait);
+ /* Use INVALID_HANDLE_VALUE to wait for any running callback to complete
+ * before returning, preventing use-after-free of configchg */
+ UnregisterWaitEx(configchg->regip4_wait, INVALID_HANDLE_VALUE);
configchg->regip4_wait = NULL;
}
if (configchg->regip6_wait != NULL) {
- UnregisterWait(configchg->regip6_wait);
+ UnregisterWaitEx(configchg->regip6_wait, INVALID_HANDLE_VALUE);
configchg->regip6_wait = NULL;
}
diff --git a/deps/cares/src/lib/event/ares_event_wake_pipe.c b/deps/cares/src/lib/event/ares_event_wake_pipe.c
index cd1534bbbd586c..2ea72c29415230 100644
--- a/deps/cares/src/lib/event/ares_event_wake_pipe.c
+++ b/deps/cares/src/lib/event/ares_event_wake_pipe.c
@@ -117,7 +117,13 @@ static void ares_pipeevent_signal(const ares_event_t *e)
}
p = e->data;
- (void)write(p->filedes[1], "1", 1);
+ /* Best-effort wakeup. A failed or short write is harmless: it can only
+ * happen when the pipe buffer is already full, which means a wakeup is
+ * already pending. The result is consumed to satisfy warn_unused_result
+ * (a plain (void) cast does not silence it under _FORTIFY_SOURCE). */
+ if (write(p->filedes[1], "1", 1) < 0) {
+ return;
+ }
}
static void ares_pipeevent_cb(ares_event_thread_t *e, ares_socket_t fd,
diff --git a/deps/cares/src/lib/event/ares_event_win32.c b/deps/cares/src/lib/event/ares_event_win32.c
index d7d1d65735082d..d558b6930a66f2 100644
--- a/deps/cares/src/lib/event/ares_event_win32.c
+++ b/deps/cares/src/lib/event/ares_event_win32.c
@@ -707,6 +707,9 @@ static ares_bool_t ares_evsys_win32_event_add(ares_event_t *event)
ares_bool_t rc = ARES_FALSE;
ed = ares_malloc_zero(sizeof(*ed));
+ if (ed == NULL) {
+ return ARES_FALSE; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
ed->event = event;
ed->socket = event->fd;
ed->base_socket = ARES_SOCKET_BAD;
diff --git a/deps/cares/src/lib/include/ares_mem.h b/deps/cares/src/lib/include/ares_mem.h
index 371cd4266dc720..d980923e5817a2 100644
--- a/deps/cares/src/lib/include/ares_mem.h
+++ b/deps/cares/src/lib/include/ares_mem.h
@@ -34,5 +34,8 @@ CARES_EXTERN void ares_free(void *ptr);
CARES_EXTERN void *ares_malloc_zero(size_t size);
CARES_EXTERN void *ares_realloc_zero(void *ptr, size_t orig_size,
size_t new_size);
+CARES_EXTERN void *ares_malloc_zero_array(size_t num, size_t size);
+CARES_EXTERN void *ares_realloc_zero_array(void *ptr, size_t orig_num,
+ size_t new_num, size_t size);
#endif
diff --git a/deps/cares/src/lib/include/ares_str.h b/deps/cares/src/lib/include/ares_str.h
index 4ee339510bf026..c7fdf4fc4c8059 100644
--- a/deps/cares/src/lib/include/ares_str.h
+++ b/deps/cares/src/lib/include/ares_str.h
@@ -60,6 +60,23 @@ CARES_EXTERN size_t ares_strcpy(char *dest, const char *src, size_t dest_size);
CARES_EXTERN ares_bool_t ares_str_isnum(const char *str);
CARES_EXTERN ares_bool_t ares_str_isalnum(const char *str);
+/*! Parse a base-10 unsigned integer from a NULL-terminated string. Unlike
+ * atoi(), this rejects overflow, leading or trailing garbage (a sign,
+ * whitespace, or non-digit characters), and any value that does not fit
+ * within the caller-provided maximum.
+ *
+ * \param[in] str String to parse. May be NULL or empty, in which case
+ * ARES_FALSE is returned.
+ * \param[in] max Largest value considered valid. Values above UINT_MAX
+ * are capped to UINT_MAX since out is an unsigned int.
+ * \param[out] out On success, receives the parsed value. Untouched on
+ * failure.
+ * \return ARES_TRUE if a valid in-range integer was parsed, ARES_FALSE
+ * otherwise.
+ */
+CARES_EXTERN ares_bool_t ares_str_parse_uint(const char *str, unsigned long max,
+ unsigned int *out);
+
CARES_EXTERN void ares_str_ltrim(char *str);
CARES_EXTERN void ares_str_rtrim(char *str);
CARES_EXTERN void ares_str_trim(char *str);
diff --git a/deps/cares/src/lib/legacy/ares_expand_name.c b/deps/cares/src/lib/legacy/ares_expand_name.c
index 72668f4cb60a07..ee1d70076be815 100644
--- a/deps/cares/src/lib/legacy/ares_expand_name.c
+++ b/deps/cares/src/lib/legacy/ares_expand_name.c
@@ -69,7 +69,7 @@ ares_status_t ares_expand_name_validated(const unsigned char *encoded,
}
start_len = ares_buf_len(buf);
- status = ares_dns_name_parse(buf, s, is_hostname);
+ status = ares_dns_name_parse(buf, s, is_hostname, ARES_TRUE);
if (status != ARES_SUCCESS) {
goto done;
}
diff --git a/deps/cares/src/lib/legacy/ares_parse_ns_reply.c b/deps/cares/src/lib/legacy/ares_parse_ns_reply.c
index fc9ab9219d399c..7013ce28b90131 100644
--- a/deps/cares/src/lib/legacy/ares_parse_ns_reply.c
+++ b/deps/cares/src/lib/legacy/ares_parse_ns_reply.c
@@ -97,12 +97,12 @@ int ares_parse_ns_reply(const unsigned char *abuf, int alen_int,
}
/* Preallocate the maximum number + 1 */
- hostent->h_aliases = ares_malloc((ancount + 1) * sizeof(*hostent->h_aliases));
+ hostent->h_aliases =
+ ares_malloc_zero_array(ancount + 1, sizeof(*hostent->h_aliases));
if (hostent->h_aliases == NULL) {
status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
goto done; /* LCOV_EXCL_LINE: OutOfMemory */
}
- memset(hostent->h_aliases, 0, (ancount + 1) * sizeof(*hostent->h_aliases));
for (i = 0; i < ancount; i++) {
const ares_dns_rr_t *rr =
diff --git a/deps/cares/src/lib/legacy/ares_parse_ptr_reply.c b/deps/cares/src/lib/legacy/ares_parse_ptr_reply.c
index 0e52f9db09bbc7..a275f3802ffc08 100644
--- a/deps/cares/src/lib/legacy/ares_parse_ptr_reply.c
+++ b/deps/cares/src/lib/legacy/ares_parse_ptr_reply.c
@@ -88,12 +88,12 @@ ares_status_t ares_parse_ptr_reply_dnsrec(const ares_dns_record_t *dnsrec,
hostent->h_length = (HOSTENT_LENGTH_TYPE)addrlen;
/* Preallocate the maximum number + 1 */
- hostent->h_aliases = ares_malloc((ancount + 1) * sizeof(*hostent->h_aliases));
+ hostent->h_aliases =
+ ares_malloc_zero_array(ancount + 1, sizeof(*hostent->h_aliases));
if (hostent->h_aliases == NULL) {
status = ARES_ENOMEM;
goto done;
}
- memset(hostent->h_aliases, 0, (ancount + 1) * sizeof(*hostent->h_aliases));
/* Cycle through answers */
diff --git a/deps/cares/src/lib/record/ares_dns_mapping.c b/deps/cares/src/lib/record/ares_dns_mapping.c
index 5a3ec28abf130b..38dedd438c6dd5 100644
--- a/deps/cares/src/lib/record/ares_dns_mapping.c
+++ b/deps/cares/src/lib/record/ares_dns_mapping.c
@@ -147,7 +147,7 @@ ares_bool_t ares_dns_class_isvalid(ares_dns_class_t qclass,
switch (qclass) {
case ARES_CLASS_IN:
case ARES_CLASS_CHAOS:
- case ARES_CLASS_HESOID:
+ case ARES_CLASS_HESIOD:
case ARES_CLASS_NONE:
return ARES_TRUE;
case ARES_CLASS_ANY:
@@ -228,7 +228,7 @@ const char *ares_dns_rec_type_tostr(ares_dns_rec_type_t type)
case ARES_REC_TYPE_CAA:
return "CAA";
case ARES_REC_TYPE_RAW_RR:
- return "RAWRR";
+ return "RAW_RR";
}
return "UNKNOWN";
}
@@ -240,7 +240,7 @@ const char *ares_dns_class_tostr(ares_dns_class_t qclass)
return "IN";
case ARES_CLASS_CHAOS:
return "CH";
- case ARES_CLASS_HESOID:
+ case ARES_CLASS_HESIOD:
return "HS";
case ARES_CLASS_ANY:
return "ANY";
@@ -670,7 +670,7 @@ ares_bool_t ares_dns_class_fromstr(ares_dns_class_t *qclass, const char *str)
} list[] = {
{ "IN", ARES_CLASS_IN },
{ "CH", ARES_CLASS_CHAOS },
- { "HS", ARES_CLASS_HESOID },
+ { "HS", ARES_CLASS_HESIOD },
{ "NONE", ARES_CLASS_NONE },
{ "ANY", ARES_CLASS_ANY },
{ NULL, 0 }
diff --git a/deps/cares/src/lib/record/ares_dns_multistring.c b/deps/cares/src/lib/record/ares_dns_multistring.c
index 44fcaccd65bb6a..a2a7adc4e4527f 100644
--- a/deps/cares/src/lib/record/ares_dns_multistring.c
+++ b/deps/cares/src/lib/record/ares_dns_multistring.c
@@ -227,7 +227,9 @@ const unsigned char *ares_dns_multistring_combined(ares_dns_multistring_t *strs,
strs->cache_str =
(unsigned char *)ares_buf_finish_str(buf, &strs->cache_str_len);
- if (strs->cache_str != NULL) {
+ if (strs->cache_str == NULL) {
+ ares_buf_destroy(buf);
+ } else {
strs->cache_invalidated = ARES_FALSE;
}
*len = strs->cache_str_len;
diff --git a/deps/cares/src/lib/record/ares_dns_name.c b/deps/cares/src/lib/record/ares_dns_name.c
index a9b92e03ca9353..ffcaf5e110d547 100644
--- a/deps/cares/src/lib/record/ares_dns_name.c
+++ b/deps/cares/src/lib/record/ares_dns_name.c
@@ -25,6 +25,18 @@
*/
#include "ares_private.h"
+/* RFC 1035 3.1 limits a name to 255 octets. We track presentation length
+ * (label octets plus one separator before each label after the first), which is
+ * up to ~2 octets looser than the strict wire limit and so never rejects a
+ * compliant name. Shared by the read and write paths. */
+#define ARES_MAX_NAME_PRESENTATION_LEN 255
+
+/* A name of <= 255 octets holds at most 128 labels, so it can never legitimately
+ * require more than that many compression-pointer jumps. Bounding the number of
+ * indirections stops a name built purely from pointers (which never adds label
+ * bytes, so the length cap never fires) from being walked without limit. */
+#define ARES_MAX_INDIRS 128
+
typedef struct {
char *name;
size_t name_len;
@@ -48,10 +60,18 @@ static ares_status_t ares_nameoffset_create(ares_llist_t **list,
ares_nameoffset_t *off = NULL;
if (list == NULL || name == NULL || ares_strlen(name) == 0 ||
- ares_strlen(name) > 255) {
+ ares_strlen(name) > ARES_MAX_NAME_PRESENTATION_LEN) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
+ /* Don't record a name as a compression target if it can't be referenced by
+ * a pointer. DNS name compression offsets are only 14 bits (RFC 1035
+ * 4.1.4), so a name positioned beyond 0x3FFF would have its offset
+ * truncated when written, producing a pointer to the wrong location. */
+ if (idx > 0x3FFF) {
+ return ARES_SUCCESS;
+ }
+
if (*list == NULL) {
*list = ares_llist_create(ares_nameoffset_free);
}
@@ -65,7 +85,12 @@ static ares_status_t ares_nameoffset_create(ares_llist_t **list,
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
- off->name = ares_strdup(name);
+ off->name = ares_strdup(name);
+ if (off->name == NULL) {
+ status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+
off->name_len = ares_strlen(off->name);
off->idx = idx;
@@ -335,7 +360,8 @@ static ares_status_t ares_split_dns_name(ares_array_t *labels,
}
/* Can't exceed maximum (unescaped) length */
- if (ares_array_len(labels) && total_len + ares_array_len(labels) - 1 > 255) {
+ if (ares_array_len(labels) &&
+ total_len + ares_array_len(labels) - 1 > ARES_MAX_NAME_PRESENTATION_LEN) {
status = ARES_EBADNAME;
goto done;
}
@@ -419,6 +445,9 @@ ares_status_t ares_dns_name_write(ares_buf_t *buf, ares_llist_t **list,
/* Output name compression offset jump */
if (off != NULL) {
+ /* off->idx is guaranteed to fit in 14 bits: ares_nameoffset_create()
+ * refuses to record offsets beyond 0x3FFF, so the mask below never
+ * actually truncates. */
unsigned short u16 =
(unsigned short)0xC000 | (unsigned short)(off->idx & 0x3FFF);
status = ares_buf_append_be16(buf, u16);
@@ -531,13 +560,16 @@ static ares_status_t ares_fetch_dnsname_into_buf(ares_buf_t *buf,
}
ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
- ares_bool_t is_hostname)
+ ares_bool_t is_hostname,
+ ares_bool_t allow_compression)
{
size_t save_offset = 0;
unsigned char c;
ares_status_t status;
ares_buf_t *namebuf = NULL;
size_t label_start = ares_buf_get_position(buf);
+ size_t name_len = 0;
+ size_t indir = 0;
if (buf == NULL) {
return ARES_EFORMERR;
@@ -588,6 +620,13 @@ ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
*/
size_t offset = (size_t)((c & 0x3F) << 8);
+ /* Some RR types (e.g. SRV) do not permit name compression within their
+ * RDATA. Reject a compression pointer when it is not allowed. */
+ if (!allow_compression) {
+ status = ARES_EBADNAME;
+ goto fail;
+ }
+
/* Fetch second byte of the redirect length */
status = ares_buf_fetch_bytes(buf, &c, 1);
if (status != ARES_SUCCESS) {
@@ -609,6 +648,16 @@ ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
goto fail;
}
+ /* Bound the number of indirections. A name made purely of pointers never
+ * adds label bytes, so the length cap below can't stop it; the
+ * strictly-decreasing rule alone still allows thousands of jumps per name.
+ * No legitimate <= 255 octet name needs more than 128 labels/jumps. */
+ indir++;
+ if (indir > ARES_MAX_INDIRS) {
+ status = ARES_EBADNAME;
+ goto fail;
+ }
+
/* First time we make a jump, save the current position */
if (save_offset == 0) {
save_offset = ares_buf_get_position(buf);
@@ -632,6 +681,20 @@ ares_status_t ares_dns_name_parse(ares_buf_t *buf, char **name,
/* New label */
+ /* RFC 1035 3.1 limits a name to 255 octets. Enforce it during
+ * decompression so labels reached through a chain of pointers can't expand a
+ * single name without bound. Track the presentation length (label data plus
+ * the separator that precedes each label after the first), matching
+ * ares_split_dns_name() on the write side. */
+ if (name_len) {
+ name_len++;
+ }
+ name_len += c;
+ if (name_len > ARES_MAX_NAME_PRESENTATION_LEN) {
+ status = ARES_EBADNAME;
+ goto fail;
+ }
+
/* Labels are separated by periods */
if (ares_buf_len(namebuf) != 0 && name != NULL) {
status = ares_buf_append_byte(namebuf, '.');
diff --git a/deps/cares/src/lib/record/ares_dns_parse.c b/deps/cares/src/lib/record/ares_dns_parse.c
index 0c545d7aa18ada..9dc28e92d84247 100644
--- a/deps/cares/src/lib/record/ares_dns_parse.c
+++ b/deps/cares/src/lib/record/ares_dns_parse.c
@@ -46,8 +46,14 @@ static ares_status_t ares_dns_parse_and_set_dns_name(ares_buf_t *buf,
{
ares_status_t status;
char *name = NULL;
+ /* Only RR types defined in RFC1035 may use name compression within their
+ * RDATA (RFC3597). Reject compression pointers for any other type (e.g.
+ * SRV per RFC2782) to match the write-side policy and avoid following
+ * pointers that a non-understanding nameserver could not have rewritten. */
+ ares_bool_t allow_compression =
+ ares_dns_rec_allow_name_comp(ares_dns_rr_get_type(rr));
- status = ares_dns_name_parse(buf, &name, is_hostname);
+ status = ares_dns_name_parse(buf, &name, is_hostname, allow_compression);
if (status != ARES_SUCCESS) {
return status;
}
@@ -514,6 +520,7 @@ static ares_status_t ares_dns_parse_rr_opt(ares_buf_t *buf, ares_dns_rr_t *rr,
status = ares_dns_rr_set_opt_own(rr, ARES_RR_OPT_OPTIONS, opt, val, len);
if (status != ARES_SUCCESS) {
+ ares_free(val);
return status;
}
}
@@ -607,6 +614,7 @@ static ares_status_t ares_dns_parse_rr_svcb(ares_buf_t *buf, ares_dns_rr_t *rr,
status = ares_dns_rr_set_opt_own(rr, ARES_RR_SVCB_PARAMS, opt, val, len);
if (status != ARES_SUCCESS) {
+ ares_free(val);
return status;
}
}
@@ -658,6 +666,7 @@ static ares_status_t ares_dns_parse_rr_https(ares_buf_t *buf, ares_dns_rr_t *rr,
status = ares_dns_rr_set_opt_own(rr, ARES_RR_HTTPS_PARAMS, opt, val, len);
if (status != ARES_SUCCESS) {
+ ares_free(val);
return status;
}
}
@@ -765,6 +774,12 @@ static ares_status_t ares_dns_parse_rr_raw_rr(ares_buf_t *buf,
ares_status_t status;
unsigned char *bytes = NULL;
+ /* Can't fail */
+ status = ares_dns_rr_set_u16(rr, ARES_RR_RAW_RR_TYPE, raw_type);
+ if (status != ARES_SUCCESS) {
+ return status;
+ }
+
if (rdlength == 0) {
return ARES_SUCCESS;
}
@@ -774,13 +789,6 @@ static ares_status_t ares_dns_parse_rr_raw_rr(ares_buf_t *buf,
return status;
}
- /* Can't fail */
- status = ares_dns_rr_set_u16(rr, ARES_RR_RAW_RR_TYPE, raw_type);
- if (status != ARES_SUCCESS) {
- ares_free(bytes);
- return status;
- }
-
status = ares_dns_rr_set_bin_own(rr, ARES_RR_RAW_RR_DATA, bytes, rdlength);
if (status != ARES_SUCCESS) {
ares_free(bytes);
@@ -920,30 +928,6 @@ static ares_status_t ares_dns_parse_header(ares_buf_t *buf, unsigned int flags,
(*dnsrec)->raw_rcode = rcode;
- if (*ancount > 0) {
- status =
- ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_ANSWER, *ancount);
- if (status != ARES_SUCCESS) {
- goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
- }
- }
-
- if (*nscount > 0) {
- status =
- ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_AUTHORITY, *nscount);
- if (status != ARES_SUCCESS) {
- goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
- }
- }
-
- if (*arcount > 0) {
- status =
- ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_ADDITIONAL, *arcount);
- if (status != ARES_SUCCESS) {
- goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
- }
- }
-
return ARES_SUCCESS;
fail:
@@ -1032,7 +1016,7 @@ static ares_status_t ares_dns_parse_qd(ares_buf_t *buf,
*/
/* Name */
- status = ares_dns_name_parse(buf, &name, ARES_FALSE);
+ status = ares_dns_name_parse(buf, &name, ARES_FALSE, ARES_TRUE);
if (status != ARES_SUCCESS) {
goto done;
}
@@ -1103,7 +1087,7 @@ static ares_status_t ares_dns_parse_rr(ares_buf_t *buf, unsigned int flags,
*/
/* Name */
- status = ares_dns_name_parse(buf, &name, ARES_FALSE);
+ status = ares_dns_name_parse(buf, &name, ARES_FALSE, ARES_TRUE);
if (status != ARES_SUCCESS) {
goto done;
}
@@ -1208,6 +1192,8 @@ static ares_status_t ares_dns_parse_buf(ares_buf_t *buf, unsigned int flags,
ares_dns_record_t **dnsrec)
{
ares_status_t status;
+ size_t total_rr_count;
+ const size_t min_rr_wire_len = 11;
unsigned short qdcount;
unsigned short ancount;
unsigned short nscount;
@@ -1268,6 +1254,35 @@ static ares_status_t ares_dns_parse_buf(ares_buf_t *buf, unsigned int flags,
}
}
+ total_rr_count = (size_t)ancount + (size_t)nscount + (size_t)arcount;
+ if (total_rr_count > ares_buf_len(buf) / min_rr_wire_len) {
+ status = ARES_EBADRESP;
+ goto fail;
+ }
+
+ if (ancount > 0) {
+ status = ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_ANSWER, ancount);
+ if (status != ARES_SUCCESS) {
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ }
+
+ if (nscount > 0) {
+ status =
+ ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_AUTHORITY, nscount);
+ if (status != ARES_SUCCESS) {
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ }
+
+ if (arcount > 0) {
+ status =
+ ares_dns_record_rr_prealloc(*dnsrec, ARES_SECTION_ADDITIONAL, arcount);
+ if (status != ARES_SUCCESS) {
+ goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
+ }
+ }
+
/* Parse Answers */
for (i = 0; i < ancount; i++) {
status = ares_dns_parse_rr(buf, flags, ARES_SECTION_ANSWER, *dnsrec);
@@ -1292,6 +1307,22 @@ static ares_status_t ares_dns_parse_buf(ares_buf_t *buf, unsigned int flags,
}
}
+ /* RFC 6891 6.1.1: a message MUST NOT contain more than one OPT RR. Reject
+ * any response carrying multiple OPT records in the additional section. */
+ {
+ size_t rr_cnt = ares_dns_record_rr_cnt(*dnsrec, ARES_SECTION_ADDITIONAL);
+ size_t opt_cnt = 0;
+ size_t j;
+ for (j = 0; j < rr_cnt; j++) {
+ const ares_dns_rr_t *rr =
+ ares_dns_record_rr_get_const(*dnsrec, ARES_SECTION_ADDITIONAL, j);
+ if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT && ++opt_cnt > 1) {
+ status = ARES_EBADRESP;
+ goto fail;
+ }
+ }
+ }
+
/* Finalize rcode now that if we have OPT it is processed */
if (!ares_dns_rcode_isvalid((*dnsrec)->raw_rcode)) {
(*dnsrec)->rcode = ARES_RCODE_SERVFAIL;
diff --git a/deps/cares/src/lib/record/ares_dns_record.c b/deps/cares/src/lib/record/ares_dns_record.c
index ec0dfbd13c49f3..2c0dadd084ca66 100644
--- a/deps/cares/src/lib/record/ares_dns_record.c
+++ b/deps/cares/src/lib/record/ares_dns_record.c
@@ -929,7 +929,7 @@ ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
ares_dns_datatype_t datatype = ares_dns_rr_key_datatype(key);
ares_bool_t is_nullterm =
(datatype == ARES_DATATYPE_ABINP) ? ARES_TRUE : ARES_FALSE;
- size_t alloclen = is_nullterm ? len + 1 : len;
+ size_t alloclen;
unsigned char *temp;
ares_dns_multistring_t **strs;
@@ -937,6 +937,15 @@ ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
return ARES_EFORMERR;
}
+ if (val == NULL && len != 0) {
+ return ARES_EFORMERR;
+ }
+
+ if (is_nullterm && len == SIZE_MAX) {
+ return ARES_ENOMEM;
+ }
+ alloclen = is_nullterm ? len + 1 : len;
+
strs = ares_dns_rr_data_ptr(dns_rr, key, NULL);
if (strs == NULL) {
return ARES_EFORMERR;
@@ -954,7 +963,9 @@ ares_status_t ares_dns_rr_add_abin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
return ARES_ENOMEM;
}
- memcpy(temp, val, len);
+ if (len != 0) {
+ memcpy(temp, val, len);
+ }
/* NULL-term ABINP */
if (is_nullterm) {
@@ -1237,14 +1248,32 @@ ares_status_t ares_dns_rr_set_bin(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
(datatype == ARES_DATATYPE_BINP || datatype == ARES_DATATYPE_ABINP)
? ARES_TRUE
: ARES_FALSE;
- size_t alloclen = is_nullterm ? len + 1 : len;
- unsigned char *temp = ares_malloc(alloclen);
+ size_t alloclen;
+ unsigned char *temp;
+
+ if (datatype != ARES_DATATYPE_BIN && datatype != ARES_DATATYPE_BINP &&
+ datatype != ARES_DATATYPE_ABINP) {
+ return ARES_EFORMERR;
+ }
+
+ if (val == NULL && len != 0) {
+ return ARES_EFORMERR;
+ }
+
+ if (is_nullterm && len == SIZE_MAX) {
+ return ARES_ENOMEM;
+ }
+ alloclen = is_nullterm ? len + 1 : len;
+
+ temp = ares_malloc(alloclen);
if (temp == NULL) {
return ARES_ENOMEM;
}
- memcpy(temp, val, len);
+ if (len != 0) {
+ memcpy(temp, val, len);
+ }
/* NULL-term BINP */
if (is_nullterm) {
@@ -1400,12 +1429,23 @@ ares_status_t ares_dns_rr_set_opt(ares_dns_rr_t *dns_rr, ares_dns_rr_key_t key,
ares_status_t status;
if (val != NULL) {
- temp = ares_malloc(val_len + 1);
+ size_t alloclen;
+
+ if (val_len == SIZE_MAX) {
+ return ARES_ENOMEM;
+ }
+ alloclen = val_len + 1;
+
+ temp = ares_malloc(alloclen);
if (temp == NULL) {
return ARES_ENOMEM;
}
- memcpy(temp, val, val_len);
+ if (val_len != 0) {
+ memcpy(temp, val, val_len);
+ }
temp[val_len] = 0;
+ } else if (val_len != 0) {
+ return ARES_EFORMERR;
}
status = ares_dns_rr_set_opt_own(dns_rr, key, opt, temp, val_len);
diff --git a/deps/cares/src/lib/str/ares_buf.c b/deps/cares/src/lib/str/ares_buf.c
index 63acc6cf7714d3..25f0372e4dbdcc 100644
--- a/deps/cares/src/lib/str/ares_buf.c
+++ b/deps/cares/src/lib/str/ares_buf.c
@@ -139,6 +139,7 @@ static ares_status_t ares_buf_ensure_space(ares_buf_t *buf, size_t needed_size)
{
size_t remaining_size;
size_t alloc_size;
+ size_t total_required;
unsigned char *ptr;
if (buf == NULL) {
@@ -151,9 +152,20 @@ static ares_status_t ares_buf_ensure_space(ares_buf_t *buf, size_t needed_size)
/* When calling ares_buf_finish_str() we end up adding a null terminator,
* so we want to ensure the size is always sufficient for this as we don't
- * want an ARES_ENOMEM at that point */
+ * want an ARES_ENOMEM at that point.
+ */
+ if (buf->data_len >= SIZE_MAX - 1) {
+ return ARES_ENOMEM;
+ }
+
needed_size++;
+ if (needed_size > SIZE_MAX - buf->data_len) {
+ return ARES_ENOMEM;
+ }
+
+ total_required = buf->data_len + needed_size;
+
/* No need to do an expensive move operation, we have enough to just append */
remaining_size = buf->alloc_buf_len - buf->data_len;
if (remaining_size >= needed_size) {
@@ -177,9 +189,11 @@ static ares_status_t ares_buf_ensure_space(ares_buf_t *buf, size_t needed_size)
/* Increase allocation by powers of 2 */
do {
- alloc_size <<= 1;
- remaining_size = alloc_size - buf->data_len;
- } while (remaining_size < needed_size);
+ if (alloc_size > SIZE_MAX >> 1) {
+ return ARES_ENOMEM;
+ }
+ alloc_size <<= 1;
+ } while (alloc_size < total_required);
ptr = ares_realloc(buf->alloc_buf, alloc_size);
if (ptr == NULL) {
@@ -1111,7 +1125,7 @@ ares_status_t ares_buf_replace(ares_buf_t *buf, const unsigned char *srch,
size_t processed_len = 0;
ares_status_t status;
- if (buf->alloc_buf == NULL || srch == NULL || srch_size == 0 ||
+ if (buf == NULL || buf->alloc_buf == NULL || srch == NULL || srch_size == 0 ||
(rplc == NULL && rplc_size != 0)) {
return ARES_EFORMERR;
}
@@ -1131,7 +1145,7 @@ ares_status_t ares_buf_replace(ares_buf_t *buf, const unsigned char *srch,
/* Store the offset this was found because our actual pointer might be
* switched out from under us by the call to ensure_space() if the
* replacement pattern is larger than the search pattern */
- found_offset = (size_t)(ptr - (size_t)(buf->alloc_buf + buf->offset));
+ found_offset = (size_t)(ptr - buf->alloc_buf) - buf->offset;
if (rplc_size > srch_size) {
status = ares_buf_ensure_space(buf, rplc_size - srch_size);
if (status != ARES_SUCCESS) {
@@ -1262,17 +1276,15 @@ static ares_status_t
}
done:
- if (status != ARES_SUCCESS) {
- ares_buf_destroy(binbuf);
+ if (status == ARES_SUCCESS && bin != NULL) {
+ size_t mylen = 0;
+ /* NOTE: we use ares_buf_finish_str() here as we guarantee NULL
+ * Termination even though we are technically returning binary data.
+ */
+ *bin = (unsigned char *)ares_buf_finish_str(binbuf, &mylen);
+ *bin_len = mylen;
} else {
- if (bin != NULL) {
- size_t mylen = 0;
- /* NOTE: we use ares_buf_finish_str() here as we guarantee NULL
- * Termination even though we are technically returning binary data.
- */
- *bin = (unsigned char *)ares_buf_finish_str(binbuf, &mylen);
- *bin_len = mylen;
- }
+ ares_buf_destroy(binbuf);
}
return status;
diff --git a/deps/cares/src/lib/str/ares_str.c b/deps/cares/src/lib/str/ares_str.c
index 0eda1ab9f15783..0819dc57849049 100644
--- a/deps/cares/src/lib/str/ares_str.c
+++ b/deps/cares/src/lib/str/ares_str.c
@@ -28,6 +28,9 @@
#include "ares_private.h"
#include "ares_str.h"
+#include
+#include
+
#ifdef HAVE_STDINT_H
# include
#endif
@@ -125,6 +128,57 @@ ares_bool_t ares_str_isnum(const char *str)
return ARES_TRUE;
}
+ares_bool_t ares_str_parse_uint(const char *str, unsigned long max,
+ unsigned int *out)
+{
+ char *end = NULL;
+ unsigned long val;
+
+ /* Require a leading digit so strtoul()'s tolerance of a sign or leading
+ * whitespace (e.g. "-1" wrapping to UINT_MAX) can't slip through here. This
+ * also rejects NULL and empty strings. */
+ if (str == NULL || out == NULL || !ares_isdigit(*str)) {
+ return ARES_FALSE;
+ }
+
+ /* out is unsigned int, so a max above UINT_MAX would let strtoul's result
+ * truncate silently on assignment. Cap it. */
+ if (max > UINT_MAX) {
+ max = UINT_MAX;
+ }
+
+ errno = 0;
+ val = strtoul(str, &end, 10);
+ if (errno == ERANGE || *end != '\0' || val > max) {
+ return ARES_FALSE;
+ }
+
+ *out = (unsigned int)val;
+ return ARES_TRUE;
+}
+
+ares_bool_t ares_parse_port(const char *str, unsigned short *port,
+ ares_bool_t allow_zero)
+{
+ unsigned int val;
+
+ if (port == NULL) {
+ return ARES_FALSE;
+ }
+
+ if (!ares_str_parse_uint(str, 65535UL, &val)) {
+ return ARES_FALSE;
+ }
+
+ if (!allow_zero && val == 0) {
+ return ARES_FALSE;
+ }
+
+ *port = (unsigned short)val;
+
+ return ARES_TRUE;
+}
+
ares_bool_t ares_str_isalnum(const char *str)
{
size_t i;
diff --git a/deps/cares/src/lib/util/ares_iface_ips.c b/deps/cares/src/lib/util/ares_iface_ips.c
index c5f507f87e1476..66d1fba84d4bce 100644
--- a/deps/cares/src/lib/util/ares_iface_ips.c
+++ b/deps/cares/src/lib/util/ares_iface_ips.c
@@ -25,6 +25,8 @@
*/
#include "ares_private.h"
+#include
+
#ifdef USE_WINSOCK
# include
# include
@@ -256,12 +258,12 @@ ares_iface_ip_flags_t ares_iface_ips_get_flags(const ares_iface_ips_t *ips,
const ares_iface_ip_t *ip;
if (ips == NULL) {
- return 0;
+ return ARES_IFACE_IP_NONE;
}
ip = ares_array_at_const(ips->ips, idx);
if (ip == NULL) {
- return 0;
+ return ARES_IFACE_IP_NONE;
}
return ip->flags;
@@ -329,6 +331,8 @@ static char *wcharp_to_charp(const wchar_t *in)
static ares_bool_t name_match(const char *name, const char *adapter_name,
unsigned int ll_scope)
{
+ unsigned int scope;
+
if (name == NULL || *name == 0) {
return ARES_TRUE;
}
@@ -337,7 +341,7 @@ static ares_bool_t name_match(const char *name, const char *adapter_name,
return ARES_TRUE;
}
- if (ares_str_isnum(name) && (unsigned int)atoi(name) == ll_scope) {
+ if (ares_str_parse_uint(name, UINT_MAX, &scope) && scope == ll_scope) {
return ARES_TRUE;
}
@@ -376,7 +380,7 @@ static ares_status_t ares_iface_ips_enumerate(ares_iface_ips_t *ips,
for (address = addresses; address != NULL; address = address->Next) {
IP_ADAPTER_UNICAST_ADDRESS *ipaddr = NULL;
- ares_iface_ip_flags_t addrflag = 0;
+ ares_iface_ip_flags_t addrflag = ARES_IFACE_IP_NONE;
char ifname[64] = "";
# if defined(HAVE_CONVERTINTERFACEINDEXTOLUID) && \
@@ -477,7 +481,7 @@ static ares_status_t ares_iface_ips_enumerate(ares_iface_ips_t *ips,
}
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
- ares_iface_ip_flags_t addrflag = 0;
+ ares_iface_ip_flags_t addrflag = ARES_IFACE_IP_NONE;
struct ares_addr addr;
unsigned char netmask = 0;
unsigned int ll_scope = 0;
@@ -500,20 +504,28 @@ static ares_status_t ares_iface_ips_enumerate(ares_iface_ips_t *ips,
addr.family = AF_INET;
memcpy(&addr.addr.addr4, &sockaddr_in->sin_addr, sizeof(addr.addr.addr4));
/* netmask */
- sockaddr_in = (struct sockaddr_in *)((void *)ifa->ifa_netmask);
- netmask = count_addr_bits((const void *)&sockaddr_in->sin_addr, 4);
+ if (ifa->ifa_netmask != NULL) {
+ sockaddr_in = (struct sockaddr_in *)((void *)ifa->ifa_netmask);
+ netmask = count_addr_bits((const void *)&sockaddr_in->sin_addr, 4);
+ } else {
+ netmask = 32;
+ }
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
const struct sockaddr_in6 *sockaddr_in6 =
(const struct sockaddr_in6 *)((void *)ifa->ifa_addr);
addr.family = AF_INET6;
memcpy(&addr.addr.addr6, &sockaddr_in6->sin6_addr,
sizeof(addr.addr.addr6));
- /* netmask */
- sockaddr_in6 = (struct sockaddr_in6 *)((void *)ifa->ifa_netmask);
- netmask = count_addr_bits((const void *)&sockaddr_in6->sin6_addr, 16);
# ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
ll_scope = sockaddr_in6->sin6_scope_id;
# endif
+ /* netmask */
+ if (ifa->ifa_netmask != NULL) {
+ sockaddr_in6 = (struct sockaddr_in6 *)((void *)ifa->ifa_netmask);
+ netmask = count_addr_bits((const void *)&sockaddr_in6->sin6_addr, 16);
+ } else {
+ netmask = 128;
+ }
} else {
/* unknown */
continue;
diff --git a/deps/cares/src/lib/util/ares_iface_ips.h b/deps/cares/src/lib/util/ares_iface_ips.h
index f22e09046a065b..ec2353448fb0f5 100644
--- a/deps/cares/src/lib/util/ares_iface_ips.h
+++ b/deps/cares/src/lib/util/ares_iface_ips.h
@@ -28,6 +28,7 @@
/*! Flags for interface ip addresses. */
typedef enum {
+ ARES_IFACE_IP_NONE = 0, /*!< No flags set / unspecified */
ARES_IFACE_IP_V4 = 1 << 0, /*!< IPv4 address. During enumeration if
* this flag is set ARES_IFACE_IP_V6
* is not, will only enumerate v4
diff --git a/deps/cares/src/lib/util/ares_math.c b/deps/cares/src/lib/util/ares_math.c
index 1106bf6bf151f8..3513146c3ddc4a 100644
--- a/deps/cares/src/lib/util/ares_math.c
+++ b/deps/cares/src/lib/util/ares_math.c
@@ -42,7 +42,7 @@ static unsigned int ares_round_up_pow2_u32(unsigned int n)
return n;
}
-static ares_int64_t ares_round_up_pow2_u64(ares_int64_t n)
+static ares_uint64_t ares_round_up_pow2_u64(ares_uint64_t n)
{
/* NOTE: if already a power of 2, will return itself, not the next */
n--;
@@ -73,7 +73,7 @@ ares_bool_t ares_is_64bit(void)
size_t ares_round_up_pow2(size_t n)
{
if (ares_is_64bit()) {
- return (size_t)ares_round_up_pow2_u64((ares_int64_t)n);
+ return (size_t)ares_round_up_pow2_u64((ares_uint64_t)n);
}
return (size_t)ares_round_up_pow2_u32((unsigned int)n);
@@ -156,3 +156,12 @@ unsigned char ares_count_bits_u8(unsigned char x)
static const unsigned char lookup[256] = { B6(0), B6(1), B6(1), B6(2) };
return lookup[x];
}
+
+ares_bool_t ares_size_t_mul_overflow(size_t a, size_t b, size_t *res)
+{
+ if (a > 0 && b > SIZE_MAX / a) {
+ return ARES_TRUE;
+ }
+ *res = a * b;
+ return ARES_FALSE;
+}
diff --git a/deps/cares/src/lib/util/ares_math.h b/deps/cares/src/lib/util/ares_math.h
index 3b60b00bdbb6ce..76e1e488370984 100644
--- a/deps/cares/src/lib/util/ares_math.h
+++ b/deps/cares/src/lib/util/ares_math.h
@@ -50,4 +50,9 @@ size_t ares_count_digits(size_t n);
size_t ares_count_hexdigits(size_t n);
unsigned char ares_count_bits_u8(unsigned char x);
+/*! Multiply two size_t values, checking for overflow. On success writes the
+ * product to *res and returns ARES_FALSE. On overflow returns ARES_TRUE and
+ * leaves *res untouched. */
+ares_bool_t ares_size_t_mul_overflow(size_t a, size_t b, size_t *res);
+
#endif
diff --git a/deps/cares/src/lib/util/ares_threads.c b/deps/cares/src/lib/util/ares_threads.c
index ab0b51afb70577..b27b7ebd70005f 100644
--- a/deps/cares/src/lib/util/ares_threads.c
+++ b/deps/cares/src/lib/util/ares_threads.c
@@ -563,8 +563,7 @@ ares_status_t ares_queue_wait_empty(ares_channel_t *channel, int timeout_ms)
if (timeout_ms >= 0) {
ares_tvnow(&tout);
- tout.sec += (ares_int64_t)(timeout_ms / 1000);
- tout.usec += (unsigned int)(timeout_ms % 1000) * 1000;
+ ares_timeval_add(&tout, (size_t)timeout_ms);
}
ares_thread_mutex_lock(channel->lock);
diff --git a/deps/cares/src/lib/util/ares_time.h b/deps/cares/src/lib/util/ares_time.h
index c6eaf97366379e..ecb721de188719 100644
--- a/deps/cares/src/lib/util/ares_time.h
+++ b/deps/cares/src/lib/util/ares_time.h
@@ -39,6 +39,7 @@ ares_bool_t ares_timedout(const ares_timeval_t *now,
const ares_timeval_t *check);
void ares_tvnow(ares_timeval_t *now);
+void ares_timeval_add(ares_timeval_t *now, size_t millisecs);
void ares_timeval_remaining(ares_timeval_t *remaining,
const ares_timeval_t *now,
const ares_timeval_t *tout);
diff --git a/deps/cares/src/lib/util/ares_uri.c b/deps/cares/src/lib/util/ares_uri.c
index 04bad0074a79e2..97f3f3f542a7cf 100644
--- a/deps/cares/src/lib/util/ares_uri.c
+++ b/deps/cares/src/lib/util/ares_uri.c
@@ -1174,6 +1174,7 @@ static ares_status_t ares_uri_parse_hostport(ares_uri_t *uri, ares_buf_t *buf)
unsigned char b;
char host[256];
char port[6];
+ unsigned short parsed_port;
size_t len;
ares_status_t status;
@@ -1242,11 +1243,11 @@ static ares_status_t ares_uri_parse_hostport(ares_uri_t *uri, ares_buf_t *buf)
}
port[len] = 0;
- if (!ares_str_isnum(port)) {
+ if (!ares_parse_port(port, &parsed_port, ARES_TRUE)) {
return ARES_EBADSTR;
}
- status = ares_uri_set_port(uri, (unsigned short)atoi(port));
+ status = ares_uri_set_port(uri, parsed_port);
if (status != ARES_SUCCESS) {
return status;
}
diff --git a/deps/cares/src/tools/adig.c b/deps/cares/src/tools/adig.c
index fce210a8053578..29f359a39287c1 100644
--- a/deps/cares/src/tools/adig.c
+++ b/deps/cares/src/tools/adig.c
@@ -1162,12 +1162,12 @@ static ares_bool_t read_cmdline(int argc, const char * const *argv,
/* skip prefix */
if (dig_options[opt].prefix != 0) {
nameptr++;
- }
-
- /* Negated option if it has a 'no' prefix */
- if (ares_streq_max(nameptr, "no", 2)) {
- is_true = ARES_FALSE;
- nameptr += 2;
+ /* Negated option if it has a 'no' prefix */
+ if (dig_options[opt].prefix == '+' &&
+ ares_streq_max(nameptr, "no", 2)) {
+ is_true = ARES_FALSE;
+ nameptr += 2;
+ }
}
if (dig_options[opt].separator != 0) {
diff --git a/deps/cares/src/tools/ahost.c b/deps/cares/src/tools/ahost.c
index 7d1d4a86dc7a2d..67b5677cad6e25 100644
--- a/deps/cares/src/tools/ahost.c
+++ b/deps/cares/src/tools/ahost.c
@@ -44,6 +44,13 @@
#include "ares_str.h"
+#define RV_OK 0 /* Success */
+#define RV_SYSERR 1 /* Internal system failure */
+#define RV_MISUSE 2 /* Misuse (command line) */
+#define RV_FAIL 3 /* Resolution failure */
+static int final_rv = RV_OK;
+
+
static void callback(void *arg, int status, int timeouts, struct hostent *host);
static void ai_callback(void *arg, int status, int timeouts,
struct ares_addrinfo *result);
diff --git a/deps/ncrypto/engine.cc b/deps/ncrypto/engine.cc
index 1845cfcca47aa4..a8e64e25049183 100644
--- a/deps/ncrypto/engine.cc
+++ b/deps/ncrypto/engine.cc
@@ -1,12 +1,18 @@
#include "ncrypto.h"
+#if !defined(OPENSSL_NO_ENGINE) && \
+ ((defined(NCRYPTO_ENGINE_COMPAT) && NCRYPTO_ENGINE_COMPAT) || \
+ NCRYPTO_USE_LEGACY_OPENSSL)
+#include
+#endif
+
namespace ncrypto {
// ============================================================================
// Engine
#ifndef OPENSSL_NO_ENGINE
-EnginePointer::EnginePointer(ENGINE* engine_, bool finish_on_exit_)
+EnginePointer::EnginePointer(void* engine_, bool finish_on_exit_)
: engine(engine_), finish_on_exit(finish_on_exit_) {}
EnginePointer::EnginePointer(EnginePointer&& other) noexcept
@@ -24,21 +30,22 @@ EnginePointer& EnginePointer::operator=(EnginePointer&& other) noexcept {
return *new (this) EnginePointer(std::move(other));
}
-void EnginePointer::reset(ENGINE* engine_, bool finish_on_exit_) {
+void EnginePointer::reset(void* engine_, bool finish_on_exit_) {
if (engine != nullptr) {
+ ENGINE* current = static_cast(engine);
if (finish_on_exit) {
// This also does the equivalent of ENGINE_free.
- ENGINE_finish(engine);
+ ENGINE_finish(current);
} else {
- ENGINE_free(engine);
+ ENGINE_free(current);
}
}
engine = engine_;
finish_on_exit = finish_on_exit_;
}
-ENGINE* EnginePointer::release() {
- ENGINE* ret = engine;
+void* EnginePointer::release() {
+ void* ret = engine;
engine = nullptr;
finish_on_exit = false;
return ret;
@@ -52,8 +59,9 @@ EnginePointer EnginePointer::getEngineByName(const char* name,
// Engine not found, try loading dynamically.
engine = EnginePointer(ENGINE_by_id("dynamic"));
if (engine) {
- if (!ENGINE_ctrl_cmd_string(engine.get(), "SO_PATH", name, 0) ||
- !ENGINE_ctrl_cmd_string(engine.get(), "LOAD", nullptr, 0)) {
+ ENGINE* current = static_cast(engine.engine);
+ if (!ENGINE_ctrl_cmd_string(current, "SO_PATH", name, 0) ||
+ !ENGINE_ctrl_cmd_string(current, "LOAD", nullptr, 0)) {
engine.reset();
}
}
@@ -64,19 +72,24 @@ EnginePointer EnginePointer::getEngineByName(const char* name,
bool EnginePointer::setAsDefault(uint32_t flags, CryptoErrorList* errors) {
if (engine == nullptr) return false;
ClearErrorOnReturn clear_error_on_return(errors);
- return ENGINE_set_default(engine, flags) != 0;
+ return ENGINE_set_default(static_cast(engine), flags) != 0;
}
bool EnginePointer::init(bool finish_on_exit) {
if (engine == nullptr) return false;
if (finish_on_exit) setFinishOnExit();
- return ENGINE_init(engine) == 1;
+ return ENGINE_init(static_cast(engine)) == 1;
}
EVPKeyPointer EnginePointer::loadPrivateKey(const char* key_name) {
if (engine == nullptr) return EVPKeyPointer();
- return EVPKeyPointer(
- ENGINE_load_private_key(engine, key_name, nullptr, nullptr));
+ return EVPKeyPointer(ENGINE_load_private_key(
+ static_cast(engine), key_name, nullptr, nullptr));
+}
+
+bool EnginePointer::setClientCertEngine(SSL_CTX* ctx) {
+ if (engine == nullptr || ctx == nullptr) return false;
+ return SSL_CTX_set_client_cert_engine(ctx, static_cast(engine)) == 1;
}
void EnginePointer::initEnginesOnce() {
diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index d62485e626c158..771589ed69b421 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -14,6 +14,7 @@
#endif
#include
#include
+#include
#include
#include
#if OPENSSL_VERSION_MAJOR >= 3
@@ -75,9 +76,209 @@ using BignumCtxPointer = DeleteFnPtr;
using BignumGenCallbackPointer = DeleteFnPtr;
using NetscapeSPKIPointer = DeleteFnPtr;
+const EVP_CIPHER* GetCipherCtxCipher(const EVP_CIPHER_CTX* ctx) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return EVP_CIPHER_CTX_get0_cipher(ctx);
+#else
+ return EVP_CIPHER_CTX_cipher(ctx);
+#endif
+}
+
+const EVP_MD* GetDigestCtxMd(const EVP_MD_CTX* ctx) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER || NCRYPTO_USE_BORINGSSL
+ return EVP_MD_CTX_get0_md(ctx);
+#else
+ return EVP_MD_CTX_md(ctx);
+#endif
+}
+
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+using OSSLParamBldPointer = DeleteFnPtr;
+using OSSLParamPointer = DeleteFnPtr;
+struct OpenSSLBufferDeleter {
+ void operator()(unsigned char* pointer) const { OPENSSL_free(pointer); }
+};
+using OpenSSLBufferPointer =
+ std::unique_ptr;
+#endif
+
static constexpr int kX509NameFlagsRFC2253WithinUtf8JSON =
XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB & ~ASN1_STRFLGS_ESC_CTRL;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+bool GetPKeyBnParam(const EVP_PKEY* pkey,
+ const char* name,
+ DeleteFnPtr* out) {
+ BIGNUM* bn = nullptr;
+ if (pkey == nullptr) return false;
+ if (EVP_PKEY_get_bn_param(pkey, name, &bn) == 1) {
+ out->reset(bn);
+ return true;
+ }
+
+ size_t len = 0;
+ if (EVP_PKEY_get_octet_string_param(pkey, name, nullptr, 0, &len) != 1) {
+ return false;
+ }
+ auto data = DataPointer::Alloc(len);
+ if (!data ||
+ EVP_PKEY_get_octet_string_param(pkey,
+ name,
+ static_cast(data.get()),
+ data.size(),
+ &len) != 1) {
+ return false;
+ }
+ bn = BN_bin2bn(static_cast(data.get()), len, nullptr);
+ if (bn == nullptr) return false;
+ out->reset(bn);
+ return true;
+}
+
+bool GetOptionalPKeyBnParam(const EVP_PKEY* pkey,
+ const char* name,
+ DeleteFnPtr* out) {
+ BIGNUM* bn = nullptr;
+ if (pkey == nullptr) {
+ out->reset();
+ return true;
+ }
+ if (EVP_PKEY_get_bn_param(pkey, name, &bn) == 1) {
+ out->reset(bn);
+ return true;
+ }
+
+ size_t len = 0;
+ if (EVP_PKEY_get_octet_string_param(pkey, name, nullptr, 0, &len) == 1) {
+ auto data = DataPointer::Alloc(len);
+ if (!data ||
+ EVP_PKEY_get_octet_string_param(pkey,
+ name,
+ static_cast(data.get()),
+ data.size(),
+ &len) != 1) {
+ return false;
+ }
+ bn = BN_bin2bn(static_cast(data.get()), len, nullptr);
+ if (bn == nullptr) return false;
+ out->reset(bn);
+ return true;
+ }
+
+ out->reset();
+ return true;
+}
+
+EVPKeyPointer NewPKeyFromData(int id, int selection, OSSL_PARAM* params) {
+ auto ctx = EVPKeyCtxPointer::NewFromID(id);
+ if (!ctx || EVP_PKEY_fromdata_init(ctx.get()) != 1) return {};
+
+ EVP_PKEY* pkey = nullptr;
+ if (EVP_PKEY_fromdata(ctx.get(), &pkey, selection, params) != 1) {
+ return {};
+ }
+ return EVPKeyPointer(pkey);
+}
+
+EVPKeyPointer NewDhPKey(const BIGNUM* p,
+ const BIGNUM* g,
+ const BIGNUM* pub = nullptr,
+ const BIGNUM* priv = nullptr) {
+ if (p == nullptr || g == nullptr) return {};
+
+ OSSLParamBldPointer bld(OSSL_PARAM_BLD_new());
+ if (!bld ||
+ OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_FFC_P, p) != 1 ||
+ OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_FFC_G, g) != 1) {
+ return {};
+ }
+
+ int selection = EVP_PKEY_KEY_PARAMETERS;
+ if (pub != nullptr) {
+ if (OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_PUB_KEY, pub) != 1) {
+ return {};
+ }
+ selection |= EVP_PKEY_PUBLIC_KEY;
+ }
+ if (priv != nullptr) {
+ if (OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_PRIV_KEY, priv) !=
+ 1) {
+ return {};
+ }
+ selection |= EVP_PKEY_PRIVATE_KEY;
+ }
+
+ OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get()));
+ if (!params) return {};
+ return NewPKeyFromData(EVP_PKEY_DH, selection, params.get());
+}
+
+EVPKeyPointer NewDhPKey(const char* group_name,
+ const BIGNUM* pub = nullptr,
+ const BIGNUM* priv = nullptr) {
+ if (group_name == nullptr) return {};
+
+ if (pub == nullptr && priv == nullptr) {
+ EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new_from_name(nullptr, "DH", nullptr));
+ OSSL_PARAM params[] = {
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_PKEY_PARAM_GROUP_NAME, const_cast(group_name), 0),
+ OSSL_PARAM_END,
+ };
+ if (!ctx || !ctx.initForParamgen() ||
+ EVP_PKEY_CTX_set_params(ctx.get(), params) != 1) {
+ return {};
+ }
+ return ctx.paramgen();
+ }
+
+ OSSLParamBldPointer bld(OSSL_PARAM_BLD_new());
+ if (!bld || OSSL_PARAM_BLD_push_utf8_string(
+ bld.get(), OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1) {
+ return {};
+ }
+
+ int selection = EVP_PKEY_KEY_PARAMETERS;
+ if (pub != nullptr) {
+ if (OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_PUB_KEY, pub) != 1) {
+ return {};
+ }
+ selection |= EVP_PKEY_PUBLIC_KEY;
+ }
+ if (priv != nullptr) {
+ if (OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_PRIV_KEY, priv) !=
+ 1) {
+ return {};
+ }
+ selection |= EVP_PKEY_PRIVATE_KEY;
+ }
+
+ OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get()));
+ if (!params) return {};
+ return NewPKeyFromData(EVP_PKEY_DH, selection, params.get());
+}
+
+bool GetDhParams(const EVP_PKEY* pkey,
+ DeleteFnPtr* p,
+ DeleteFnPtr* g,
+ DeleteFnPtr* q = nullptr,
+ DeleteFnPtr* j = nullptr) {
+ return GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_P, p) &&
+ GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_G, g) &&
+ (q == nullptr ||
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_Q, q)) &&
+ (j == nullptr ||
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_COFACTOR, j));
+}
+
+bool GetDhKeys(const EVP_PKEY* pkey,
+ DeleteFnPtr* pub,
+ DeleteFnPtr* priv) {
+ return GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PUB_KEY, pub) &&
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_PRIV_KEY, priv);
+}
+#endif
+
#if NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK
struct BoringSSLCipher {
const EVP_CIPHER* (*get)();
@@ -500,7 +701,18 @@ int BignumPointer::isPrime(int nchecks,
},
&innerCb);
}
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return BN_check_prime(get(), ctx.get(), cb.get());
+#elif NCRYPTO_USE_BORINGSSL
+ int is_probably_prime = 0;
+ if (BN_primality_test(
+ &is_probably_prime, get(), nchecks, ctx.get(), 0, cb.get()) != 1) {
+ return -1;
+ }
+ return is_probably_prime;
+#else
return BN_is_prime_ex(get(), nchecks, ctx.get(), cb.get());
+#endif
}
BignumPointer BignumPointer::NewPrime(const PrimeConfig& params,
@@ -1389,7 +1601,11 @@ bool X509View::ifRsa(KeyCallback callback) const {
OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_);
auto id = EVP_PKEY_id(pkey);
if (id == EVP_PKEY_RSA || id == EVP_PKEY_RSA2 || id == EVP_PKEY_RSA_PSS) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ Rsa rsa(pkey);
+#else
Rsa rsa(EVP_PKEY_get0_RSA(pkey));
+#endif
if (!rsa) [[unlikely]]
return true;
return callback(rsa);
@@ -1402,7 +1618,11 @@ bool X509View::ifEc(KeyCallback callback) const {
OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert_);
auto id = EVP_PKEY_id(pkey);
if (id == EVP_PKEY_EC) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ Ec ec(pkey);
+#else
Ec ec(EVP_PKEY_get0_EC_KEY(pkey));
+#endif
if (!ec) [[unlikely]]
return true;
return callback(ec);
@@ -1430,7 +1650,11 @@ X509Pointer X509Pointer::IssuerFrom(const SSL_CTX* ctx, const X509View& cert) {
}
X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return X509Pointer(SSL_get1_peer_certificate(ssl.get()));
+#else
return X509Pointer(SSL_get_peer_certificate(ssl.get()));
+#endif
}
// When adding or removing errors below, please also update the list in the API
@@ -1560,11 +1784,198 @@ bool EqualNoCase(const std::string_view a, const std::string_view b) {
return std::tolower(a) == std::tolower(b);
});
}
+
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+const char* GetOpenSSLDhGroupName(const std::string_view name,
+ DHPointer::FindGroupOption option) {
+ if (option != DHPointer::FindGroupOption::NO_SMALL_PRIMES &&
+ EqualNoCase(name, "modp5")) {
+ return "modp_1536";
+ }
+ if (EqualNoCase(name, "modp14")) return "modp_2048";
+ if (EqualNoCase(name, "modp15")) return "modp_3072";
+ if (EqualNoCase(name, "modp16")) return "modp_4096";
+ if (EqualNoCase(name, "modp17")) return "modp_6144";
+ if (EqualNoCase(name, "modp18")) return "modp_8192";
+ return nullptr;
+}
+
+int GetDhGroupPrivateBits(const char* group_name) {
+ if (group_name == nullptr) return 0;
+ if (strcmp(group_name, "modp_1536") == 0) return 200;
+ if (strcmp(group_name, "modp_2048") == 0) return 225;
+ if (strcmp(group_name, "modp_3072") == 0) return 275;
+ if (strcmp(group_name, "modp_4096") == 0) return 325;
+ if (strcmp(group_name, "modp_6144") == 0) return 375;
+ if (strcmp(group_name, "modp_8192") == 0) return 400;
+ return 0;
+}
+
+bool GenerateDhPrivateKey(BignumPointer* out,
+ const BIGNUM* p,
+ const char* group_name) {
+ if (out == nullptr || p == nullptr) return false;
+ auto priv = BignumPointer::NewSecure();
+ if (!priv) return false;
+
+ const int bits = GetDhGroupPrivateBits(group_name);
+ if (bits > 0) {
+ if (BN_priv_rand(priv.get(), bits, BN_RAND_TOP_ONE, BN_RAND_BOTTOM_ANY) !=
+ 1) {
+ return false;
+ }
+ } else {
+ auto range = BignumPointer(BN_dup(p));
+ if (!range || BN_sub_word(range.get(), 3) != 1 ||
+ BN_priv_rand_range(priv.get(), range.get()) != 1 ||
+ BN_add_word(priv.get(), 2) != 1) {
+ return false;
+ }
+ }
+
+ *out = std::move(priv);
+ return true;
+}
+
+// Recompute DH public keys locally when a private key already exists. Provider
+// keygen creates a fresh keypair, which is both slower and changes semantics.
+bool GenerateDhPublicKey(BignumPointer* out,
+ const BIGNUM* p,
+ const BIGNUM* g,
+ const BIGNUM* priv) {
+ if (out == nullptr || p == nullptr || g == nullptr || priv == nullptr) {
+ return false;
+ }
+ auto pub = BignumPointer::New();
+ BignumCtxPointer ctx(BN_CTX_new());
+ if (!pub || !ctx ||
+ BN_mod_exp_mont_consttime(pub.get(), g, priv, p, ctx.get(), nullptr) !=
+ 1) {
+ return false;
+ }
+
+ *out = std::move(pub);
+ return true;
+}
+
+std::optional CheckDhParams(const BIGNUM* p,
+ const BIGNUM* g,
+ const BIGNUM* q,
+ const BIGNUM* j) {
+ // TODO(panva): In a semver-major, consider tightening OpenSSL 3 validation
+ // to report generator and q failures as strictly as legacy DH_check().
+ if (p == nullptr || g == nullptr) return std::nullopt;
+
+ const int p_bits = BN_num_bits(p);
+ if (p_bits > OPENSSL_DH_CHECK_MAX_MODULUS_BITS) return std::nullopt;
+
+ int codes = 0;
+ if (!BN_is_odd(p)) {
+ codes |= static_cast(DHPointer::CheckResult::P_NOT_PRIME);
+ }
+ if (BN_is_negative(g) || BN_is_zero(g) || BN_is_one(g)) {
+ codes |= static_cast(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR);
+ }
+ if (p_bits < 512) {
+ codes |= static_cast(DHPointer::CheckResult::MODULUS_TOO_SMALL);
+ }
+ if (p_bits > OPENSSL_DH_MAX_MODULUS_BITS) {
+ codes |= static_cast(DHPointer::CheckResult::MODULUS_TOO_LARGE);
+ }
+
+ BignumCtxPointer ctx(BN_CTX_new());
+ if (!ctx) return std::nullopt;
+
+ auto tmp1 = BignumPointer::New();
+ auto tmp2 = BignumPointer::New();
+ if (!tmp1 || !tmp2) return std::nullopt;
+
+ if (BN_copy(tmp1.get(), p) == nullptr || BN_sub_word(tmp1.get(), 1) != 1) {
+ return std::nullopt;
+ }
+ if (BN_cmp(g, tmp1.get()) >= 0) {
+ codes |= static_cast(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR);
+ }
+
+ bool q_good = false;
+ if (q != nullptr) {
+ if (BN_ucmp(p, q) > 0) {
+ q_good = true;
+ } else {
+ codes |= static_cast(DHPointer::CheckResult::INVALID_Q);
+ }
+ }
+
+ if (q_good) {
+ if (BN_cmp(g, BN_value_one()) <= 0 || BN_cmp(g, p) >= 0) {
+ codes |= static_cast(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR);
+ } else if (BN_mod_exp(tmp1.get(), g, q, p, ctx.get()) != 1) {
+ return std::nullopt;
+ } else if (!BN_is_one(tmp1.get())) {
+ codes |= static_cast(DHPointer::CheckResult::NOT_SUITABLE_GENERATOR);
+ }
+
+ const int q_is_prime = BN_check_prime(q, ctx.get(), nullptr);
+ if (q_is_prime < 0) return std::nullopt;
+ if (q_is_prime == 0) {
+ codes |= static_cast(DHPointer::CheckResult::Q_NOT_PRIME);
+ }
+
+ if (BN_div(tmp1.get(), tmp2.get(), p, q, ctx.get()) != 1) {
+ return std::nullopt;
+ }
+ if (!BN_is_one(tmp2.get())) {
+ codes |= static_cast(DHPointer::CheckResult::INVALID_Q);
+ }
+ if (j != nullptr && BN_cmp(j, tmp1.get()) != 0) {
+ codes |= static_cast(DHPointer::CheckResult::INVALID_J);
+ }
+ }
+
+ const int p_is_prime = BN_check_prime(p, ctx.get(), nullptr);
+ if (p_is_prime < 0) return std::nullopt;
+ if (p_is_prime == 0) {
+ codes |= static_cast(DHPointer::CheckResult::P_NOT_PRIME);
+ } else if (q == nullptr) {
+ if (BN_rshift1(tmp1.get(), p) != 1) return std::nullopt;
+ const int q_is_prime = BN_check_prime(tmp1.get(), ctx.get(), nullptr);
+ if (q_is_prime < 0) return std::nullopt;
+ if (q_is_prime == 0) {
+ codes |= static_cast(DHPointer::CheckResult::P_NOT_SAFE_PRIME);
+ }
+ }
+
+ return codes;
+}
+#endif
} // namespace
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+DHPointer::DHPointer(EVPKeyPointer&& key, const char* group_name)
+ : dh_(key.release()), group_name_(group_name) {}
+
+DHPointer::DHPointer(BignumPointer&& p,
+ BignumPointer&& g,
+ const char* group_name)
+ : p_(std::move(p)), g_(std::move(g)), group_name_(group_name) {}
+#else
DHPointer::DHPointer(DH* dh) : dh_(dh) {}
+#endif
-DHPointer::DHPointer(DHPointer&& other) noexcept : dh_(other.release()) {}
+DHPointer::DHPointer(DHPointer&& other) noexcept
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ : dh_(other.dh_.release()),
+ p_(std::move(other.p_)),
+ g_(std::move(other.g_)),
+ pub_key_(std::move(other.pub_key_)),
+ pvt_key_(std::move(other.pvt_key_)),
+ group_name_(other.group_name_) {
+ other.group_name_ = nullptr;
+}
+#else
+ : dh_(other.release()) {
+}
+#endif
DHPointer& DHPointer::operator=(DHPointer&& other) noexcept {
if (this == &other) return *this;
@@ -1576,13 +1987,45 @@ DHPointer::~DHPointer() {
reset();
}
-void DHPointer::reset(DH* dh) {
+void DHPointer::reset(
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ EVP_PKEY* dh
+#else
+ DH* dh
+#endif
+) {
dh_.reset(dh);
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ p_.reset();
+ g_.reset();
+ pub_key_.reset();
+ pvt_key_.reset();
+ group_name_ = nullptr;
+#endif
}
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+EVP_PKEY* DHPointer::release() {
+ if (!dh_ && p_ && g_) {
+ auto pkey =
+ group_name_ != nullptr
+ ? NewDhPKey(group_name_, pub_key_.get(), pvt_key_.get())
+ : NewDhPKey(p_.get(), g_.get(), pub_key_.get(), pvt_key_.get());
+ if (!pkey) return nullptr;
+ dh_.reset(pkey.release());
+ }
+ p_.reset();
+ g_.reset();
+ pub_key_.reset();
+ pvt_key_.reset();
+ group_name_ = nullptr;
+ return dh_.release();
+}
+#else
DH* DHPointer::release() {
return dh_.release();
}
+#endif
BignumPointer DHPointer::FindGroup(const std::string_view name,
FindGroupOption option) {
@@ -1608,7 +2051,7 @@ BignumPointer DHPointer::FindGroup(const std::string_view name,
BignumPointer DHPointer::GetStandardGenerator() {
auto bn = BignumPointer::New();
if (!bn) return {};
- if (!bn.setWord(DH_GENERATOR_2)) return {};
+ if (!bn.setWord(2)) return {};
return bn;
}
@@ -1620,12 +2063,22 @@ DHPointer DHPointer::FromGroup(const std::string_view name,
auto generator = GetStandardGenerator();
if (!generator) return {}; // Unable to create the generator.
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ const char* group_name = GetOpenSSLDhGroupName(name, option);
+ return DHPointer(std::move(group), std::move(generator), group_name);
+#else
return New(std::move(group), std::move(generator));
+#endif
}
DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) {
if (!p || !g) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ auto pkey = NewDhPKey(p.get(), g.get());
+ if (!pkey) return {};
+ return DHPointer(std::move(pkey));
+#else
DHPointer dh(DH_new());
if (!dh) return {};
@@ -1640,9 +2093,21 @@ DHPointer DHPointer::New(BignumPointer&& p, BignumPointer&& g) {
g.release();
return dh;
+#endif
}
DHPointer DHPointer::New(size_t bits, unsigned int generator) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ auto param_ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_DH);
+ if (!param_ctx.initForParamgen() ||
+ !param_ctx.setDhParameters(bits, generator)) {
+ return {};
+ }
+
+ auto key_params = param_ctx.paramgen();
+ if (!key_params) return {};
+ return DHPointer(std::move(key_params));
+#else
DHPointer dh(DH_new());
if (!dh) return {};
@@ -1651,23 +2116,103 @@ DHPointer DHPointer::New(size_t bits, unsigned int generator) {
}
return dh;
+#endif
}
DHPointer::CheckResult DHPointer::check() {
ClearErrorOnReturn clearErrorOnReturn;
- if (!dh_) return DHPointer::CheckResult::NONE;
+ if (!*this) return DHPointer::CheckResult::NONE;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ // TODO(panva): In a semver-major, consider validating named DH groups
+ // through the provider instead of preserving the historical verifyError.
+ if (group_name_ != nullptr) return CheckResult::NONE;
+
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ DeleteFnPtr q;
+ DeleteFnPtr j;
+ const BIGNUM* p_bn = p_.get();
+ const BIGNUM* g_bn = g_.get();
+ const BIGNUM* q_bn = nullptr;
+ const BIGNUM* j_bn = nullptr;
+ if ((p_bn == nullptr || g_bn == nullptr) &&
+ !GetDhParams(dh_.get(), &p, &g, &q, &j)) {
+ return DHPointer::CheckResult::CHECK_FAILED;
+ }
+ if (p_bn == nullptr) p_bn = p.get();
+ if (g_bn == nullptr) g_bn = g.get();
+ q_bn = q.get();
+ j_bn = j.get();
+ if (p_bn == nullptr || g_bn == nullptr) {
+ return DHPointer::CheckResult::CHECK_FAILED;
+ }
+
+ auto codes = CheckDhParams(p_bn, g_bn, q_bn, j_bn);
+ if (!codes) return DHPointer::CheckResult::CHECK_FAILED;
+ return static_cast(*codes);
+#else
int codes = 0;
if (DH_check(dh_.get(), &codes) != 1)
return DHPointer::CheckResult::CHECK_FAILED;
return static_cast(codes);
+#endif
}
DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey(
const BignumPointer& pub_key) {
ClearErrorOnReturn clearErrorOnReturn;
- if (!pub_key || !dh_) {
+ if (!pub_key || !*this) {
+ return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
+ }
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ const BIGNUM* p_bn = p_.get();
+ const BIGNUM* g_bn = g_.get();
+ if ((p_bn == nullptr || g_bn == nullptr) && !GetDhParams(dh_.get(), &p, &g)) {
+ return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
+ }
+ if (p_bn == nullptr) p_bn = p.get();
+ if (g_bn == nullptr) g_bn = g.get();
+ if (p_bn == nullptr || g_bn == nullptr) {
+ return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
+ }
+
+ if (BN_cmp(pub_key.get(), BN_value_one()) <= 0) {
+ return DHPointer::CheckPublicKeyResult::TOO_SMALL;
+ }
+
+ DeleteFnPtr p_minus_one(BN_dup(p_bn));
+ if (!p_minus_one || BN_sub_word(p_minus_one.get(), 1) != 1) {
return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
}
+
+ if (BN_cmp(pub_key.get(), p_minus_one.get()) >= 0) {
+ return DHPointer::CheckPublicKeyResult::TOO_LARGE;
+ }
+
+ if (p_) {
+ if (group_name_ == nullptr) return CheckPublicKeyResult::NONE;
+
+ auto peer = NewDhPKey(group_name_, pub_key.get());
+ if (!peer) return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
+ EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(peer.get(), nullptr));
+ if (!ctx) return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
+ if (EVP_PKEY_public_check(ctx.get()) != 1) {
+ return DHPointer::CheckPublicKeyResult::INVALID;
+ }
+ return CheckPublicKeyResult::NONE;
+ }
+
+ auto peer = NewDhPKey(p_bn, g_bn, pub_key.get());
+ if (!peer) return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
+ EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(peer.get(), nullptr));
+ if (!ctx) return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
+ if (EVP_PKEY_public_check(ctx.get()) != 1) {
+ return DHPointer::CheckPublicKeyResult::INVALID;
+ }
+ return CheckPublicKeyResult::NONE;
+#else
int codes = 0;
if (DH_check_pub_key(dh_.get(), pub_key.get(), &codes) != 1) {
return DHPointer::CheckPublicKeyResult::CHECK_FAILED;
@@ -1684,65 +2229,242 @@ DHPointer::CheckPublicKeyResult DHPointer::checkPublicKey(
return DHPointer::CheckPublicKeyResult::INVALID;
}
return CheckPublicKeyResult::NONE;
+#endif
}
DataPointer DHPointer::getPrime() const {
- if (!dh_) return {};
+ if (!*this) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (p_) return p_.encode();
+
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ if (!GetDhParams(dh_.get(), &p, &g)) return {};
+ return BignumPointer::Encode(p.get());
+#else
const BIGNUM* p;
DH_get0_pqg(dh_.get(), &p, nullptr, nullptr);
return BignumPointer::Encode(p);
+#endif
+}
+
+size_t DHPointer::getPrimeBits() const {
+ if (!*this) return 0;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (p_) return BignumPointer::GetBitCount(p_.get());
+
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ if (!GetDhParams(dh_.get(), &p, &g)) return 0;
+ return BignumPointer::GetBitCount(p.get());
+#else
+ const BIGNUM* p;
+ DH_get0_pqg(dh_.get(), &p, nullptr, nullptr);
+ return BignumPointer::GetBitCount(p);
+#endif
}
DataPointer DHPointer::getGenerator() const {
- if (!dh_) return {};
+ if (!*this) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (g_) return g_.encode();
+
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ if (!GetDhParams(dh_.get(), &p, &g)) return {};
+ return BignumPointer::Encode(g.get());
+#else
const BIGNUM* g;
DH_get0_pqg(dh_.get(), nullptr, nullptr, &g);
return BignumPointer::Encode(g);
+#endif
}
DataPointer DHPointer::getPublicKey() const {
+ if (!*this) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (pub_key_) return pub_key_.encode();
if (!dh_) return {};
+
+ DeleteFnPtr pub_key;
+ DeleteFnPtr pvt_key;
+ if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return {};
+ return BignumPointer::Encode(pub_key.get());
+#else
const BIGNUM* pub_key;
DH_get0_key(dh_.get(), &pub_key, nullptr);
return BignumPointer::Encode(pub_key);
+#endif
}
DataPointer DHPointer::getPrivateKey() const {
+ if (!*this) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (pvt_key_) return pvt_key_.encode();
if (!dh_) return {};
+
+ DeleteFnPtr pub_key;
+ DeleteFnPtr pvt_key;
+ if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return {};
+ return BignumPointer::Encode(pvt_key.get());
+#else
const BIGNUM* pvt_key;
DH_get0_key(dh_.get(), nullptr, &pvt_key);
return BignumPointer::Encode(pvt_key);
+#endif
}
bool DHPointer::hasPrivateKey() const {
+ if (!*this) return false;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (pvt_key_) return true;
if (!dh_) return false;
+
+ DeleteFnPtr pub_key;
+ DeleteFnPtr pvt_key;
+ if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) return false;
+ return pvt_key != nullptr;
+#else
const BIGNUM* pvt_key = nullptr;
DH_get0_key(dh_.get(), nullptr, &pvt_key);
return pvt_key != nullptr;
+#endif
}
-DataPointer DHPointer::generateKeys() const {
+DataPointer DHPointer::generateKeys() {
ClearErrorOnReturn clearErrorOnReturn;
- if (!dh_) return {};
+ if (!*this) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (p_ && g_) {
+ if (!pvt_key_ && !GenerateDhPrivateKey(&pvt_key_, p_.get(), group_name_)) {
+ return {};
+ }
+
+ BignumPointer generated_pub_key;
+ if (!GenerateDhPublicKey(
+ &generated_pub_key, p_.get(), g_.get(), pvt_key_.get())) {
+ return {};
+ }
+
+ if (pub_key_ && BN_cmp(pub_key_.get(), generated_pub_key.get()) == 0) {
+ return getPublicKey();
+ }
+
+ pub_key_ = std::move(generated_pub_key);
+ return getPublicKey();
+ }
+
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ DeleteFnPtr pub_key;
+ DeleteFnPtr pvt_key;
+ if (!GetDhParams(dh_.get(), &p, &g) ||
+ !GetDhKeys(dh_.get(), &pub_key, &pvt_key)) {
+ return {};
+ }
+
+ if (pvt_key != nullptr) {
+ BignumPointer generated_pub_key;
+ if (!GenerateDhPublicKey(
+ &generated_pub_key, p.get(), g.get(), pvt_key.get())) {
+ return {};
+ }
+
+ if (pub_key != nullptr &&
+ BN_cmp(pub_key.get(), generated_pub_key.get()) == 0) {
+ return getPublicKey();
+ }
+
+ auto replacement =
+ group_name_ != nullptr
+ ? NewDhPKey(group_name_, generated_pub_key.get(), pvt_key.get())
+ : NewDhPKey(
+ p.get(), g.get(), generated_pub_key.get(), pvt_key.get());
+ if (!replacement) return {};
+ dh_.reset(replacement.release());
+ return getPublicKey();
+ }
+
+ EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(dh_.get(), nullptr));
+ if (!ctx || !ctx.initForKeygen()) return {};
+ EVP_PKEY* generated = nullptr;
+ if (EVP_PKEY_keygen(ctx.get(), &generated) != 1) return {};
+ dh_.reset(generated);
+ return getPublicKey();
+#else
// Key generation failed
if (!DH_generate_key(dh_.get())) return {};
return getPublicKey();
+#endif
}
size_t DHPointer::size() const {
- if (!dh_) return 0;
+ if (!*this) return 0;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (p_) return BignumPointer::GetByteCount(p_.get());
+
+ const int bits = EVP_PKEY_get_bits(dh_.get());
+ return bits > 0 ? (static_cast(bits) + 7) / 8 : 0;
+#else
int ret = DH_size(dh_.get());
// DH_size can return a -1 on error but we just want to return a 0
// in that case so we don't wrap around when returning the size_t.
return ret >= 0 ? static_cast(ret) : 0;
+#endif
}
DataPointer DHPointer::computeSecret(const BignumPointer& peer) const {
ClearErrorOnReturn clearErrorOnReturn;
- if (!dh_ || !peer) return {};
+ if (!*this || !peer) return {};
+
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (p_ && pvt_key_) {
+ auto secret = BignumPointer::NewSecure();
+ BignumCtxPointer ctx(BN_CTX_new());
+ if (!secret || !ctx ||
+ BN_mod_exp_mont_consttime(secret.get(),
+ peer.get(),
+ pvt_key_.get(),
+ p_.get(),
+ ctx.get(),
+ nullptr) != 1) {
+ return {};
+ }
+ return secret.encodePadded(size());
+ }
+
+ EVPKeyPointer peer_key;
+ if (group_name_ != nullptr) {
+ peer_key = NewDhPKey(group_name_, peer.get());
+ } else {
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ if (!GetDhParams(dh_.get(), &p, &g)) return {};
+ peer_key = NewDhPKey(p.get(), g.get(), peer.get());
+ }
+ if (!peer_key) return {};
+
+ EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(dh_.get(), nullptr));
+ size_t out_size = size();
+ if (!ctx || EVP_PKEY_derive_init(ctx.get()) != 1 ||
+ EVP_PKEY_CTX_set_dh_pad(ctx.get(), 1) != 1 ||
+ EVP_PKEY_derive_set_peer(ctx.get(), peer_key.get()) != 1 ||
+ EVP_PKEY_derive(ctx.get(), nullptr, &out_size) != 1) {
+ return {};
+ }
+
+ if (out_size == 0) return {};
+ auto dp = DataPointer::Alloc(out_size);
+ if (!dp) return {};
+ if (EVP_PKEY_derive(
+ ctx.get(), static_cast(dp.get()), &out_size) != 1) {
+ return {};
+ }
+ return dp.resize(out_size);
+#else
auto dp = DataPointer::Alloc(size());
if (!dp) return {};
@@ -1760,10 +2482,35 @@ DataPointer DHPointer::computeSecret(const BignumPointer& peer) const {
}
return dp;
+#endif
}
bool DHPointer::setPublicKey(BignumPointer&& key) {
- if (!dh_) return false;
+ if (!*this) return false;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (p_ && g_) {
+ pub_key_ = std::move(key);
+ return true;
+ }
+
+ DeleteFnPtr pub_key;
+ DeleteFnPtr pvt_key;
+ if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) {
+ return false;
+ }
+ EVPKeyPointer pkey;
+ if (group_name_ != nullptr) {
+ pkey = NewDhPKey(group_name_, key.get(), pvt_key.get());
+ } else {
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ if (!GetDhParams(dh_.get(), &p, &g)) return false;
+ pkey = NewDhPKey(p.get(), g.get(), key.get(), pvt_key.get());
+ }
+ if (!pkey) return false;
+ dh_.reset(pkey.release());
+ return true;
+#else
if (DH_set0_key(dh_.get(), key.get(), nullptr) == 1) {
// If DH_set0_key returns successfully, then dh_ takes ownership of the
// BIGNUM, so we must release it here. Unfortunately coverity does not
@@ -1773,10 +2520,35 @@ bool DHPointer::setPublicKey(BignumPointer&& key) {
return true;
}
return false;
+#endif
}
bool DHPointer::setPrivateKey(BignumPointer&& key) {
- if (!dh_) return false;
+ if (!*this) return false;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (p_ && g_) {
+ pvt_key_ = std::move(key);
+ return true;
+ }
+
+ DeleteFnPtr pub_key;
+ DeleteFnPtr pvt_key;
+ if (!GetDhKeys(dh_.get(), &pub_key, &pvt_key)) {
+ return false;
+ }
+ EVPKeyPointer pkey;
+ if (group_name_ != nullptr) {
+ pkey = NewDhPKey(group_name_, pub_key.get(), key.get());
+ } else {
+ DeleteFnPtr p;
+ DeleteFnPtr g;
+ if (!GetDhParams(dh_.get(), &p, &g)) return false;
+ pkey = NewDhPKey(p.get(), g.get(), pub_key.get(), key.get());
+ }
+ if (!pkey) return false;
+ dh_.reset(pkey.release());
+ return true;
+#else
if (DH_set0_key(dh_.get(), nullptr, key.get()) == 1) {
// If DH_set0_key returns successfully, then dh_ takes ownership of the
// BIGNUM, so we must release it here. Unfortunately coverity does not
@@ -1786,6 +2558,7 @@ bool DHPointer::setPrivateKey(BignumPointer&& key) {
return true;
}
return false;
+#endif
}
DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey,
@@ -1794,8 +2567,12 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey,
if (!ourKey || !theirKey) return {};
auto ctx = EVPKeyCtxPointer::New(ourKey);
- if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0 ||
- EVP_PKEY_derive_set_peer(ctx.get(), theirKey.get()) <= 0 ||
+ if (!ctx || EVP_PKEY_derive_init(ctx.get()) <= 0) {
+ return {};
+ }
+ // TODO(panva): In a semver-major, consider padding OpenSSL 3 DH derivation
+ // results here to match DiffieHellman::computeSecret().
+ if (EVP_PKEY_derive_set_peer(ctx.get(), theirKey.get()) <= 0 ||
EVP_PKEY_derive(ctx.get(), nullptr, &out_size) <= 0) {
return {};
}
@@ -2209,14 +2986,60 @@ EVPKeyPointer EVPKeyPointer::NewRawSeed(
EVPKeyPointer EVPKeyPointer::NewDH(DHPointer&& dh) {
if (!dh) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return EVPKeyPointer(dh.release());
+#else
auto key = New();
if (!key) return {};
if (EVP_PKEY_assign_DH(key.get(), dh.get())) {
dh.release();
}
return key;
+#endif
}
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+EVPKeyPointer EVPKeyPointer::NewRSA(const Rsa& rsa) {
+ const auto public_key = rsa.getPublicKey();
+ if (public_key.n == nullptr || public_key.e == nullptr) return {};
+
+ OSSLParamBldPointer bld(OSSL_PARAM_BLD_new());
+ if (!bld ||
+ OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_N, public_key.n) !=
+ 1 ||
+ OSSL_PARAM_BLD_push_BN(bld.get(), OSSL_PKEY_PARAM_RSA_E, public_key.e) !=
+ 1) {
+ return {};
+ }
+
+ int selection = EVP_PKEY_PUBLIC_KEY;
+ if (public_key.d != nullptr) {
+ const auto private_key = rsa.getPrivateKey();
+ if (private_key.p == nullptr || private_key.q == nullptr ||
+ private_key.dp == nullptr || private_key.dq == nullptr ||
+ private_key.qi == nullptr ||
+ OSSL_PARAM_BLD_push_BN(
+ bld.get(), OSSL_PKEY_PARAM_RSA_D, public_key.d) != 1 ||
+ OSSL_PARAM_BLD_push_BN(
+ bld.get(), OSSL_PKEY_PARAM_RSA_FACTOR1, private_key.p) != 1 ||
+ OSSL_PARAM_BLD_push_BN(
+ bld.get(), OSSL_PKEY_PARAM_RSA_FACTOR2, private_key.q) != 1 ||
+ OSSL_PARAM_BLD_push_BN(
+ bld.get(), OSSL_PKEY_PARAM_RSA_EXPONENT1, private_key.dp) != 1 ||
+ OSSL_PARAM_BLD_push_BN(
+ bld.get(), OSSL_PKEY_PARAM_RSA_EXPONENT2, private_key.dq) != 1 ||
+ OSSL_PARAM_BLD_push_BN(
+ bld.get(), OSSL_PKEY_PARAM_RSA_COEFFICIENT1, private_key.qi) != 1) {
+ return {};
+ }
+ selection = EVP_PKEY_KEYPAIR;
+ }
+
+ OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get()));
+ if (!params) return {};
+ return NewPKeyFromData(EVP_PKEY_RSA, selection, params.get());
+}
+#else
EVPKeyPointer EVPKeyPointer::NewRSA(RSAPointer&& rsa) {
if (!rsa) return {};
auto key = New();
@@ -2226,6 +3049,7 @@ EVPKeyPointer EVPKeyPointer::NewRSA(RSAPointer&& rsa) {
}
return key;
}
+#endif // NCRYPTO_USE_OPENSSL3_PROVIDER
EVPKeyPointer::EVPKeyPointer(EVP_PKEY* pkey) : pkey_(pkey) {}
@@ -2363,18 +3187,83 @@ BIOPointer EVPKeyPointer::derPublicKey() const {
bool EVPKeyPointer::assign(const ECKeyPointer& eckey) {
if (!pkey_ || !eckey) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return set(eckey);
+#else
return EVP_PKEY_assign_EC_KEY(pkey_.get(), eckey.get());
+#endif
}
bool EVPKeyPointer::set(const ECKeyPointer& eckey) {
if (!pkey_ || !eckey) return false;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ const int nid = EC_GROUP_get_curve_name(eckey.group_.get());
+ const char* group_name = OBJ_nid2sn(nid);
+ if (group_name == nullptr) return false;
+
+ OSSLParamBldPointer bld(OSSL_PARAM_BLD_new());
+ if (!bld || OSSL_PARAM_BLD_push_utf8_string(
+ bld.get(), OSSL_PKEY_PARAM_GROUP_NAME, group_name, 0) != 1) {
+ return false;
+ }
+
+ int selection = EVP_PKEY_KEY_PARAMETERS;
+ OpenSSLBufferPointer encoded_public_key;
+ ECPointPointer generated_public_key;
+ const EC_POINT* public_key = eckey.pub_.get();
+ if (public_key == nullptr && eckey.priv_ != nullptr) {
+ generated_public_key = ECPointPointer::New(eckey.group_.get());
+ if (!generated_public_key ||
+ !generated_public_key.mul(eckey.group_.get(), eckey.priv_.get())) {
+ return false;
+ }
+ public_key = generated_public_key.get();
+ }
+
+ if (public_key != nullptr) {
+ unsigned char* encoded_public_key_raw = nullptr;
+ const size_t encoded_public_key_len =
+ EC_POINT_point2buf(eckey.group_.get(),
+ public_key,
+ POINT_CONVERSION_UNCOMPRESSED,
+ &encoded_public_key_raw,
+ nullptr);
+ if (encoded_public_key_len == 0) return false;
+ encoded_public_key.reset(encoded_public_key_raw);
+ if (OSSL_PARAM_BLD_push_octet_string(bld.get(),
+ OSSL_PKEY_PARAM_PUB_KEY,
+ encoded_public_key.get(),
+ encoded_public_key_len) != 1) {
+ return false;
+ }
+ selection |= EVP_PKEY_PUBLIC_KEY;
+ }
+
+ if (eckey.priv_ != nullptr) {
+ if (OSSL_PARAM_BLD_push_BN(
+ bld.get(), OSSL_PKEY_PARAM_PRIV_KEY, eckey.priv_.get()) != 1) {
+ return false;
+ }
+ selection |= EVP_PKEY_PRIVATE_KEY;
+ }
+
+ OSSLParamPointer params(OSSL_PARAM_BLD_to_param(bld.get()));
+ if (!params) return false;
+ auto pkey = NewPKeyFromData(EVP_PKEY_EC, selection, params.get());
+ if (!pkey) return false;
+ reset(pkey.release());
+ return true;
+#else
return EVP_PKEY_set1_EC_KEY(pkey_.get(), eckey);
+#endif
}
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
EVPKeyPointer::operator const EC_KEY*() const {
if (!pkey_) return nullptr;
return EVP_PKEY_get0_EC_KEY(pkey_.get());
}
+#endif // NCRYPTO_USE_LEGACY_KEY_TYPES
namespace {
@@ -2429,6 +3318,34 @@ constexpr bool IsASN1Sequence(const unsigned char* data,
return true;
}
+constexpr bool ReadASN1Element(const unsigned char* data,
+ size_t size,
+ unsigned char tag,
+ size_t* header_size,
+ size_t* content_size,
+ size_t* total_size) {
+ if (size < 2 || data[0] != tag) return false;
+
+ size_t offset;
+ size_t length;
+ if (data[1] & 0x80) {
+ size_t n_bytes = data[1] & ~0x80;
+ if (n_bytes + 2 > size || n_bytes > sizeof(size_t)) return false;
+ length = 0;
+ for (size_t i = 0; i < n_bytes; i++) length = (length << 8) | data[i + 2];
+ offset = 2 + n_bytes;
+ } else {
+ offset = 2;
+ length = data[1];
+ }
+
+ if (offset > size || length > size - offset) return false;
+ *header_size = offset;
+ *content_size = length;
+ *total_size = offset + length;
+ return true;
+}
+
constexpr bool IsEncryptedPrivateKeyInfo(
const Buffer& buffer) {
// Both PrivateKeyInfo and EncryptedPrivateKeyInfo start with a SEQUENCE.
@@ -2546,6 +3463,103 @@ Buffer GetPassphrase(
}
return pass;
}
+
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+using OSSLEncoderCtxPointer =
+ DeleteFnPtr;
+
+bool WriteEncodedPKey(BIO* bio,
+ const EVP_PKEY* pkey,
+ int selection,
+ EVPKeyPointer::PKFormatType format,
+ const char* structure,
+ const EVP_CIPHER* cipher = nullptr,
+ Buffer passphrase = {}) {
+ const char* output_type =
+ format == EVPKeyPointer::PKFormatType::PEM ? "PEM" : "DER";
+ OSSLEncoderCtxPointer ctx(OSSL_ENCODER_CTX_new_for_pkey(
+ pkey, selection, output_type, structure, nullptr));
+ if (!ctx || OSSL_ENCODER_CTX_get_num_encoders(ctx.get()) == 0) {
+ return false;
+ }
+
+ if (cipher != nullptr) {
+ if (OSSL_ENCODER_CTX_set_cipher(
+ ctx.get(), EVP_CIPHER_get0_name(cipher), nullptr) != 1 ||
+ OSSL_ENCODER_CTX_set_passphrase(
+ ctx.get(),
+ reinterpret_cast(passphrase.data),
+ passphrase.len) != 1) {
+ return false;
+ }
+ }
+
+ return OSSL_ENCODER_to_bio(ctx.get(), bio) == 1;
+}
+
+struct DERView {
+ const unsigned char* data = nullptr;
+ size_t len = 0;
+};
+
+int WriteDERView(const void* x, unsigned char** out) {
+ const auto* der = static_cast(x);
+ if (der == nullptr || der->data == nullptr ||
+ der->len > static_cast(INT_MAX)) {
+ return -1;
+ }
+ if (out != nullptr) {
+ memcpy(*out, der->data, der->len);
+ *out += der->len;
+ }
+ return static_cast(der->len);
+}
+
+bool WriteEncryptedTraditionalPEM(BIO* bio,
+ const EVP_PKEY* pkey,
+ const EVP_CIPHER* cipher,
+ Buffer passphrase) {
+ if (passphrase.len > static_cast(INT_MAX)) return false;
+
+ unsigned char* der = nullptr;
+ size_t der_len = 0;
+ OSSLEncoderCtxPointer ctx(OSSL_ENCODER_CTX_new_for_pkey(
+ pkey, OSSL_KEYMGMT_SELECT_KEYPAIR, "DER", "pkcs1", nullptr));
+ if (!ctx || OSSL_ENCODER_to_data(ctx.get(), &der, &der_len) != 1) {
+ return false;
+ }
+
+ OpenSSLBufferPointer der_storage(der);
+ DERView der_view{der_storage.get(), der_len};
+ return PEM_ASN1_write_bio(
+ WriteDERView,
+ PEM_STRING_RSA,
+ bio,
+ &der_view,
+ cipher,
+ reinterpret_cast(passphrase.data),
+ static_cast(passphrase.len),
+ nullptr,
+ nullptr) == 1;
+}
+
+bool ECKeyHasMissingOid(const EVPKeyPointer& key) {
+ if (key.id() != EVP_PKEY_EC) return false;
+
+ const Ec ec(key.get());
+ const EC_GROUP* group = ec.getGroup();
+ if (group == nullptr ||
+ EC_GROUP_get_asn1_flag(group) != OPENSSL_EC_NAMED_CURVE) {
+ return false;
+ }
+
+ const int nid = EC_GROUP_get_curve_name(group);
+ if (nid == NID_undef) return true;
+
+ const ASN1_OBJECT* asn1 = OBJ_nid2obj(nid);
+ return asn1 == nullptr || OBJ_length(asn1) == 0;
+}
+#endif
} // namespace
EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePrivateKey(
@@ -2633,6 +3647,22 @@ Result EVPKeyPointer::writePrivateKey(
// PKCS1 is only permitted for RSA keys.
if (id() != EVP_PKEY_RSA) return Result(false);
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ const EVP_CIPHER* cipher =
+ config.format == PKFormatType::PEM ? config.cipher : nullptr;
+ if (cipher != nullptr && passphrase.len == 0) {
+ err =
+ !WriteEncryptedTraditionalPEM(bio.get(), get(), cipher, passphrase);
+ } else {
+ err = !WriteEncodedPKey(bio.get(),
+ get(),
+ OSSL_KEYMGMT_SELECT_ALL,
+ config.format,
+ "pkcs1",
+ cipher,
+ passphrase);
+ }
+#else
#if OPENSSL_VERSION_MAJOR >= 3
const RSA* rsa = EVP_PKEY_get0_RSA(get());
#else
@@ -2660,6 +3690,7 @@ Result EVPKeyPointer::writePrivateKey(
return Result(false);
}
}
+#endif
break;
}
case PKEncodingType::PKCS8: {
@@ -2696,6 +3727,17 @@ Result EVPKeyPointer::writePrivateKey(
// SEC1 is only permitted for EC keys
if (id() != EVP_PKEY_EC) return Result(false);
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ const EVP_CIPHER* cipher =
+ config.format == PKFormatType::PEM ? config.cipher : nullptr;
+ err = !WriteEncodedPKey(bio.get(),
+ get(),
+ OSSL_KEYMGMT_SELECT_ALL,
+ config.format,
+ "type-specific",
+ cipher,
+ passphrase);
+#else
#if OPENSSL_VERSION_MAJOR >= 3
const EC_KEY* ec = EVP_PKEY_get0_EC_KEY(get());
#else
@@ -2723,6 +3765,7 @@ Result EVPKeyPointer::writePrivateKey(
return Result(false);
}
}
+#endif
break;
}
default: {
@@ -2749,6 +3792,18 @@ Result EVPKeyPointer::writePublicKey(
if (config.type == ncrypto::EVPKeyPointer::PKEncodingType::PKCS1) {
// PKCS#1 is only valid for RSA keys.
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (id() != EVP_PKEY_RSA) return Result(false);
+ if (!WriteEncodedPKey(bio.get(),
+ get(),
+ OSSL_KEYMGMT_SELECT_PUBLIC_KEY,
+ config.format,
+ "pkcs1")) {
+ return Result(false,
+ mark_pop_error_on_return.peekError());
+ }
+ return bio;
+#else
#if OPENSSL_VERSION_MAJOR >= 3
const RSA* rsa = EVP_PKEY_get0_RSA(get());
#else
@@ -2769,7 +3824,16 @@ Result EVPKeyPointer::writePublicKey(
mark_pop_error_on_return.peekError());
}
return bio;
+#endif
+ }
+
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (ECKeyHasMissingOid(*this)) {
+ ERR_raise(ERR_LIB_EC, EC_R_MISSING_OID);
+ return Result(false,
+ mark_pop_error_on_return.peekError());
}
+#endif
if (config.format == ncrypto::EVPKeyPointer::PKFormatType::PEM) {
// Encode SPKI as PEM.
@@ -2841,11 +3905,23 @@ std::optional EVPKeyPointer::getBytesOfRS() const {
int bits, id = base_id();
if (id == EVP_PKEY_DSA) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ DeleteFnPtr q;
+ if (!GetPKeyBnParam(get(), OSSL_PKEY_PARAM_FFC_Q, &q)) return std::nullopt;
+ bits = BignumPointer::GetBitCount(q.get());
+#else
const DSA* dsa_key = EVP_PKEY_get0_DSA(get());
// Both r and s are computed mod q, so their width is limited by that of q.
bits = BignumPointer::GetBitCount(DSA_get0_q(dsa_key));
+#endif
} else if (id == EVP_PKEY_EC) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ Ec ec(get());
+ if (!ec) return std::nullopt;
+ bits = EC_GROUP_order_bits(ec.getGroup());
+#else
bits = EC_GROUP_order_bits(ECKeyPointer::GetGroup(*this));
+#endif
} else {
return std::nullopt;
}
@@ -2857,6 +3933,9 @@ EVPKeyPointer::operator Rsa() const {
int type = id();
if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return Rsa(get());
+#else
// TODO(tniessen): Remove the "else" branch once we drop support for OpenSSL
// versions older than 1.1.1e via FIPS / dynamic linking.
OSSL3_CONST RSA* rsa;
@@ -2867,15 +3946,20 @@ EVPKeyPointer::operator Rsa() const {
}
if (rsa == nullptr) return {};
return Rsa(rsa);
+#endif
}
EVPKeyPointer::operator Dsa() const {
int type = id();
if (type != EVP_PKEY_DSA) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return Dsa(get());
+#else
OSSL3_CONST DSA* dsa = EVP_PKEY_get0_DSA(get());
if (dsa == nullptr) return {};
return Dsa(dsa);
+#endif
}
bool EVPKeyPointer::validateDsaParameters() const {
@@ -2886,12 +3970,25 @@ bool EVPKeyPointer::validateDsaParameters() const {
#else
if (FIPS_mode() && EVP_PKEY_DSA == id()) {
#endif
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ DeleteFnPtr p;
+ DeleteFnPtr q;
+ if (!GetPKeyBnParam(pkey_.get(), OSSL_PKEY_PARAM_FFC_P, &p) ||
+ !GetPKeyBnParam(pkey_.get(), OSSL_PKEY_PARAM_FFC_Q, &q)) {
+ return false;
+ }
+ const BIGNUM* p_value = p.get();
+ const BIGNUM* q_value = q.get();
+#else
const DSA* dsa = EVP_PKEY_get0_DSA(pkey_.get());
const BIGNUM* p;
const BIGNUM* q;
DSA_get0_pqg(dsa, &p, &q, nullptr);
- int L = BignumPointer::GetBitCount(p);
- int N = BignumPointer::GetBitCount(q);
+ const BIGNUM* p_value = p;
+ const BIGNUM* q_value = q;
+#endif
+ int L = BignumPointer::GetBitCount(p_value);
+ int N = BignumPointer::GetBitCount(q_value);
return (L == 1024 && N == 160) || (L == 2048 && N == 224) ||
(L == 2048 && N == 256) || (L == 3072 && N == 256);
@@ -3196,7 +4293,7 @@ const Cipher Cipher::FromNid(int nid) {
}
const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) {
- return Cipher(EVP_CIPHER_CTX_cipher(ctx.get()));
+ return Cipher(GetCipherCtxCipher(ctx.get()));
}
const Cipher Cipher::EMPTY = Cipher();
@@ -3345,7 +4442,7 @@ int Cipher::bytesToKey(const Digest& digest,
CipherCtxPointer CipherCtxPointer::New() {
auto ret = CipherCtxPointer(EVP_CIPHER_CTX_new());
if (!ret) return {};
- EVP_CIPHER_CTX_init(ret.get());
+ EVP_CIPHER_CTX_reset(ret.get());
return ret;
}
@@ -3609,8 +4706,15 @@ bool ECPointPointer::mul(const EC_GROUP* group, const BIGNUM* priv_key) {
// ============================================================================
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
ECKeyPointer::ECKeyPointer() : key_(nullptr) {}
+ECKeyPointer::ECKeyPointer(const EVPKeyPointer& key) : key_(nullptr) {
+ if (key.id() != EVP_PKEY_EC) return;
+ const EC_KEY* ec = key;
+ if (ec != nullptr) key_.reset(EC_KEY_dup(ec));
+}
+
ECKeyPointer::ECKeyPointer(EC_KEY* key) : key_(key) {}
ECKeyPointer::ECKeyPointer(ECKeyPointer&& other) noexcept
@@ -3731,6 +4835,20 @@ bool ECKeyPointer::checkKey() const {
return Check(key_.get());
}
+DataPointer ECKeyPointer::computeSecret(const ECPointPointer& peer) const {
+ if (!key_ || !peer) return {};
+ const EC_GROUP* group = getGroup();
+ const int field_size = EC_GROUP_get_degree(group);
+ const size_t out_len = (field_size + 7) / 8;
+ auto out = DataPointer::Alloc(out_len);
+ if (!out) return {};
+ if (ECDH_compute_key(
+ out.get(), out.size(), peer.get(), key_.get(), nullptr) == 0) {
+ return {};
+ }
+ return out;
+}
+
ECKeyPointer ECKeyPointer::NewByCurveName(int nid) {
return ECKeyPointer(EC_KEY_new_by_curve_name(nid));
}
@@ -3741,6 +4859,262 @@ ECKeyPointer ECKeyPointer::New(const EC_GROUP* group) {
if (!EC_KEY_set_group(ptr.get(), group)) return {};
return ptr;
}
+#else
+ECKeyPointer::ECKeyPointer() : group_(nullptr), pub_(nullptr), priv_(nullptr) {}
+
+ECKeyPointer::ECKeyPointer(const EVPKeyPointer& key) : ECKeyPointer() {
+ if (key.id() != EVP_PKEY_EC) return;
+ char group_name[80];
+ size_t group_name_len = 0;
+ if (EVP_PKEY_get_utf8_string_param(key.get(),
+ OSSL_PKEY_PARAM_GROUP_NAME,
+ group_name,
+ sizeof(group_name),
+ &group_name_len) != 1) {
+ return;
+ }
+
+ const int nid = Ec::GetCurveIdFromName(group_name);
+ if (nid == NID_undef) return;
+ group_.reset(EC_GROUP_new_by_curve_name(nid));
+ if (!group_) return;
+
+ GetOptionalPKeyBnParam(key.get(), OSSL_PKEY_PARAM_PRIV_KEY, &priv_);
+
+ size_t public_key_len = 0;
+ if (EVP_PKEY_get_octet_string_param(
+ key.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &public_key_len) ==
+ 1) {
+ auto public_key = DataPointer::Alloc(public_key_len);
+ if (!public_key || EVP_PKEY_get_octet_string_param(
+ key.get(),
+ OSSL_PKEY_PARAM_PUB_KEY,
+ static_cast(public_key.get()),
+ public_key.size(),
+ &public_key_len) != 1) {
+ reset();
+ return;
+ }
+
+ auto point = ECPointPointer::New(group_.get());
+ if (!point ||
+ !point.setFromBuffer(
+ {static_cast(public_key.get()), public_key_len},
+ group_.get())) {
+ reset();
+ return;
+ }
+ pub_.reset(point.release());
+ }
+}
+
+ECKeyPointer::ECKeyPointer(ECKeyPointer&& other) noexcept
+ : group_(std::move(other.group_)),
+ pub_(std::move(other.pub_)),
+ priv_(std::move(other.priv_)) {}
+
+ECKeyPointer& ECKeyPointer::operator=(ECKeyPointer&& other) noexcept {
+ group_ = std::move(other.group_);
+ pub_ = std::move(other.pub_);
+ priv_ = std::move(other.priv_);
+ return *this;
+}
+
+ECKeyPointer::~ECKeyPointer() {
+ reset();
+}
+
+void ECKeyPointer::reset() {
+ group_.reset();
+ pub_.reset();
+ priv_.reset();
+}
+
+ECKeyPointer ECKeyPointer::clone() const {
+ if (!group_) return {};
+ ECKeyPointer ret;
+ ret.group_.reset(EC_GROUP_dup(group_.get()));
+ if (!ret.group_) return {};
+ if (pub_ != nullptr) {
+ ret.pub_.reset(EC_POINT_dup(pub_.get(), ret.group_.get()));
+ if (!ret.pub_) return {};
+ }
+ if (priv_ != nullptr) {
+ ret.priv_.reset(BN_dup(priv_.get()));
+ if (!ret.priv_) return {};
+ }
+ return ret;
+}
+
+bool ECKeyPointer::generate() {
+ if (!group_) return false;
+ const int nid = EC_GROUP_get_curve_name(group_.get());
+ auto ctx = EVPKeyCtxPointer::NewFromID(EVP_PKEY_EC);
+ if (!ctx || !ctx.initForKeygen() ||
+ !ctx.setEcParameters(nid, OPENSSL_EC_NAMED_CURVE)) {
+ return false;
+ }
+
+ EVP_PKEY* raw = nullptr;
+ if (EVP_PKEY_keygen(ctx.get(), &raw) != 1) return false;
+ EVPKeyPointer pkey(raw);
+
+ DeleteFnPtr priv;
+ if (!GetPKeyBnParam(pkey.get(), OSSL_PKEY_PARAM_PRIV_KEY, &priv)) {
+ return false;
+ }
+
+ size_t public_key_len = 0;
+ if (EVP_PKEY_get_octet_string_param(
+ pkey.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &public_key_len) !=
+ 1) {
+ return false;
+ }
+
+ auto public_key = DataPointer::Alloc(public_key_len);
+ if (!public_key || EVP_PKEY_get_octet_string_param(
+ pkey.get(),
+ OSSL_PKEY_PARAM_PUB_KEY,
+ static_cast(public_key.get()),
+ public_key.size(),
+ &public_key_len) != 1) {
+ return false;
+ }
+
+ auto point = ECPointPointer::New(group_.get());
+ if (!point ||
+ !point.setFromBuffer(
+ {static_cast(public_key.get()), public_key_len},
+ group_.get())) {
+ return false;
+ }
+
+ priv_ = std::move(priv);
+ pub_.reset(point.release());
+ return true;
+}
+
+bool ECKeyPointer::setPublicKey(const ECPointPointer& pub) {
+ if (!group_ || !pub) return false;
+ pub_.reset(EC_POINT_dup(pub.get(), group_.get()));
+ return pub_ != nullptr;
+}
+
+bool ECKeyPointer::setPublicKeyRaw(const BignumPointer& x,
+ const BignumPointer& y) {
+ if (!group_ || !x || !y) return false;
+ const size_t field_len = (EC_GROUP_get_degree(group_.get()) + 7) / 8;
+ const size_t uncompressed_len = 1 + 2 * field_len;
+ auto buf = DataPointer::Alloc(uncompressed_len);
+ if (!buf) return false;
+ unsigned char* ptr = static_cast(buf.get());
+ ptr[0] = POINT_CONVERSION_UNCOMPRESSED;
+ x.encodePaddedInto(ptr + 1, field_len);
+ y.encodePaddedInto(ptr + 1 + field_len, field_len);
+
+ auto point = ECPointPointer::New(group_.get());
+ if (!point || !point.setFromBuffer({ptr, uncompressed_len}, group_.get())) {
+ return false;
+ }
+ pub_.reset(point.release());
+ return true;
+}
+
+bool ECKeyPointer::setPrivateKey(const BignumPointer& priv) {
+ if (!group_ || !priv) return false;
+ priv_.reset(BN_dup(priv.get()));
+ return priv_ != nullptr;
+}
+
+const BIGNUM* ECKeyPointer::getPrivateKey() const {
+ return priv_.get();
+}
+
+const EC_POINT* ECKeyPointer::getPublicKey() const {
+ return pub_.get();
+}
+
+const EC_GROUP* ECKeyPointer::getGroup() const {
+ return group_.get();
+}
+
+bool ECKeyPointer::checkKey() const {
+ if (!group_) return false;
+
+ if (priv_ != nullptr) {
+ auto order = BignumPointer::New();
+ if (!order || !EC_GROUP_get_order(group_.get(), order.get(), nullptr)) {
+ return false;
+ }
+ if (BN_is_zero(priv_.get()) || BN_is_negative(priv_.get()) ||
+ BN_cmp(priv_.get(), order.get()) >= 0) {
+ return false;
+ }
+ }
+
+ if (pub_ != nullptr &&
+ EC_POINT_is_on_curve(group_.get(), pub_.get(), nullptr) != 1) {
+ return false;
+ }
+
+ if (priv_ != nullptr && pub_ != nullptr) {
+ auto expected = ECPointPointer::New(group_.get());
+ if (!expected || !expected.mul(group_.get(), priv_.get()) ||
+ EC_POINT_cmp(group_.get(), expected.get(), pub_.get(), nullptr) != 0) {
+ return false;
+ }
+ }
+
+ auto pkey = EVPKeyPointer::New();
+ if (!pkey || !pkey.set(*this)) return false;
+ EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
+ if (!ctx) return false;
+ if (pub_ != nullptr && EVP_PKEY_public_check(ctx.get()) != 1) return false;
+ if (priv_ != nullptr && EVP_PKEY_private_check(ctx.get()) != 1) return false;
+ return true;
+}
+
+DataPointer ECKeyPointer::computeSecret(const ECPointPointer& peer) const {
+ if (!group_ || !priv_ || !peer) return {};
+ auto our_key = EVPKeyPointer::New();
+ auto their_key = EVPKeyPointer::New();
+ auto their_ec = ECKeyPointer::New(group_.get());
+ if (!our_key || !their_key || !our_key.set(*this) ||
+ !their_ec.setPublicKey(peer) || !their_key.set(their_ec)) {
+ return {};
+ }
+
+ EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(our_key.get(), nullptr));
+ size_t out_len = 0;
+ if (!ctx || EVP_PKEY_derive_init(ctx.get()) != 1 ||
+ EVP_PKEY_derive_set_peer(ctx.get(), their_key.get()) != 1 ||
+ EVP_PKEY_derive(ctx.get(), nullptr, &out_len) != 1) {
+ return {};
+ }
+
+ auto out = DataPointer::Alloc(out_len);
+ if (!out) return {};
+ if (EVP_PKEY_derive(
+ ctx.get(), static_cast(out.get()), &out_len) != 1) {
+ return {};
+ }
+ return out.resize(out_len);
+}
+
+ECKeyPointer ECKeyPointer::NewByCurveName(int nid) {
+ ECKeyPointer ret;
+ ret.group_.reset(EC_GROUP_new_by_curve_name(nid));
+ return ret;
+}
+
+ECKeyPointer ECKeyPointer::New(const EC_GROUP* group) {
+ ECKeyPointer ret;
+ if (group != nullptr) {
+ ret.group_.reset(EC_GROUP_dup(group));
+ }
+ return ret;
+}
+#endif // NCRYPTO_USE_LEGACY_KEY_TYPES
// ============================================================================
@@ -3835,8 +5209,33 @@ bool EVPKeyCtxPointer::setDsaParameters(uint32_t bits,
bool EVPKeyCtxPointer::setEcParameters(int curve, int encoding) {
if (!ctx_) return false;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ const char* group_name = OBJ_nid2sn(curve);
+ if (group_name == nullptr) return false;
+
+ const char* encoding_name = nullptr;
+ switch (encoding) {
+ case OPENSSL_EC_EXPLICIT_CURVE:
+ encoding_name = OSSL_PKEY_EC_ENCODING_EXPLICIT;
+ break;
+ case OPENSSL_EC_NAMED_CURVE:
+ encoding_name = OSSL_PKEY_EC_ENCODING_GROUP;
+ break;
+ default:
+ return false;
+ }
+ OSSL_PARAM params[] = {
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_PKEY_PARAM_GROUP_NAME, const_cast(group_name), 0),
+ OSSL_PARAM_construct_utf8_string(
+ OSSL_PKEY_PARAM_EC_ENCODING, const_cast(encoding_name), 0),
+ OSSL_PARAM_END,
+ };
+ return EVP_PKEY_CTX_set_params(ctx_.get(), params) == 1;
+#else
return EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx_.get(), curve) == 1 &&
EVP_PKEY_CTX_set_ec_param_enc(ctx_.get(), encoding) == 1;
+#endif
}
bool EVPKeyCtxPointer::setRsaOaepMd(const Digest& md) {
@@ -3875,12 +5274,16 @@ bool EVPKeyCtxPointer::setRsaKeygenBits(int bits) {
bool EVPKeyCtxPointer::setRsaKeygenPubExp(BignumPointer&& e) {
if (!ctx_) return false;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return EVP_PKEY_CTX_set1_rsa_keygen_pubexp(ctx_.get(), e.get()) == 1;
+#else
if (EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx_.get(), e.get()) == 1) {
// The ctx_ takes ownership of e on success.
e.release();
return true;
}
return false;
+#endif
}
bool EVPKeyCtxPointer::setRsaPssKeygenMd(const Digest& md) {
@@ -3934,7 +5337,7 @@ bool EVPKeyCtxPointer::setRsaOaepLabel(DataPointer&& data) {
bool EVPKeyCtxPointer::setSignatureMd(const EVPMDCtxPointer& md) {
if (!ctx_) return false;
- return EVP_PKEY_CTX_set_signature_md(ctx_.get(), EVP_MD_CTX_md(md.get())) ==
+ return EVP_PKEY_CTX_set_signature_md(ctx_.get(), GetDigestCtxMd(md.get())) ==
1;
}
@@ -4124,26 +5527,213 @@ DataPointer CipherImpl(const EVPKeyPointer& key,
}
} // namespace
-Rsa::Rsa() : rsa_(nullptr) {}
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+namespace {
+int DigestAlgorithmIdentifierToNid(const unsigned char* data, size_t size) {
+ size_t sequence_header;
+ size_t sequence_len;
+ size_t sequence_total;
+ if (!ReadASN1Element(
+ data, size, 0x30, &sequence_header, &sequence_len, &sequence_total)) {
+ return NID_undef;
+ }
+
+ size_t oid_header;
+ size_t oid_len;
+ size_t oid_total;
+ const unsigned char* oid = data + sequence_header;
+ if (!ReadASN1Element(
+ oid, sequence_len, 0x06, &oid_header, &oid_len, &oid_total)) {
+ return NID_undef;
+ }
+
+ const unsigned char* oid_data = oid;
+ DeleteFnPtr obj(
+ d2i_ASN1_OBJECT(nullptr, &oid_data, oid_total));
+ if (!obj) return NID_undef;
+ return OBJ_obj2nid(obj.get());
+}
+
+bool ReadRsaPssParams(const EVP_PKEY* pkey, Rsa::PssParams* params) {
+ const int der_len = i2d_PUBKEY(pkey, nullptr);
+ if (der_len <= 0) return false;
+
+ auto der = DataPointer::Alloc(der_len);
+ if (!der) return false;
+
+ auto serialized = static_cast(der.get());
+ if (i2d_PUBKEY(pkey, &serialized) != der_len) return false;
+
+ size_t outer_header;
+ size_t outer_len;
+ size_t outer_total;
+ const auto* data = static_cast(der.get());
+ if (!ReadASN1Element(
+ data, der.size(), 0x30, &outer_header, &outer_len, &outer_total)) {
+ return false;
+ }
+
+ size_t alg_header;
+ size_t alg_len;
+ size_t alg_total;
+ const unsigned char* alg = data + outer_header;
+ if (!ReadASN1Element(
+ alg, outer_len, 0x30, &alg_header, &alg_len, &alg_total)) {
+ return false;
+ }
+
+ size_t oid_header;
+ size_t oid_len;
+ size_t oid_total;
+ const unsigned char* oid = alg + alg_header;
+ if (!ReadASN1Element(oid, alg_len, 0x06, &oid_header, &oid_len, &oid_total) ||
+ oid_total == alg_len) {
+ return false;
+ }
+
+ size_t pss_header;
+ size_t pss_len;
+ size_t pss_total;
+ const unsigned char* pss = oid + oid_total;
+ if (!ReadASN1Element(
+ pss, alg_len - oid_total, 0x30, &pss_header, &pss_len, &pss_total)) {
+ return false;
+ }
+
+ const unsigned char* cursor = pss + pss_header;
+ size_t remaining = pss_len;
+ while (remaining > 0) {
+ const unsigned char tag = cursor[0];
+ size_t item_header;
+ size_t item_len;
+ size_t item_total;
+ if (!ReadASN1Element(
+ cursor, remaining, tag, &item_header, &item_len, &item_total)) {
+ return false;
+ }
+
+ const unsigned char* item = cursor + item_header;
+ switch (tag) {
+ case 0xa0: {
+ const int nid = DigestAlgorithmIdentifierToNid(item, item_len);
+ if (nid != NID_undef) params->digest = OBJ_nid2ln(nid);
+ break;
+ }
+ case 0xa1: {
+ size_t mgf_header;
+ size_t mgf_len;
+ size_t mgf_total;
+ if (!ReadASN1Element(
+ item, item_len, 0x30, &mgf_header, &mgf_len, &mgf_total)) {
+ return false;
+ }
+ const unsigned char* mgf = item + mgf_header;
+ size_t mgf_oid_header;
+ size_t mgf_oid_len;
+ size_t mgf_oid_total;
+ if (!ReadASN1Element(mgf,
+ mgf_len,
+ 0x06,
+ &mgf_oid_header,
+ &mgf_oid_len,
+ &mgf_oid_total) ||
+ mgf_oid_total == mgf_len) {
+ return false;
+ }
+ const int nid = DigestAlgorithmIdentifierToNid(mgf + mgf_oid_total,
+ mgf_len - mgf_oid_total);
+ if (nid != NID_undef) params->mgf1_digest = OBJ_nid2ln(nid);
+ break;
+ }
+ case 0xa2: {
+ size_t int_header;
+ size_t int_len;
+ size_t int_total;
+ if (!ReadASN1Element(
+ item, item_len, 0x02, &int_header, &int_len, &int_total)) {
+ return false;
+ }
+ // TODO(panva): In a semver-major, reject malformed RSA-PSS parameters
+ // at key import instead of omitting asymmetricKeyDetails fields.
+ if (int_len == 0 || int_len > sizeof(uint64_t) ||
+ (item[int_header] & 0x80) != 0) {
+ return false;
+ }
+ uint64_t salt_length = 0;
+ for (size_t n = 0; n < int_len; n++) {
+ salt_length = (salt_length << 8) | item[int_header + n];
+ }
+ params->salt_length = static_cast(salt_length);
+ break;
+ }
+ }
+
+ cursor += item_total;
+ remaining -= item_total;
+ }
+
+ return true;
+}
+} // namespace
+
+Rsa::Rsa() : rsa_(false) {}
+
+Rsa::Rsa(const EVP_PKEY* pkey) : Rsa() {
+ const int type = EVPKeyPointer::id(pkey);
+ if (type != EVP_PKEY_RSA && type != EVP_PKEY_RSA_PSS) return;
+ if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_N, &n_) ||
+ !GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_E, &e_)) {
+ return;
+ }
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_D, &d_);
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p_);
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q_);
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dp_);
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dq_);
+ GetOptionalPKeyBnParam(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &qi_);
+
+ if (type == EVP_PKEY_RSA_PSS) {
+ MarkPopErrorOnReturn pop_errors;
+ PssParams params;
+ if (ReadRsaPssParams(pkey, ¶ms)) pss_params_ = params;
+ }
+ rsa_ = true;
+}
+#else
+Rsa::Rsa() : rsa_(nullptr) {}
Rsa::Rsa(OSSL3_CONST RSA* ptr) : rsa_(ptr) {}
+#endif
const Rsa::PublicKey Rsa::getPublicKey() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (!rsa_) return {};
+ return PublicKey{n_.get(), e_.get(), d_.get()};
+#else
if (rsa_ == nullptr) return {};
PublicKey key;
RSA_get0_key(rsa_, &key.n, &key.e, &key.d);
return key;
+#endif
}
const Rsa::PrivateKey Rsa::getPrivateKey() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (!rsa_) return {};
+ return PrivateKey{p_.get(), q_.get(), dp_.get(), dq_.get(), qi_.get()};
+#else
if (rsa_ == nullptr) return {};
PrivateKey key;
RSA_get0_factors(rsa_, &key.p, &key.q);
RSA_get0_crt_params(rsa_, &key.dp, &key.dq, &key.qi);
return key;
+#endif
}
const std::optional Rsa::getPssParams() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return pss_params_;
+#else
if (rsa_ == nullptr) return std::nullopt;
const RSA_PSS_PARAMS* params = RSA_get0_pss_params(rsa_);
if (params == nullptr) return std::nullopt;
@@ -4176,16 +5766,36 @@ const std::optional Rsa::getPssParams() const {
}
}
return ret;
+#endif
+}
+
+BIOPointer Rsa::derPublicKey() const {
+ auto bio = BIOPointer::NewMem();
+ if (!bio) return {};
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ auto pkey = EVPKeyPointer::NewRSA(*this);
+ if (!pkey || i2d_PUBKEY_bio(bio.get(), pkey.get()) != 1) return {};
+#else
+ if (rsa_ == nullptr || i2d_RSA_PUBKEY_bio(bio.get(), rsa_) != 1) return {};
+#endif
+ return bio;
}
bool Rsa::setPublicKey(BignumPointer&& n, BignumPointer&& e) {
if (!n || !e) return false;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ n_.reset(n.release());
+ e_.reset(e.release());
+ rsa_ = true;
+ return true;
+#else
if (RSA_set0_key(const_cast(rsa_), n.get(), e.get(), nullptr) == 1) {
n.release();
e.release();
return true;
}
return false;
+#endif
}
bool Rsa::setPrivateKey(BignumPointer&& d,
@@ -4194,6 +5804,17 @@ bool Rsa::setPrivateKey(BignumPointer&& d,
BignumPointer&& dp,
BignumPointer&& dq,
BignumPointer&& qi) {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (!d || !q || !p || !dp || !dq || !qi) return false;
+ d_.reset(d.release());
+ q_.reset(q.release());
+ p_.reset(p.release());
+ dp_.reset(dp.release());
+ dq_.reset(dq.release());
+ qi_.reset(qi.release());
+ rsa_ = n_ != nullptr && e_ != nullptr;
+ return rsa_;
+#else
if (!RSA_set0_key(const_cast(rsa_), nullptr, nullptr, d.get())) {
return false;
}
@@ -4213,6 +5834,7 @@ bool Rsa::setPrivateKey(BignumPointer&& d,
dq.release();
qi.release();
return true;
+#endif
}
DataPointer Rsa::encrypt(const EVPKeyPointer& key,
@@ -4335,12 +5957,99 @@ void Cipher::ForEach(Cipher::CipherNameCallback callback) {
// ============================================================================
-Ec::Ec() : ec_(nullptr) {}
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+Ec::Ec() : ec_(nullptr), pub_(nullptr) {}
+
+Ec::Ec(const EVP_PKEY* pkey) : Ec() {
+ if (EVPKeyPointer::id(pkey) != EVP_PKEY_EC) return;
+ char group_name[80];
+ size_t group_name_len = 0;
+ if (EVP_PKEY_get_utf8_string_param(pkey,
+ OSSL_PKEY_PARAM_GROUP_NAME,
+ group_name,
+ sizeof(group_name),
+ &group_name_len) != 1) {
+ return;
+ }
+
+ const int nid = GetCurveIdFromName(group_name);
+ if (nid == NID_undef) return;
+ ec_.reset(EC_GROUP_new_by_curve_name(nid));
+ if (!ec_) return;
+
+ size_t public_key_len = 0;
+ if (EVP_PKEY_get_octet_string_param(
+ pkey, OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &public_key_len) != 1) {
+ return;
+ }
+
+ auto public_key = DataPointer::Alloc(public_key_len);
+ if (!public_key ||
+ EVP_PKEY_get_octet_string_param(
+ pkey,
+ OSSL_PKEY_PARAM_PUB_KEY,
+ static_cast(public_key.get()),
+ public_key.size(),
+ &public_key_len) != 1 ||
+ public_key_len == 0) {
+ ec_.reset();
+ return;
+ }
+
+ const auto* public_key_data =
+ static_cast(public_key.get());
+ switch (public_key_data[0]) {
+ case POINT_CONVERSION_COMPRESSED:
+ case POINT_CONVERSION_COMPRESSED + 1:
+ form_ = POINT_CONVERSION_COMPRESSED;
+ break;
+ case POINT_CONVERSION_UNCOMPRESSED:
+ form_ = POINT_CONVERSION_UNCOMPRESSED;
+ break;
+ case POINT_CONVERSION_HYBRID:
+ case POINT_CONVERSION_HYBRID + 1:
+ form_ = POINT_CONVERSION_HYBRID;
+ break;
+ default:
+ ec_.reset();
+ return;
+ }
+ auto point = ECPointPointer::New(ec_.get());
+ if (!point ||
+ !point.setFromBuffer({public_key_data, public_key_len}, ec_.get())) {
+ ec_.reset();
+ return;
+ }
+ pub_.reset(point.release());
+}
+#else
+Ec::Ec() : ec_(nullptr) {}
Ec::Ec(OSSL3_CONST EC_KEY* key) : ec_(key) {}
+#endif
const EC_GROUP* Ec::getGroup() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return ec_.get();
+#else
return ECKeyPointer::GetGroup(ec_);
+#endif
+}
+
+const EC_POINT* Ec::getPublicKey() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return pub_.get();
+#else
+ return ECKeyPointer::GetPublicKey(ec_);
+#endif
+}
+
+point_conversion_form_t Ec::getPointConversionForm() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ return form_;
+#else
+ return EC_KEY_get_conv_form(ec_);
+#endif
}
int Ec::getCurve() const {
@@ -4444,7 +6153,7 @@ size_t EVPMDCtxPointer::getDigestSize() const {
const EVP_MD* EVPMDCtxPointer::getDigest() const {
if (!ctx_) return nullptr;
- return EVP_MD_CTX_md(ctx_.get());
+ return GetDigestCtxMd(ctx_.get());
}
bool EVPMDCtxPointer::hasXofFlag() const {
@@ -4986,31 +6695,61 @@ std::pair X509Name::Iterator::operator*() const {
// ============================================================================
-Dsa::Dsa() : dsa_(nullptr) {}
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+Dsa::Dsa() : dsa_(false) {}
+Dsa::Dsa(const EVP_PKEY* pkey) : Dsa() {
+ if (EVPKeyPointer::id(pkey) != EVP_PKEY_DSA) return;
+ if (!GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_P, &p_) ||
+ !GetPKeyBnParam(pkey, OSSL_PKEY_PARAM_FFC_Q, &q_)) {
+ return;
+ }
+ dsa_ = true;
+}
+#else
+Dsa::Dsa() : dsa_(nullptr) {}
Dsa::Dsa(OSSL3_CONST DSA* dsa) : dsa_(dsa) {}
+#endif
const BIGNUM* Dsa::getP() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (!dsa_) return nullptr;
+ return p_.get();
+#else
if (dsa_ == nullptr) return nullptr;
const BIGNUM* p;
DSA_get0_pqg(dsa_, &p, nullptr, nullptr);
return p;
+#endif
}
const BIGNUM* Dsa::getQ() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (!dsa_) return nullptr;
+ return q_.get();
+#else
if (dsa_ == nullptr) return nullptr;
const BIGNUM* q;
DSA_get0_pqg(dsa_, nullptr, &q, nullptr);
return q;
+#endif
}
size_t Dsa::getModulusLength() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (!dsa_) return 0;
+#else
if (dsa_ == nullptr) return 0;
+#endif
return BignumPointer::GetBitCount(getP());
}
size_t Dsa::getDivisorLength() const {
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ if (!dsa_) return 0;
+#else
if (dsa_ == nullptr) return 0;
+#endif
return BignumPointer::GetBitCount(getQ());
}
diff --git a/deps/ncrypto/ncrypto.gyp b/deps/ncrypto/ncrypto.gyp
index 1747f3ea0149b9..804a664fa0a2a1 100644
--- a/deps/ncrypto/ncrypto.gyp
+++ b/deps/ncrypto/ncrypto.gyp
@@ -2,10 +2,25 @@
'variables': {
'ncrypto_bssl_libdecrepit_missing%': 1,
'ncrypto_sources': [
- 'engine.cc',
'ncrypto.cc',
'ncrypto.h',
],
+ 'ncrypto_engine_sources': [
+ 'engine.cc',
+ 'ncrypto.h',
+ ],
+ 'ncrypto_strict_defines': [
+ 'OPENSSL_API_COMPAT=30000',
+ 'OPENSSL_NO_DEPRECATED',
+ ],
+ 'ncrypto_legacy_openssl_defines': [
+ 'OPENSSL_API_COMPAT=0x10100000L',
+ ],
+ 'ncrypto_engine_defines': [
+ 'OPENSSL_API_COMPAT=30000',
+ 'OPENSSL_SUPPRESS_DEPRECATED',
+ 'NCRYPTO_ENGINE_COMPAT=1',
+ ],
},
'targets': [
{
@@ -20,9 +35,25 @@
'defines': [
'NCRYPTO_BSSL_LIBDECREPIT_MISSING=<(ncrypto_bssl_libdecrepit_missing)',
],
+ 'conditions': [
+ ['openssl_is_boringssl=="false" and openssl_version >= 0x3000000f', {
+ 'defines!': [ '<@(ncrypto_legacy_openssl_defines)' ],
+ 'defines': [ '<@(ncrypto_strict_defines)' ],
+ }],
+ ],
},
'sources': [ '<@(ncrypto_sources)' ],
'conditions': [
+ ['openssl_is_boringssl=="false" and openssl_version >= 0x3000000f', {
+ 'defines!': [ '<@(ncrypto_legacy_openssl_defines)' ],
+ 'defines': [ '<@(ncrypto_strict_defines)' ],
+ 'dependencies': [
+ 'ncrypto_engine',
+ ],
+ }],
+ ['openssl_is_boringssl=="false" and openssl_version < 0x3000000f', {
+ 'sources': [ '<@(ncrypto_engine_sources)' ],
+ }],
['node_shared_openssl=="false"', {
'dependencies': [
'../openssl/openssl.gyp:openssl'
@@ -30,5 +61,28 @@
}],
]
},
- ]
+ ],
+ 'conditions': [
+ ['openssl_is_boringssl=="false" and openssl_version >= 0x3000000f', {
+ 'targets': [
+ {
+ 'target_name': 'ncrypto_engine',
+ 'type': 'static_library',
+ 'include_dirs': ['.'],
+ 'defines': [
+ 'NCRYPTO_BSSL_LIBDECREPIT_MISSING=<(ncrypto_bssl_libdecrepit_missing)',
+ '<@(ncrypto_engine_defines)',
+ ],
+ 'sources': [ '<@(ncrypto_engine_sources)' ],
+ 'conditions': [
+ ['node_shared_openssl=="false"', {
+ 'dependencies': [
+ '../openssl/openssl.gyp:openssl'
+ ]
+ }],
+ ]
+ },
+ ],
+ }],
+ ],
}
diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h
index 76d79652607139..92b36a15dd6250 100644
--- a/deps/ncrypto/ncrypto.h
+++ b/deps/ncrypto/ncrypto.h
@@ -19,9 +19,10 @@
#include
#include
#include
-#ifndef OPENSSL_NO_ENGINE
+#if defined(NCRYPTO_ENGINE_COMPAT) && NCRYPTO_ENGINE_COMPAT && \
+ !defined(OPENSSL_NO_ENGINE)
#include
-#endif // !OPENSSL_NO_ENGINE
+#endif // NCRYPTO_ENGINE_COMPAT && !OPENSSL_NO_ENGINE
#ifndef OPENSSL_VERSION_PREREQ
#define OPENSSL_VERSION_PREREQ(maj, min) \
@@ -40,6 +41,40 @@
#define NCRYPTO_USE_BORINGSSL_EVP_DO_ALL_FALLBACK 0
#endif
+// Backend split:
+// - OpenSSL >= 3 uses provider APIs and hides deprecated low-level objects.
+// - BoringSSL has its own API-compatible branch.
+// - OpenSSL < 3 remains the legacy fallback branch.
+#if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 0)
+#define NCRYPTO_USE_OPENSSL3_PROVIDER 1
+#else
+#define NCRYPTO_USE_OPENSSL3_PROVIDER 0
+#endif
+
+#ifdef OPENSSL_IS_BORINGSSL
+#define NCRYPTO_USE_BORINGSSL 1
+#else
+#define NCRYPTO_USE_BORINGSSL 0
+#endif
+
+#if !NCRYPTO_USE_OPENSSL3_PROVIDER && !NCRYPTO_USE_BORINGSSL
+#define NCRYPTO_USE_LEGACY_OPENSSL 1
+#else
+#define NCRYPTO_USE_LEGACY_OPENSSL 0
+#endif
+
+#if NCRYPTO_USE_BORINGSSL || NCRYPTO_USE_LEGACY_OPENSSL
+#define NCRYPTO_USE_LEGACY_KEY_TYPES 1
+#else
+#define NCRYPTO_USE_LEGACY_KEY_TYPES 0
+#endif
+
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+#include
+#include
+#include
+#endif
+
// The FIPS-related functions are only available
// when the OpenSSL itself was compiled with FIPS support.
#if defined(OPENSSL_FIPS) && !OPENSSL_VERSION_PREREQ(3, 0)
@@ -112,7 +147,6 @@
#define EVP_PKEY_ML_KEM_512 NID_ML_KEM_512
#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768
#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024
-#include
#elif OPENSSL_WITH_BORINGSSL_PQC
#define EVP_PKEY_ML_KEM_768 NID_ML_KEM_768
#define EVP_PKEY_ML_KEM_1024 NID_ML_KEM_1024
@@ -303,7 +337,9 @@ template
using DeleteFnPtr = typename FunctionDeleter::Pointer;
using PKCS8Pointer = DeleteFnPtr;
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
using RSAPointer = DeleteFnPtr;
+#endif
using SSLSessionPointer = DeleteFnPtr;
class BIOPointer;
@@ -502,11 +538,23 @@ class Cipher final {
class Dsa final {
public:
Dsa();
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ explicit Dsa(const EVP_PKEY* pkey);
+#else
Dsa(OSSL3_CONST DSA* dsa);
+#endif
NCRYPTO_DISALLOW_COPY_AND_MOVE(Dsa)
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ inline operator bool() const {
+ return dsa_;
+ }
+#else
inline operator bool() const { return dsa_ != nullptr; }
+#endif
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
inline operator OSSL3_CONST DSA*() const { return dsa_; }
+#endif
const BIGNUM* getP() const;
const BIGNUM* getQ() const;
@@ -514,7 +562,13 @@ class Dsa final {
size_t getDivisorLength() const;
private:
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ bool dsa_ = false;
+ DeleteFnPtr p_;
+ DeleteFnPtr q_;
+#else
OSSL3_CONST DSA* dsa_;
+#endif
};
// ============================================================================
@@ -523,11 +577,23 @@ class Dsa final {
class Rsa final {
public:
Rsa();
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ explicit Rsa(const EVP_PKEY* pkey);
+#else
Rsa(OSSL3_CONST RSA* rsa);
+#endif
NCRYPTO_DISALLOW_COPY_AND_MOVE(Rsa)
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ inline operator bool() const {
+ return rsa_;
+ }
+#else
inline operator bool() const { return rsa_ != nullptr; }
+#endif
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
inline operator OSSL3_CONST RSA*() const { return rsa_; }
+#endif
struct PublicKey {
const BIGNUM* n;
@@ -561,6 +627,8 @@ class Rsa final {
using CipherParams = Cipher::CipherParams;
+ BIOPointer derPublicKey() const;
+
static DataPointer encrypt(const EVPKeyPointer& key,
const CipherParams& params,
const Buffer in);
@@ -569,20 +637,41 @@ class Rsa final {
const Buffer in);
private:
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ bool rsa_ = false;
+ DeleteFnPtr n_;
+ DeleteFnPtr e_;
+ DeleteFnPtr d_;
+ DeleteFnPtr p_;
+ DeleteFnPtr q_;
+ DeleteFnPtr dp_;
+ DeleteFnPtr dq_;
+ DeleteFnPtr qi_;
+ std::optional pss_params_;
+#else
OSSL3_CONST RSA* rsa_;
+#endif
};
class Ec final {
public:
Ec();
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ explicit Ec(const EVP_PKEY* pkey);
+#else
Ec(OSSL3_CONST EC_KEY* key);
+#endif
NCRYPTO_DISALLOW_COPY_AND_MOVE(Ec)
const EC_GROUP* getGroup() const;
+ const EC_POINT* getPublicKey() const;
+ point_conversion_form_t getPointConversionForm() const;
int getCurve() const;
inline operator bool() const { return ec_ != nullptr; }
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
inline operator OSSL3_CONST EC_KEY*() const { return ec_; }
+#endif
static int GetCurveIdFromName(const char* name);
@@ -590,7 +679,13 @@ class Ec final {
static bool GetCurves(GetCurveCallback callback);
private:
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ DeleteFnPtr ec_;
+ DeleteFnPtr pub_;
+ point_conversion_form_t form_ = POINT_CONVERSION_UNCOMPRESSED;
+#else
OSSL3_CONST EC_KEY* ec_ = nullptr;
+#endif
};
// A managed pointer to a buffer of data. When destroyed the underlying
@@ -926,7 +1021,11 @@ class EVPKeyPointer final {
const Buffer& data);
#endif
static EVPKeyPointer NewDH(DHPointer&& dh);
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ static EVPKeyPointer NewRSA(const Rsa& rsa);
+#else
static EVPKeyPointer NewRSA(RSAPointer&& rsa);
+#endif
enum class PKEncodingType {
// RSAPublicKey / RSAPrivateKey according to PKCS#1.
@@ -998,7 +1097,9 @@ class EVPKeyPointer final {
bool assign(const ECKeyPointer& eckey);
bool set(const ECKeyPointer& eckey);
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
operator const EC_KEY*() const;
+#endif
inline bool operator==(std::nullptr_t) const noexcept {
return pkey_ == nullptr;
@@ -1070,29 +1171,53 @@ class DHPointer final {
static DHPointer New(size_t bits, unsigned int generator);
DHPointer() = default;
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ explicit DHPointer(EVPKeyPointer&& key, const char* group_name = nullptr);
+ DHPointer(BignumPointer&& p, BignumPointer&& g, const char* group_name);
+#else
explicit DHPointer(DH* dh);
+#endif
DHPointer(DHPointer&& other) noexcept;
DHPointer& operator=(DHPointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(DHPointer)
~DHPointer();
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ inline bool operator==(std::nullptr_t) noexcept {
+ return !operator bool();
+ }
+ inline operator bool() const {
+ return dh_ != nullptr || (p_ && g_);
+ }
+#else
inline bool operator==(std::nullptr_t) noexcept { return dh_ == nullptr; }
inline operator bool() const { return dh_ != nullptr; }
+#endif
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
inline DH* get() const { return dh_.get(); }
void reset(DH* dh = nullptr);
DH* release();
+#else
+ inline EVP_PKEY* get() const {
+ return dh_.get();
+ }
+ void reset(EVP_PKEY* dh = nullptr);
+ EVP_PKEY* release();
+#endif
enum class CheckResult {
NONE,
- P_NOT_PRIME = DH_CHECK_P_NOT_PRIME,
- P_NOT_SAFE_PRIME = DH_CHECK_P_NOT_SAFE_PRIME,
- UNABLE_TO_CHECK_GENERATOR = DH_UNABLE_TO_CHECK_GENERATOR,
- NOT_SUITABLE_GENERATOR = DH_NOT_SUITABLE_GENERATOR,
- Q_NOT_PRIME = DH_CHECK_Q_NOT_PRIME,
+ P_NOT_PRIME = 0x01,
+ P_NOT_SAFE_PRIME = 0x02,
+ UNABLE_TO_CHECK_GENERATOR = 0x04,
+ NOT_SUITABLE_GENERATOR = 0x08,
+ Q_NOT_PRIME = 0x10,
#ifndef OPENSSL_IS_BORINGSSL
// Boringssl does not define the DH_CHECK_INVALID_[Q or J]_VALUE
- INVALID_Q = DH_CHECK_INVALID_Q_VALUE,
- INVALID_J = DH_CHECK_INVALID_J_VALUE,
+ INVALID_Q = 0x20,
+ INVALID_J = 0x40,
+ MODULUS_TOO_SMALL = 0x80,
+ MODULUS_TOO_LARGE = 0x100,
#endif
CHECK_FAILED = 512,
};
@@ -1114,11 +1239,12 @@ class DHPointer final {
CheckPublicKeyResult checkPublicKey(const BignumPointer& pub_key);
DataPointer getPrime() const;
+ size_t getPrimeBits() const;
DataPointer getGenerator() const;
DataPointer getPublicKey() const;
DataPointer getPrivateKey() const;
bool hasPrivateKey() const;
- DataPointer generateKeys() const;
+ DataPointer generateKeys();
DataPointer computeSecret(const BignumPointer& peer) const;
bool setPublicKey(BignumPointer&& key);
@@ -1130,7 +1256,16 @@ class DHPointer final {
const EVPKeyPointer& theirKey);
private:
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ DeleteFnPtr dh_;
+ BignumPointer p_;
+ BignumPointer g_;
+ BignumPointer pub_key_;
+ BignumPointer pvt_key_;
+ const char* group_name_ = nullptr;
+#else
DeleteFnPtr dh_;
+#endif
};
struct StackOfX509Deleter {
@@ -1439,20 +1574,38 @@ class ECPointPointer final {
};
class ECKeyPointer final {
+ friend class EVPKeyPointer;
+
public:
ECKeyPointer();
+ explicit ECKeyPointer(const EVPKeyPointer& key);
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
explicit ECKeyPointer(EC_KEY* key);
+#endif
ECKeyPointer(ECKeyPointer&& other) noexcept;
ECKeyPointer& operator=(ECKeyPointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(ECKeyPointer)
~ECKeyPointer();
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ inline bool operator==(std::nullptr_t) noexcept {
+ return group_ == nullptr;
+ }
+ inline operator bool() const {
+ return group_ != nullptr;
+ }
+#else
inline bool operator==(std::nullptr_t) noexcept { return key_ == nullptr; }
inline operator bool() const { return key_ != nullptr; }
+#endif
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
inline EC_KEY* get() const { return key_.get(); }
inline operator EC_KEY*() const { return key_.get(); }
void reset(EC_KEY* key = nullptr);
EC_KEY* release();
+#else
+ void reset();
+#endif
ECKeyPointer clone() const;
bool setPrivateKey(const BignumPointer& priv);
@@ -1460,6 +1613,7 @@ class ECKeyPointer final {
bool setPublicKeyRaw(const BignumPointer& x, const BignumPointer& y);
bool generate();
bool checkKey() const;
+ DataPointer computeSecret(const ECPointPointer& peer) const;
const EC_GROUP* getGroup() const;
const BIGNUM* getPrivateKey() const;
@@ -1468,14 +1622,22 @@ class ECKeyPointer final {
static ECKeyPointer New(const EC_GROUP* group);
static ECKeyPointer NewByCurveName(int nid);
+#if NCRYPTO_USE_LEGACY_KEY_TYPES
static const EC_POINT* GetPublicKey(const EC_KEY* key);
static const BIGNUM* GetPrivateKey(const EC_KEY* key);
static const EC_GROUP* GetGroup(const EC_KEY* key);
static int GetGroupName(const EC_KEY* key);
static bool Check(const EC_KEY* key);
+#endif
private:
+#if NCRYPTO_USE_OPENSSL3_PROVIDER
+ DeleteFnPtr group_;
+ DeleteFnPtr pub_;
+ DeleteFnPtr priv_;
+#else
DeleteFnPtr key_;
+#endif
};
class EVPMDCtxPointer final {
@@ -1642,24 +1804,23 @@ class EnginePointer final {
public:
EnginePointer() = default;
- explicit EnginePointer(ENGINE* engine_, bool finish_on_exit = false);
+ explicit EnginePointer(void* engine_, bool finish_on_exit = false);
EnginePointer(EnginePointer&& other) noexcept;
EnginePointer& operator=(EnginePointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(EnginePointer)
~EnginePointer();
inline operator bool() const { return engine != nullptr; }
- inline ENGINE* get() { return engine; }
inline void setFinishOnExit() { finish_on_exit = true; }
- void reset(ENGINE* engine_ = nullptr, bool finish_on_exit_ = false);
+ void reset(void* engine_ = nullptr, bool finish_on_exit_ = false);
bool setAsDefault(uint32_t flags, CryptoErrorList* errors = nullptr);
bool init(bool finish_on_exit = false);
EVPKeyPointer loadPrivateKey(const char* key_name);
+ bool setClientCertEngine(SSL_CTX* ctx);
- // Release ownership of the ENGINE* pointer.
- ENGINE* release();
+ void* release();
// Retrieve an OpenSSL Engine instance by name. If the name does not
// identify a valid named engine, the returned EnginePointer will be
@@ -1671,7 +1832,7 @@ class EnginePointer final {
static void initEnginesOnce();
private:
- ENGINE* engine = nullptr;
+ void* engine = nullptr;
bool finish_on_exit = false;
};
#endif // !OPENSSL_NO_ENGINE
diff --git a/deps/ncrypto/unofficial.gni b/deps/ncrypto/unofficial.gni
index 7cb27d22b9b8e0..dad4fbbf16f060 100644
--- a/deps/ncrypto/unofficial.gni
+++ b/deps/ncrypto/unofficial.gni
@@ -26,7 +26,11 @@ template("ncrypto_gn_build") {
source_set(target_name) {
forward_variables_from(invoker, "*")
public_configs = [ ":ncrypto_config" ]
- sources = gypi_values.ncrypto_sources
+ defines = [
+ "NCRYPTO_ENGINE_COMPAT=1",
+ "OPENSSL_SUPPRESS_DEPRECATED",
+ ]
+ sources = gypi_values.ncrypto_sources + gypi_values.ncrypto_engine_sources
deps = [ "$node_openssl_path" ]
}
}
diff --git a/deps/npm/docs/content/commands/npm-approve-scripts.md b/deps/npm/docs/content/commands/npm-approve-scripts.md
index a89a250a769dc1..c4d3c15a8149f2 100644
--- a/deps/npm/docs/content/commands/npm-approve-scripts.md
+++ b/deps/npm/docs/content/commands/npm-approve-scripts.md
@@ -59,6 +59,14 @@ the command cannot infer. Existing `false` entries always win;
`approve-scripts` will not silently re-allow a package you previously
denied.
+If a registry dependency has no `resolved` URL in your `package-lock.json`
+(for example, an older lockfile or one written with
+`omit-lockfile-registry-resolved`), npm cannot verify a trusted version for
+it and cannot pin it: a `pkg@1.2.3` entry never matches, so the package
+keeps appearing under `--allow-scripts-pending`. `approve-scripts` approves
+these by name (`pkg: true`) and warns when it does. To restore pinning,
+refresh the lockfile with `npm install`.
+
### Examples
```bash
diff --git a/deps/npm/docs/content/commands/npm-ci.md b/deps/npm/docs/content/commands/npm-ci.md
index bc460070459604..741caf5eae7026 100644
--- a/deps/npm/docs/content/commands/npm-ci.md
+++ b/deps/npm/docs/content/commands/npm-ci.md
@@ -74,8 +74,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -298,6 +306,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
diff --git a/deps/npm/docs/content/commands/npm-dedupe.md b/deps/npm/docs/content/commands/npm-dedupe.md
index 8186ee2c1f31c7..a48bf1bd622e5c 100644
--- a/deps/npm/docs/content/commands/npm-dedupe.md
+++ b/deps/npm/docs/content/commands/npm-dedupe.md
@@ -74,8 +74,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
diff --git a/deps/npm/docs/content/commands/npm-exec.md b/deps/npm/docs/content/commands/npm-exec.md
index 13a0939209a5ea..ff08d07786a8d3 100644
--- a/deps/npm/docs/content/commands/npm-exec.md
+++ b/deps/npm/docs/content/commands/npm-exec.md
@@ -194,6 +194,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
diff --git a/deps/npm/docs/content/commands/npm-find-dupes.md b/deps/npm/docs/content/commands/npm-find-dupes.md
index 52bb59b9f81b57..2d72186fe5a22e 100644
--- a/deps/npm/docs/content/commands/npm-find-dupes.md
+++ b/deps/npm/docs/content/commands/npm-find-dupes.md
@@ -25,8 +25,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
diff --git a/deps/npm/docs/content/commands/npm-install-ci-test.md b/deps/npm/docs/content/commands/npm-install-ci-test.md
index 4528f63dfe28e8..2194a4df84a80c 100644
--- a/deps/npm/docs/content/commands/npm-install-ci-test.md
+++ b/deps/npm/docs/content/commands/npm-install-ci-test.md
@@ -27,8 +27,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -251,6 +259,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
diff --git a/deps/npm/docs/content/commands/npm-install-scripts.md b/deps/npm/docs/content/commands/npm-install-scripts.md
new file mode 100644
index 00000000000000..4182392c9a3d1e
--- /dev/null
+++ b/deps/npm/docs/content/commands/npm-install-scripts.md
@@ -0,0 +1,166 @@
+---
+title: npm-install-scripts
+section: 1
+description: Manage install-script approvals for dependencies
+---
+
+### Synopsis
+
+```bash
+npm install-scripts approve [ ...]
+npm install-scripts approve --all
+npm install-scripts deny [ ...]
+npm install-scripts deny --all
+npm install-scripts ls
+npm install-scripts prune
+```
+
+Note: This command is unaware of workspaces.
+
+### Description
+
+Manages the `allowScripts` field in your project's `package.json`, which
+records which of your dependencies are permitted to run install scripts
+(`preinstall`, `install`, `postinstall`, and `prepare` for non-registry
+sources). This is the recommended way to maintain that field.
+
+Dependency install scripts are blocked by default. Install commands
+silently skip lifecycle scripts for any dependency that does not have a
+matching entry in `allowScripts`, and end with a list of the packages
+whose scripts were skipped so you can review them here.
+
+This command only works inside a project that has a `package.json`. Running
+it with `--global` (`-g`) fails with an `EGLOBAL` error, since global
+installs (`npm install -g`) and one-off executions (`npm exec` / `npx`) have
+no project `package.json` to write to. To allow install scripts in those
+contexts, use the `--allow-scripts` flag at install time (for example
+`npm install -g --allow-scripts=canvas,sharp`) or persist the setting with
+`npm config set allow-scripts=canvas,sharp --location=user`.
+
+There are four subcommands:
+
+```bash
+npm install-scripts approve [ ...]
+npm install-scripts approve --all
+npm install-scripts deny [ ...]
+npm install-scripts deny --all
+npm install-scripts ls
+npm install-scripts prune
+```
+
+`approve` allows install scripts for the named packages. `` matches
+every installed version of that package. By default it writes pinned entries
+(`pkg@1.2.3`), which keep their approval narrowed to the specific version you
+reviewed. Pass `--no-allow-scripts-pin` to write name-only entries that allow
+any future version. `--all` approves every package with unreviewed install
+scripts in one go.
+
+`deny` records an explicit denial for the named packages (a name-only `false`
+entry), which survives `npm install-scripts approve --all` and excludes the
+package from any future blanket approval. `--all` denies every package with
+unreviewed install scripts.
+
+`ls` is read-only: it lists every package whose install scripts are not yet
+covered by `allowScripts`, without modifying `package.json`.
+
+`prune` removes `allowScripts` entries that no longer match an installed
+package with an install script, either because the package is no longer
+installed (a transitive dependency changed, or a pinned `pkg@1.2.3` was
+upgraded) or because it no longer has an install script. Both approvals
+(`true`) and denials (`false`) are removed. It edits only the `allowScripts`
+field in `package.json`, never `.npmrc` or `--allow-scripts`. Pass `--dry-run`
+to preview without writing. Unparseable keys are left alone.
+
+`approve` honours the asymmetric pin rule: if you re-approve a package whose
+installed version has changed, the existing pin is rewritten to track the new
+installed version. Multi-version statements (`pkg@1 || 2`) are left alone,
+since they likely capture intent that the command cannot infer. Existing
+`false` entries always win; `approve` will not silently re-allow a package you
+previously denied.
+
+The standalone commands [`npm approve-scripts`](/commands/npm-approve-scripts)
+and [`npm deny-scripts`](/commands/npm-deny-scripts) are aliases for
+`npm install-scripts approve` and `npm install-scripts deny`.
+
+### Examples
+
+```bash
+# Approve all currently-installed install scripts after reviewing them
+npm install-scripts approve --all
+
+# Approve specific packages, pinned to their installed version
+npm install-scripts approve canvas sharp
+
+# Deny a package so it stays blocked
+npm install-scripts deny telemetry-pkg
+
+# Preview which packages still need review
+npm install-scripts ls
+
+# Preview stale allowScripts entries, then remove them
+npm install-scripts prune --dry-run
+npm install-scripts prune
+```
+
+### Configuration
+
+#### `all`
+
+* Default: false
+* Type: Boolean
+
+Show or act on all packages, not just the ones your project directly depends
+on. For `npm outdated` and `npm ls` this lists every outdated or installed
+package. For `npm approve-scripts` and `npm deny-scripts` it selects every
+package with pending install scripts.
+
+
+
+#### `allow-scripts-pin`
+
+* Default: true
+* Type: Boolean
+
+Write pinned (`pkg@version`) entries when approving install scripts. Set to
+`false` to write name-only entries that allow any version. Has no effect on
+`npm deny-scripts`, which always writes name-only entries regardless of this
+setting.
+
+
+
+#### `dry-run`
+
+* Default: false
+* Type: Boolean
+
+Indicates that you don't want npm to make any changes and that it should
+only report what it would have done. This can be passed into any of the
+commands that modify your local installation, eg, `install`, `update`,
+`dedupe`, `uninstall`, as well as `pack` and `publish`.
+
+Note: This is NOT honored by other network related commands, eg `dist-tags`,
+`owner`, etc.
+
+
+
+#### `json`
+
+* Default: false
+* Type: Boolean
+
+Whether or not to output JSON data, rather than the normal output.
+
+* In `npm pkg set` it enables parsing set values with JSON.parse() before
+ saving them to your `package.json`.
+
+Not supported by all npm commands.
+
+
+
+### See Also
+
+* [npm approve-scripts](/commands/npm-approve-scripts)
+* [npm deny-scripts](/commands/npm-deny-scripts)
+* [npm install](/commands/npm-install)
+* [npm rebuild](/commands/npm-rebuild)
+* [package.json](/configuring-npm/package-json)
diff --git a/deps/npm/docs/content/commands/npm-install-test.md b/deps/npm/docs/content/commands/npm-install-test.md
index 44de058c56aa05..e13f79a51e6f18 100644
--- a/deps/npm/docs/content/commands/npm-install-test.md
+++ b/deps/npm/docs/content/commands/npm-install-test.md
@@ -68,8 +68,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -328,6 +336,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
@@ -375,6 +387,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -397,6 +413,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm-install.md b/deps/npm/docs/content/commands/npm-install.md
index 5bd36c1fa45320..98d69d5e842451 100644
--- a/deps/npm/docs/content/commands/npm-install.md
+++ b/deps/npm/docs/content/commands/npm-install.md
@@ -410,8 +410,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -670,6 +678,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
@@ -717,6 +729,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -739,6 +755,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm-link.md b/deps/npm/docs/content/commands/npm-link.md
index 37efda66408fbd..fa9626c4edd804 100644
--- a/deps/npm/docs/content/commands/npm-link.md
+++ b/deps/npm/docs/content/commands/npm-link.md
@@ -138,8 +138,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
diff --git a/deps/npm/docs/content/commands/npm-ls.md b/deps/npm/docs/content/commands/npm-ls.md
index ae8e64ada95551..fba77fd01cffaf 100644
--- a/deps/npm/docs/content/commands/npm-ls.md
+++ b/deps/npm/docs/content/commands/npm-ls.md
@@ -23,7 +23,7 @@ Note that nested packages will *also* show the paths to the specified packages.
For example, running `npm ls promzard` in npm's source tree will show:
```bash
-npm@11.17.0 /path/to/npm
+npm@11.18.0 /path/to/npm
└─┬ init-package-json@0.0.4
└── promzard@0.1.5
```
diff --git a/deps/npm/docs/content/commands/npm-outdated.md b/deps/npm/docs/content/commands/npm-outdated.md
index 1f368132c465a5..40f7e835a68a51 100644
--- a/deps/npm/docs/content/commands/npm-outdated.md
+++ b/deps/npm/docs/content/commands/npm-outdated.md
@@ -172,6 +172,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -194,6 +198,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm-query.md b/deps/npm/docs/content/commands/npm-query.md
index 83ea73188f7f10..4349e47ad23e06 100644
--- a/deps/npm/docs/content/commands/npm-query.md
+++ b/deps/npm/docs/content/commands/npm-query.md
@@ -284,6 +284,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -306,6 +310,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm-rebuild.md b/deps/npm/docs/content/commands/npm-rebuild.md
index 18b1d37c779956..c70307a2a7fe2d 100644
--- a/deps/npm/docs/content/commands/npm-rebuild.md
+++ b/deps/npm/docs/content/commands/npm-rebuild.md
@@ -136,6 +136,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
diff --git a/deps/npm/docs/content/commands/npm-update.md b/deps/npm/docs/content/commands/npm-update.md
index ee61e27dcda0e6..317f85f7d0dbd6 100644
--- a/deps/npm/docs/content/commands/npm-update.md
+++ b/deps/npm/docs/content/commands/npm-update.md
@@ -177,8 +177,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -338,6 +346,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `dangerously-allow-all-scripts`
@@ -385,6 +397,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -407,6 +423,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
diff --git a/deps/npm/docs/content/commands/npm.md b/deps/npm/docs/content/commands/npm.md
index d924d23c0ece0e..088553c6ffc486 100644
--- a/deps/npm/docs/content/commands/npm.md
+++ b/deps/npm/docs/content/commands/npm.md
@@ -14,7 +14,7 @@ Note: This command is unaware of workspaces.
### Version
-11.17.0
+11.18.0
### Description
diff --git a/deps/npm/docs/content/using-npm/config.md b/deps/npm/docs/content/using-npm/config.md
index 6bbb68fdf56528..5d951493e8a1c1 100644
--- a/deps/npm/docs/content/using-npm/config.md
+++ b/deps/npm/docs/content/using-npm/config.md
@@ -349,6 +349,10 @@ sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with `min-release-age`, when this cutoff blocks a fix that `npm audit
+fix` would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -1029,8 +1033,16 @@ Sets the strategy for installing packages in node_modules. hoisted
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+
+We recommend that package authors use `--install-strategy=linked` during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+`import` of a package that was never added to `package.json` can fail
+instead of resolving by accident and shipping broken. See [Catching
+undeclared ("phantom")
+dependencies](/using-npm/developers#catching-undeclared-phantom-dependencies).
@@ -1224,6 +1236,12 @@ your `.npmrc` is preserved when npm internally spawns a sub-process with
apply, `before` wins within a single source and across sources the standard
precedence rules apply.
+When this window stops `npm audit fix` from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+`min-release-age-exclude`, or relax `min-release-age` or `before`.
+
Packages whose names match `min-release-age-exclude` are exempt from this
filter.
@@ -1620,7 +1638,14 @@ registry (https://registry.npmjs.org) to the configured registry. If set to
"never", then use the registry value. If set to "always", then replace the
registry host with the configured host every time.
-You may also specify a bare hostname (e.g., "registry.npmjs.org").
+You may also specify a bare hostname (e.g., "registry.npmjs.org") to only
+replace URLs coming from that host.
+
+You may also specify a full URL including a path (e.g.,
+"https://old-registry.example.com/npm/path"). In that case, resolved URLs
+whose host and path begin with that prefix will have the entire prefix
+replaced with the configured registry URL (host and path), without
+duplicating path segments.
@@ -1877,6 +1902,10 @@ silently skipped; this setting only affects unreviewed entries.
`--ignore-scripts` and `--dangerously-allow-all-scripts` both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching `os`, `cpu`, or `libc`) are not flagged, because
+their install scripts never run.
+
#### `strict-peer-deps`
diff --git a/deps/npm/docs/content/using-npm/developers.md b/deps/npm/docs/content/using-npm/developers.md
index de0cb848c59ff4..b1fff0e3894f30 100644
--- a/deps/npm/docs/content/using-npm/developers.md
+++ b/deps/npm/docs/content/using-npm/developers.md
@@ -171,6 +171,24 @@ to install it locally into the node_modules folder in that other place.
Then go into the node-repl, and try using require("my-thing") to bring in your module's main module.
+#### Catching undeclared ("phantom") dependencies
+
+Under the default hoisted `node_modules` layout, your package can `import` a dependency it never declared and still resolve it.
+A transitive dependency hoisted alongside it, or your workspace root's `node_modules`, happens to satisfy the `import`.
+That undeclared ("phantom") dependency passes your own build silently, then fails for anyone who installs your package on its own.
+
+We recommend developing your package under [`install-strategy=linked`](/using-npm/config#install-strategy).
+The isolated layout only exposes a package's *declared* dependencies, so an `import` of an undeclared package fails for you during development instead of resolving by accident, shipping broken, and failing for your users:
+
+```bash
+npm install --install-strategy=linked
+npm test
+```
+
+> **Note:** This doesn't catch every case.
+> A dependency that's still satisfied at your build by a `devDependency` or by your workspace root's `node_modules` can resolve fine for you and still be missing for whoever installs your package.
+> So treat it as one check, not a guarantee, alongside auditing the dependencies your published package actually uses.
+
### Create a User Account
Create a user with the adduser command.
diff --git a/deps/npm/docs/output/commands/npm-access.html b/deps/npm/docs/output/commands/npm-access.html
index fd3ccdbdf886aa..13475a5d16f425 100644
--- a/deps/npm/docs/output/commands/npm-access.html
+++ b/deps/npm/docs/output/commands/npm-access.html
@@ -186,9 +186,9 @@
-
+
npm-access
- @11.17.0
+ @11.18.0
Set access level on published packages
diff --git a/deps/npm/docs/output/commands/npm-adduser.html b/deps/npm/docs/output/commands/npm-adduser.html
index 56679269f19860..e43659736fec33 100644
--- a/deps/npm/docs/output/commands/npm-adduser.html
+++ b/deps/npm/docs/output/commands/npm-adduser.html
@@ -186,9 +186,9 @@
-
+
npm-adduser
- @11.17.0
+ @11.18.0
Add a registry user account
diff --git a/deps/npm/docs/output/commands/npm-approve-scripts.html b/deps/npm/docs/output/commands/npm-approve-scripts.html
index 8d13f05fb30d4b..c3bc0172b5a4db 100644
--- a/deps/npm/docs/output/commands/npm-approve-scripts.html
+++ b/deps/npm/docs/output/commands/npm-approve-scripts.html
@@ -186,9 +186,9 @@
-
+
npm-approve-scripts
- @11.17.0
+ @11.18.0
Approve install scripts for specific dependencies
@@ -238,6 +238,13 @@ Description
the command cannot infer. Existing false entries always win;
approve-scripts will not silently re-allow a package you previously
denied.
+If a registry dependency has no resolved URL in your package-lock.json
+(for example, an older lockfile or one written with
+omit-lockfile-registry-resolved), npm cannot verify a trusted version for
+it and cannot pin it: a pkg@1.2.3 entry never matches, so the package
+keeps appearing under --allow-scripts-pending. approve-scripts approves
+these by name (pkg: true) and warns when it does. To restore pinning,
+refresh the lockfile with npm install.
Examples
# Approve all currently-installed install scripts after reviewing them
npm approve-scripts --all
diff --git a/deps/npm/docs/output/commands/npm-audit.html b/deps/npm/docs/output/commands/npm-audit.html
index e250ae85e00907..61042b6fbb07c0 100644
--- a/deps/npm/docs/output/commands/npm-audit.html
+++ b/deps/npm/docs/output/commands/npm-audit.html
@@ -186,9 +186,9 @@
-
+
npm-audit
- @11.17.0
+ @11.18.0
Run a security audit
diff --git a/deps/npm/docs/output/commands/npm-bugs.html b/deps/npm/docs/output/commands/npm-bugs.html
index 9d7cabb2eb07a9..660f011d1a0d9f 100644
--- a/deps/npm/docs/output/commands/npm-bugs.html
+++ b/deps/npm/docs/output/commands/npm-bugs.html
@@ -186,9 +186,9 @@
-
+
npm-bugs
- @11.17.0
+ @11.18.0
Report bugs for a package in a web browser
diff --git a/deps/npm/docs/output/commands/npm-cache.html b/deps/npm/docs/output/commands/npm-cache.html
index 49b091085717d2..553a38d06f42ab 100644
--- a/deps/npm/docs/output/commands/npm-cache.html
+++ b/deps/npm/docs/output/commands/npm-cache.html
@@ -186,9 +186,9 @@
-
+
npm-cache
- @11.17.0
+ @11.18.0
Manipulates packages cache
diff --git a/deps/npm/docs/output/commands/npm-ci.html b/deps/npm/docs/output/commands/npm-ci.html
index f01388a8be4ff3..3e61cba31beaa8 100644
--- a/deps/npm/docs/output/commands/npm-ci.html
+++ b/deps/npm/docs/output/commands/npm-ci.html
@@ -186,9 +186,9 @@
-
+
npm-ci
- @11.17.0
+ @11.18.0
Clean install a project
@@ -251,8 +251,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
@@ -419,6 +426,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
diff --git a/deps/npm/docs/output/commands/npm-completion.html b/deps/npm/docs/output/commands/npm-completion.html
index e0769eb5665c5a..d24da48ebb03ff 100644
--- a/deps/npm/docs/output/commands/npm-completion.html
+++ b/deps/npm/docs/output/commands/npm-completion.html
@@ -186,9 +186,9 @@
-
+
npm-completion
- @11.17.0
+ @11.18.0
Tab Completion for npm
diff --git a/deps/npm/docs/output/commands/npm-config.html b/deps/npm/docs/output/commands/npm-config.html
index 83b7a3b0eeab8c..a3c0dab2dee799 100644
--- a/deps/npm/docs/output/commands/npm-config.html
+++ b/deps/npm/docs/output/commands/npm-config.html
@@ -186,9 +186,9 @@
-
+
npm-config
- @11.17.0
+ @11.18.0
Manage the npm configuration files
diff --git a/deps/npm/docs/output/commands/npm-dedupe.html b/deps/npm/docs/output/commands/npm-dedupe.html
index 26065b3862e16c..fa8d4b7084379d 100644
--- a/deps/npm/docs/output/commands/npm-dedupe.html
+++ b/deps/npm/docs/output/commands/npm-dedupe.html
@@ -186,9 +186,9 @@
-
+
npm-dedupe
- @11.17.0
+ @11.18.0
Reduce duplication in the package tree
@@ -245,8 +245,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
diff --git a/deps/npm/docs/output/commands/npm-deny-scripts.html b/deps/npm/docs/output/commands/npm-deny-scripts.html
index 957193be87c446..4f7653c40173a6 100644
--- a/deps/npm/docs/output/commands/npm-deny-scripts.html
+++ b/deps/npm/docs/output/commands/npm-deny-scripts.html
@@ -186,9 +186,9 @@
-
+
npm-deny-scripts
- @11.17.0
+ @11.18.0
Deny install scripts for specific dependencies
diff --git a/deps/npm/docs/output/commands/npm-deprecate.html b/deps/npm/docs/output/commands/npm-deprecate.html
index b58d5388a985b9..30c4c912238480 100644
--- a/deps/npm/docs/output/commands/npm-deprecate.html
+++ b/deps/npm/docs/output/commands/npm-deprecate.html
@@ -186,9 +186,9 @@
-
+
npm-deprecate
- @11.17.0
+ @11.18.0
Deprecate a version of a package
diff --git a/deps/npm/docs/output/commands/npm-diff.html b/deps/npm/docs/output/commands/npm-diff.html
index a092efb1faf40c..6cf770360c1076 100644
--- a/deps/npm/docs/output/commands/npm-diff.html
+++ b/deps/npm/docs/output/commands/npm-diff.html
@@ -186,9 +186,9 @@
-
+
npm-diff
- @11.17.0
+ @11.18.0
The registry diff command
diff --git a/deps/npm/docs/output/commands/npm-dist-tag.html b/deps/npm/docs/output/commands/npm-dist-tag.html
index 2fc358fef37ca3..59985e4a43f316 100644
--- a/deps/npm/docs/output/commands/npm-dist-tag.html
+++ b/deps/npm/docs/output/commands/npm-dist-tag.html
@@ -186,9 +186,9 @@
-
+
npm-dist-tag
- @11.17.0
+ @11.18.0
Modify package distribution tags
diff --git a/deps/npm/docs/output/commands/npm-docs.html b/deps/npm/docs/output/commands/npm-docs.html
index 2eaade58d4d9ae..884f20d70d2f93 100644
--- a/deps/npm/docs/output/commands/npm-docs.html
+++ b/deps/npm/docs/output/commands/npm-docs.html
@@ -186,9 +186,9 @@
-
+
npm-docs
- @11.17.0
+ @11.18.0
Open documentation for a package in a web browser
diff --git a/deps/npm/docs/output/commands/npm-doctor.html b/deps/npm/docs/output/commands/npm-doctor.html
index e7a3771d5d654e..13bb25228dd28b 100644
--- a/deps/npm/docs/output/commands/npm-doctor.html
+++ b/deps/npm/docs/output/commands/npm-doctor.html
@@ -186,9 +186,9 @@
-
+
npm-doctor
- @11.17.0
+ @11.18.0
Check the health of your npm environment
diff --git a/deps/npm/docs/output/commands/npm-edit.html b/deps/npm/docs/output/commands/npm-edit.html
index 6f1ee44d54b0fe..6d1215ebfe1038 100644
--- a/deps/npm/docs/output/commands/npm-edit.html
+++ b/deps/npm/docs/output/commands/npm-edit.html
@@ -186,9 +186,9 @@
-
+
npm-edit
- @11.17.0
+ @11.18.0
Edit an installed package
diff --git a/deps/npm/docs/output/commands/npm-exec.html b/deps/npm/docs/output/commands/npm-exec.html
index d64cf7d587da27..3f57293a17fc56 100644
--- a/deps/npm/docs/output/commands/npm-exec.html
+++ b/deps/npm/docs/output/commands/npm-exec.html
@@ -186,9 +186,9 @@
-
+
npm-exec
- @11.17.0
+ @11.18.0
Run a command from a local or remote npm package
@@ -336,6 +336,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
diff --git a/deps/npm/docs/output/commands/npm-explain.html b/deps/npm/docs/output/commands/npm-explain.html
index 4c61e4dc5fbf14..5d5f6cea5ce113 100644
--- a/deps/npm/docs/output/commands/npm-explain.html
+++ b/deps/npm/docs/output/commands/npm-explain.html
@@ -186,9 +186,9 @@
-
+
npm-explain
- @11.17.0
+ @11.18.0
Explain installed packages
diff --git a/deps/npm/docs/output/commands/npm-explore.html b/deps/npm/docs/output/commands/npm-explore.html
index 68f61b31ed7d1b..d1ecb4e81886a8 100644
--- a/deps/npm/docs/output/commands/npm-explore.html
+++ b/deps/npm/docs/output/commands/npm-explore.html
@@ -186,9 +186,9 @@
-
+
npm-explore
- @11.17.0
+ @11.18.0
Browse an installed package
diff --git a/deps/npm/docs/output/commands/npm-find-dupes.html b/deps/npm/docs/output/commands/npm-find-dupes.html
index c6dea14d12f997..28a9066398760c 100644
--- a/deps/npm/docs/output/commands/npm-find-dupes.html
+++ b/deps/npm/docs/output/commands/npm-find-dupes.html
@@ -186,9 +186,9 @@
-
+
npm-find-dupes
- @11.17.0
+ @11.18.0
Find duplication in the package tree
@@ -213,8 +213,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
diff --git a/deps/npm/docs/output/commands/npm-fund.html b/deps/npm/docs/output/commands/npm-fund.html
index 0b7602251f020f..26761cc90196c8 100644
--- a/deps/npm/docs/output/commands/npm-fund.html
+++ b/deps/npm/docs/output/commands/npm-fund.html
@@ -186,9 +186,9 @@
-
+
npm-fund
- @11.17.0
+ @11.18.0
Retrieve funding information
diff --git a/deps/npm/docs/output/commands/npm-get.html b/deps/npm/docs/output/commands/npm-get.html
index e81310c39b33c0..e8fe5dfc0373ea 100644
--- a/deps/npm/docs/output/commands/npm-get.html
+++ b/deps/npm/docs/output/commands/npm-get.html
@@ -186,9 +186,9 @@
-
+
npm-get
- @11.17.0
+ @11.18.0
Get a value from the npm configuration
diff --git a/deps/npm/docs/output/commands/npm-help-search.html b/deps/npm/docs/output/commands/npm-help-search.html
index b44eddadf701b0..24f4ddafaea589 100644
--- a/deps/npm/docs/output/commands/npm-help-search.html
+++ b/deps/npm/docs/output/commands/npm-help-search.html
@@ -186,9 +186,9 @@
-
+
npm-help-search
- @11.17.0
+ @11.18.0
Search npm help documentation
diff --git a/deps/npm/docs/output/commands/npm-help.html b/deps/npm/docs/output/commands/npm-help.html
index d22edb7c826b09..4fb2ef4be76c30 100644
--- a/deps/npm/docs/output/commands/npm-help.html
+++ b/deps/npm/docs/output/commands/npm-help.html
@@ -186,9 +186,9 @@
-
+
npm-help
- @11.17.0
+ @11.18.0
Get help on npm
diff --git a/deps/npm/docs/output/commands/npm-init.html b/deps/npm/docs/output/commands/npm-init.html
index 836969ed135304..deeaf25a2348a0 100644
--- a/deps/npm/docs/output/commands/npm-init.html
+++ b/deps/npm/docs/output/commands/npm-init.html
@@ -186,9 +186,9 @@
-
+
npm-init
- @11.17.0
+ @11.18.0
Create a package.json file
diff --git a/deps/npm/docs/output/commands/npm-install-ci-test.html b/deps/npm/docs/output/commands/npm-install-ci-test.html
index 6f633c2c18a02d..2227da94885950 100644
--- a/deps/npm/docs/output/commands/npm-install-ci-test.html
+++ b/deps/npm/docs/output/commands/npm-install-ci-test.html
@@ -186,9 +186,9 @@
-
+
npm-install-ci-test
- @11.17.0
+ @11.18.0
Install a project with a clean slate and run tests
@@ -215,8 +215,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
@@ -383,6 +390,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
diff --git a/deps/npm/docs/output/commands/npm-install-scripts.html b/deps/npm/docs/output/commands/npm-install-scripts.html
new file mode 100644
index 00000000000000..1b036581f6a36e
--- /dev/null
+++ b/deps/npm/docs/output/commands/npm-install-scripts.html
@@ -0,0 +1,341 @@
+
+
+npm-install-scripts
+
+
+
+
+
+
+
+
+
+
+
+npm command-line interface
+
+
+
+
+
+
+
+ npm-install-scripts
+ @11.18.0
+
+Manage install-script approvals for dependencies
+
+
+
+
+Synopsis
+
npm install-scripts approve <pkg> [<pkg> ...]
+npm install-scripts approve --all
+npm install-scripts deny <pkg> [<pkg> ...]
+npm install-scripts deny --all
+npm install-scripts ls
+npm install-scripts prune
+
+
Note: This command is unaware of workspaces.
+
Description
+
Manages the allowScripts field in your project's package.json, which
+records which of your dependencies are permitted to run install scripts
+(preinstall, install, postinstall, and prepare for non-registry
+sources). This is the recommended way to maintain that field.
+
Dependency install scripts are blocked by default. Install commands
+silently skip lifecycle scripts for any dependency that does not have a
+matching entry in allowScripts, and end with a list of the packages
+whose scripts were skipped so you can review them here.
+
This command only works inside a project that has a package.json. Running
+it with --global (-g) fails with an EGLOBAL error, since global
+installs (npm install -g) and one-off executions (npm exec / npx) have
+no project package.json to write to. To allow install scripts in those
+contexts, use the --allow-scripts flag at install time (for example
+npm install -g --allow-scripts=canvas,sharp) or persist the setting with
+npm config set allow-scripts=canvas,sharp --location=user.
+
There are four subcommands:
+
npm install-scripts approve <pkg> [<pkg> ...]
+npm install-scripts approve --all
+npm install-scripts deny <pkg> [<pkg> ...]
+npm install-scripts deny --all
+npm install-scripts ls
+npm install-scripts prune
+
+
approve allows install scripts for the named packages. <pkg> matches
+every installed version of that package. By default it writes pinned entries
+(pkg@1.2.3), which keep their approval narrowed to the specific version you
+reviewed. Pass --no-allow-scripts-pin to write name-only entries that allow
+any future version. --all approves every package with unreviewed install
+scripts in one go.
+
deny records an explicit denial for the named packages (a name-only false
+entry), which survives npm install-scripts approve --all and excludes the
+package from any future blanket approval. --all denies every package with
+unreviewed install scripts.
+
ls is read-only: it lists every package whose install scripts are not yet
+covered by allowScripts, without modifying package.json.
+
prune removes allowScripts entries that no longer match an installed
+package with an install script, either because the package is no longer
+installed (a transitive dependency changed, or a pinned pkg@1.2.3 was
+upgraded) or because it no longer has an install script. Both approvals
+(true) and denials (false) are removed. It edits only the allowScripts
+field in package.json, never .npmrc or --allow-scripts. Pass --dry-run
+to preview without writing. Unparseable keys are left alone.
+
approve honours the asymmetric pin rule: if you re-approve a package whose
+installed version has changed, the existing pin is rewritten to track the new
+installed version. Multi-version statements (pkg@1 || 2) are left alone,
+since they likely capture intent that the command cannot infer. Existing
+false entries always win; approve will not silently re-allow a package you
+previously denied.
+
The standalone commands npm approve-scripts
+and npm deny-scripts are aliases for
+npm install-scripts approve and npm install-scripts deny.
+
Examples
+
# Approve all currently-installed install scripts after reviewing them
+npm install-scripts approve --all
+
+# Approve specific packages, pinned to their installed version
+npm install-scripts approve canvas sharp
+
+# Deny a package so it stays blocked
+npm install-scripts deny telemetry-pkg
+
+# Preview which packages still need review
+npm install-scripts ls
+
+# Preview stale allowScripts entries, then remove them
+npm install-scripts prune --dry-run
+npm install-scripts prune
+
+
Configuration
+
all
+
+Default: false
+Type: Boolean
+
+
Show or act on all packages, not just the ones your project directly depends
+on. For npm outdated and npm ls this lists every outdated or installed
+package. For npm approve-scripts and npm deny-scripts it selects every
+package with pending install scripts.
+
allow-scripts-pin
+
+Default: true
+Type: Boolean
+
+
Write pinned (pkg@version) entries when approving install scripts. Set to
+false to write name-only entries that allow any version. Has no effect on
+npm deny-scripts, which always writes name-only entries regardless of this
+setting.
+
dry-run
+
+Default: false
+Type: Boolean
+
+
Indicates that you don't want npm to make any changes and that it should
+only report what it would have done. This can be passed into any of the
+commands that modify your local installation, eg, install, update,
+dedupe, uninstall, as well as pack and publish.
+
Note: This is NOT honored by other network related commands, eg dist-tags,
+owner, etc.
+
json
+
+Default: false
+Type: Boolean
+
+
Whether or not to output JSON data, rather than the normal output.
+
+In npm pkg set it enables parsing set values with JSON.parse() before
+saving them to your package.json.
+
+
Not supported by all npm commands.
+
See Also
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deps/npm/docs/output/commands/npm-install-test.html b/deps/npm/docs/output/commands/npm-install-test.html
index bd12094e8ca1c6..94231076345ad4 100644
--- a/deps/npm/docs/output/commands/npm-install-test.html
+++ b/deps/npm/docs/output/commands/npm-install-test.html
@@ -186,9 +186,9 @@
-
+
npm-install-test
- @11.17.0
+ @11.18.0
Install package(s) and run tests
@@ -246,8 +246,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
@@ -439,6 +446,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
@@ -475,6 +485,8 @@ before
sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with min-release-age, when this cutoff blocks a fix that npm audit fix would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
Packages whose names match min-release-age-exclude are exempt from this
filter.
min-release-age
@@ -492,6 +504,11 @@ min-release-age
--before while preparing a git: or github: dependency); when both
apply, before wins within a single source and across sources the standard
precedence rules apply.
+When this window stops npm audit fix from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+min-release-age-exclude, or relax min-release-age or before.
Packages whose names match min-release-age-exclude are exempt from this
filter.
This value is not exported to the environment for child processes.
diff --git a/deps/npm/docs/output/commands/npm-install.html b/deps/npm/docs/output/commands/npm-install.html
index 767a65f28bbd2c..fd6fe12eb8cd6d 100644
--- a/deps/npm/docs/output/commands/npm-install.html
+++ b/deps/npm/docs/output/commands/npm-install.html
@@ -186,9 +186,9 @@
-
+
npm-install
- @11.17.0
+ @11.18.0
Install a package
@@ -521,8 +521,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
@@ -714,6 +721,9 @@ strict-allow-scripts
silently skipped; this setting only affects unreviewed entries.
--ignore-scripts and --dangerously-allow-all-scripts both override this
setting.
+Optional dependencies that cannot be installed on the current platform or
+engine (a non-matching os, cpu, or libc) are not flagged, because
+their install scripts never run.
dangerously-allow-all-scripts
Default: false
@@ -750,6 +760,8 @@ before
sources, the standard precedence applies (cli > env > project > user >
global), so a higher-priority source can always relax or override a
lower-priority one.
+As with min-release-age, when this cutoff blocks a fix that npm audit fix would install, npm keeps the vulnerable version, warns, and exits with
+a non-zero code.
Packages whose names match min-release-age-exclude are exempt from this
filter.
min-release-age
@@ -767,6 +779,11 @@ min-release-age
--before while preparing a git: or github: dependency); when both
apply, before wins within a single source and across sources the standard
precedence rules apply.
+When this window stops npm audit fix from installing a patched version
+(because the fix was published too recently), npm keeps the package at its
+vulnerable version, warns that the fix was blocked, and exits with a
+non-zero code. To install the fix, add the package to
+min-release-age-exclude, or relax min-release-age or before.
Packages whose names match min-release-age-exclude are exempt from this
filter.
This value is not exported to the environment for child processes.
diff --git a/deps/npm/docs/output/commands/npm-link.html b/deps/npm/docs/output/commands/npm-link.html
index 42fd82304ecf17..e4c9f092f37c09 100644
--- a/deps/npm/docs/output/commands/npm-link.html
+++ b/deps/npm/docs/output/commands/npm-link.html
@@ -186,9 +186,9 @@
-
+
npm-link
- @11.17.0
+ @11.18.0
Symlink a package folder
@@ -288,8 +288,15 @@ install-strategy
(default): Install non-duplicated in top-level, and duplicated as necessary
within directory structure. nested: (formerly --legacy-bundling) install in
place, no hoisting. shallow (formerly --global-style) only install direct
-deps at top-level. linked: (experimental) install in node_modules/.store,
-link in place, unhoisted.
+deps at top-level. linked: install in node_modules/.store, link in place,
+unhoisted.
+We recommend that package authors use --install-strategy=linked during
+development to catch undeclared ("phantom") dependencies before publishing:
+the isolated layout only exposes a package's declared dependencies, so an
+import of a package that was never added to package.json can fail
+instead of resolving by accident and shipping broken. See Catching
+undeclared ("phantom")
+dependencies .
legacy-bundling
Default: false
diff --git a/deps/npm/docs/output/commands/npm-ll.html b/deps/npm/docs/output/commands/npm-ll.html
index e288cc8a65d634..a7e3061ddf98bf 100644
--- a/deps/npm/docs/output/commands/npm-ll.html
+++ b/deps/npm/docs/output/commands/npm-ll.html
@@ -186,9 +186,9 @@
-
+
npm-ll
- @11.17.0
+ @11.18.0
List installed packages
diff --git a/deps/npm/docs/output/commands/npm-login.html b/deps/npm/docs/output/commands/npm-login.html
index 7ead867b8d396c..b08ac7b3357030 100644
--- a/deps/npm/docs/output/commands/npm-login.html
+++ b/deps/npm/docs/output/commands/npm-login.html
@@ -186,9 +186,9 @@
-
+
npm-login
- @11.17.0
+ @11.18.0
Login to a registry user account
diff --git a/deps/npm/docs/output/commands/npm-logout.html b/deps/npm/docs/output/commands/npm-logout.html
index 19ecf24f01efad..dfc1b256538f45 100644
--- a/deps/npm/docs/output/commands/npm-logout.html
+++ b/deps/npm/docs/output/commands/npm-logout.html
@@ -186,9 +186,9 @@
-
+
npm-logout
- @11.17.0
+ @11.18.0
Log out of the registry
diff --git a/deps/npm/docs/output/commands/npm-ls.html b/deps/npm/docs/output/commands/npm-ls.html
index 181541d978ea07..a3de66d0323c8e 100644
--- a/deps/npm/docs/output/commands/npm-ls.html
+++ b/deps/npm/docs/output/commands/npm-ls.html
@@ -186,9 +186,9 @@
-
+
npm-ls
- @11.17.0
+ @11.18.0
List installed packages
@@ -209,7 +209,7 @@ Description
Positional arguments are name@version-range identifiers, which will limit the results to only the paths to the packages named.
Note that nested packages will also show the paths to the specified packages.
For example, running npm ls promzard in npm's source tree will show:
-npm@11.17.0 /path/to/npm
+npm@11.18.0 /path/to/npm
└─┬ init-package-json@0.0.4
└── promzard@0.1.5
diff --git a/deps/npm/docs/output/commands/npm-org.html b/deps/npm/docs/output/commands/npm-org.html
index 361f44be6f3040..7c0526d750c62f 100644
--- a/deps/npm/docs/output/commands/npm-org.html
+++ b/deps/npm/docs/output/commands/npm-org.html
@@ -186,9 +186,9 @@