diff --git a/contrib/windows/codeql/Invoke-CodeQLZFSinAnalysis.ps1 b/contrib/windows/codeql/Invoke-CodeQLZFSinAnalysis.ps1 new file mode 100644 index 000000000000..e04c0500c25e --- /dev/null +++ b/contrib/windows/codeql/Invoke-CodeQLZFSinAnalysis.ps1 @@ -0,0 +1,187 @@ +# ***************************************************************************** +# Copyright (c) 2026 DataCore Software Corporation. All rights reserved. +# ***************************************************************************** + +<# + .SYNOPSIS + Runs CodeQL static analysis over the ZFSin kernel driver target only + (not the full CMake build) and reports the Must-Fix findings required + for the HLK "Static Tools Logo Test" (WHCP certification). + + .DESCRIPTION + 1. Builds a CodeQL database by tracing a CMake+Ninja build of ONLY the + `ZFSin` target (the driver .sys and everything statically linked + into it: splkern, zlibkern, icpkern, luakern, zfskern, zfskern_os, + zcommonkern, nvpairkern, unicodekern, zstdkern). This deliberately + excludes the user-mode tools (zfs.exe, zpool.exe, zfsinstaller.exe, + etc.) and user-mode libraries (libzfs, libnvpair, libzpool, ...) - + several of those share source files with the driver (e.g. + module/zfs/*.c is also compiled into libzpool) but under different + macros/headers, and mixing both into one database produces + confusing multi-context findings for the same source line. + 2. Analyzes the database with the WHCP `mustfix.qls` suite, writing + SARIF to the repo root. + 3. Prints a summary grouped by rule/API. + + .PREREQUISITES + - CodeQL CLI 2.20.1 unpacked to -CodeQLHome (WHCP matrix version). + - Query packs downloaded: microsoft/windows-drivers@1.8.0 and + microsoft/cpp-queries@0.0.4. + - VS2019 (vcvars64.bat) + WDK 10.0.19041 + OpenSSL-Win64 + the + prebuilt ISA-L static libs under lib\ISA-L\ - all + already required by this repo's normal CMake build. + + .EXAMPLE + .\Invoke-CodeQLZFSinAnalysis.ps1 +#> + +PARAM +( + [Parameter(HelpMessage = "Directory containing the CodeQL CLI (codeql.exe)")] + [string]$CodeQLHome = "C:\codeql-home\codeql", + + [Parameter(HelpMessage = "WHCP query suite to run")] + [ValidateSet("mustfix", "recommended", "mustrun")] + [string]$Suite = "mustfix", + + [Parameter(HelpMessage = "CMake build configuration")] + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Debug", + + [Parameter(HelpMessage = "Path to vcvars64.bat (VS2019)")] + [string]$VcVars64 = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvars64.bat", + + [Parameter(HelpMessage = "Directory for the CodeQL database")] + [string]$DatabasePath, + + [Parameter(HelpMessage = "Output SARIF path")] + [string]$SarifPath, + + [Parameter(HelpMessage = "Reuse an existing CodeQL database instead of rebuilding it")] + [switch]$ReuseDatabase +) + +$ErrorActionPreference = "Stop" + +# Repo root is two levels up from contrib\windows\codeql. +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..")).Path + +if (!$DatabasePath) { $DatabasePath = Join-Path $repoRoot "out\CodeQL\databases\ZFSin" } +if (!$SarifPath) { $SarifPath = Join-Path $repoRoot "ZFSin.codeql.sarif" } + +$buildDir = Join-Path $repoRoot "out\build\codeql-zfsin" + +function Invoke-Tool([string]$Exe, [string[]]$ToolArgs) +{ + $prevEap = $script:ErrorActionPreference + $script:ErrorActionPreference = "Continue" + try + { + & $Exe @ToolArgs 2>&1 | ForEach-Object { Write-Host $_.ToString() } + return $LASTEXITCODE + } + finally + { + $script:ErrorActionPreference = $prevEap + } +} + +$codeqlExe = Join-Path $CodeQLHome "codeql.exe" +if (!(Test-Path $codeqlExe)) +{ + $nested = Join-Path $CodeQLHome "codeql\codeql.exe" + if (Test-Path $nested) { $codeqlExe = $nested } +} +if (!(Test-Path $codeqlExe)) +{ + throw "codeql.exe not found under '$CodeQLHome'." +} + +if (!(Test-Path $VcVars64)) +{ + throw "vcvars64.bat not found at '$VcVars64'. Pass -VcVars64 explicitly." +} + +$suiteSpec = "microsoft/windows-drivers:windows-driver-suites\$Suite.qls" + +Write-Host "Repo root : $repoRoot" +Write-Host "CodeQL : $codeqlExe" +Write-Host "Query suite : $suiteSpec" +Write-Host "Build dir : $buildDir" +Write-Host "Database : $DatabasePath" +Write-Host "SARIF output : $SarifPath" +Write-Host "" + +# Fail early if the WHCP query packs are missing. +$prevEap = $ErrorActionPreference +$ErrorActionPreference = "Continue" +$qlpacks = (& $codeqlExe resolve packs 2>&1 | ForEach-Object { $_.ToString() }) -join "`n" +$ErrorActionPreference = $prevEap +if ($qlpacks -notmatch "microsoft/windows-drivers") +{ + throw "Query pack microsoft/windows-drivers not found. Run: codeql pack download microsoft/windows-drivers@1.8.0 (and microsoft/cpp-queries@0.0.4)." +} + +New-Item -ItemType Directory -Force (Split-Path $DatabasePath -Parent) > $null + +if ($ReuseDatabase -and (Test-Path (Join-Path $DatabasePath "codeql-database.yml"))) +{ + Write-Host "Reusing existing database $DatabasePath" +} +else +{ + # Build script: vcvars64 -> configure (only if needed) -> build ONLY the + # ZFSin target (not the default `all` target, which would also build the + # unrelated user-mode tools/libraries). + $buildScript = Join-Path $env:TEMP "build-zfsin-codeql.cmd" + @" +@echo off +setlocal +call "$VcVars64" +if errorlevel 1 exit /b 1 +set "CMAKE=%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" +set "NINJA=%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Professional\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe" +cd /d "$repoRoot" +if not exist "$buildDir\CMakeCache.txt" ( + "%CMAKE%" -S . -B "$buildDir" -G Ninja -DCMAKE_BUILD_TYPE=$Configuration -DCMAKE_MAKE_PROGRAM="%NINJA%" + if errorlevel 1 exit /b 1 +) +"%CMAKE%" --build "$buildDir" --target ZFSin --clean-first +if errorlevel 1 exit /b 1 +exit /b 0 +"@ | Out-File -FilePath $buildScript -Encoding ASCII + + if (Test-Path $DatabasePath) { Remove-Item -Recurse -Force $DatabasePath } + $code = Invoke-Tool $codeqlExe @("database", "create", $DatabasePath, "--language=cpp", + "--source-root=$repoRoot", "--command=$buildScript", "--overwrite") + if ($code -ne 0) { throw "codeql database create failed (exit $code)" } +} + +$code = Invoke-Tool $codeqlExe @("database", "analyze", $DatabasePath, $suiteSpec, + "--format=sarifv2.1.0", "--output=$SarifPath", "--rerun") +if ($code -ne 0) { throw "codeql database analyze failed (exit $code)" } + +$sarif = Get-Content $SarifPath -Raw | ConvertFrom-Json +$findings = @() +foreach ($run in $sarif.runs) { if ($run.results) { $findings += $run.results } } + +Write-Host "" +Write-Host "================ ZFSin ($Suite) ================" -ForegroundColor Cyan +if ($findings.Count -eq 0) +{ + Write-Host "clean - 0 findings" -ForegroundColor Green +} +else +{ + Write-Host "$($findings.Count) finding(s):" -ForegroundColor Yellow + $findings | Group-Object ruleId | Sort-Object Count -Descending | ForEach-Object { + Write-Host (" {0,4} x {1}" -f $_.Count, $_.Name) -ForegroundColor Yellow + } + if ($Suite -eq "mustfix") + { + Write-Host "" + Write-Host "FAILS certification until fixed" -ForegroundColor Red + } +} + +exit $findings.Count diff --git a/include/os/windows/spl/sys/types.h b/include/os/windows/spl/sys/types.h index db7b1fb545e8..751117176aa7 100644 --- a/include/os/windows/spl/sys/types.h +++ b/include/os/windows/spl/sys/types.h @@ -146,54 +146,12 @@ spl_snprintf(char *buf, size_t size, const char *fmt, ...) */ /* - * Kernel-mode strncpy() does not NUL-terminate if strlen(src) >= n, - * and zero-fills the whole remainder of the buffer if strlen(src) < n - * - neither behavior is depended on by any of this codebase's callers. - * strlcpy() is the semantically-closest safe replacement (always - * terminates, never overflows) but has no kernel-linkable - * implementation here, so provide one - mirrors lib/libspl/strlcpy.c's - * existing user-mode algorithm exactly. Callers pass n == the size of - * the destination buffer (or the intended-substring-length + 1), + * strncpy() and strcat() are replaced tree-wide by strlcpy() and + * strlcat(), which are declared in sys/sunddi.h and implemented for + * kernel mode in module/os/windows/spl/spl-ddi.c. Callers pass n == the + * size of the destination buffer (or the intended-substring-length + 1), * unlike strncpy's n == copy-length - not a drop-in same-args swap. */ -static __inline size_t -spl_strlcpy(char *dst, const char *src, size_t dstsize) -{ - size_t srclen = strlen(src); - size_t copied = (srclen < dstsize) ? srclen : dstsize - 1; - - if (dstsize != 0) { - memcpy(dst, src, copied); - dst[copied] = '\0'; - } - return (srclen); -} - -/* - * strcat() has no size parameter at all - unbounded by construction. - * strlcat() is the closest safe replacement (always terminates, never - * overflows, return value is the total length it tried to create) but - * - same as strlcpy() - has no kernel-linkable implementation here. - * Mirrors lib/libspl/strlcat.c's existing user-mode algorithm exactly. - */ -static __inline size_t -spl_strlcat(char *dst, const char *src, size_t dstsize) -{ - char *df = dst; - size_t left = dstsize; - size_t l1, l2 = strlen(src), copied; - - while (left-- != 0 && *df != '\0') - df++; - l1 = df - dst; - if (dstsize == l1) - return (l1 + l2); - - copied = (l1 + l2 >= dstsize) ? dstsize - l1 - 1 : l2; - memcpy(dst + l1, src, copied); - dst[l1 + copied] = '\0'; - return (l1 + l2); -} #ifndef ULLONG_MAX #define ULLONG_MAX (~0ULL) diff --git a/lib/libspl/include/os/windows/sys/types.h b/lib/libspl/include/os/windows/sys/types.h index fb0e153febff..815cb60cedd5 100644 --- a/lib/libspl/include/os/windows/sys/types.h +++ b/lib/libspl/include/os/windows/sys/types.h @@ -232,27 +232,15 @@ typedef uint64_t zoff_t; #include /* - * Mirrors include/os/windows/spl/sys/types.h's kernel-mode shims of the + * Mirrors include/os/windows/spl/sys/types.h's kernel-mode shim of the * same name. Several shared module/zfs, module/icp, and module/lua * source files (built both into the ZFSin kernel driver and into - * user-mode libzpool/libicp/zlib here) call these directly by name, not - * through a portable macro. In user mode, real strlcpy/strlcat - * (lib/libspl) and real, C99-conformant UCRT vsnprintf are already - * available, so these are simple passthroughs - no downlevel- - * unavailability workaround is needed here, unlike the kernel version. + * user-mode libzpool/libicp/zlib here) call this directly by name, not + * through a portable macro. In user mode a real, C99-conformant UCRT + * vsnprintf is already available, so this is a simple passthrough - no + * downlevel-unavailability workaround is needed here, unlike the kernel + * version. */ -static __inline size_t -spl_strlcpy(char *dst, const char *src, size_t dstsize) -{ - return (strlcpy(dst, src, dstsize)); -} - -static __inline size_t -spl_strlcat(char *dst, const char *src, size_t dstsize) -{ - return (strlcat(dst, src, dstsize)); -} - static __inline int spl_vsnprintf(char *buf, size_t size, const char *fmt, va_list args) { diff --git a/lib/os/windows/zlib-1.2.3/gzio.c b/lib/os/windows/zlib-1.2.3/gzio.c index 5c7ed4aaf16b..36daa59082bf 100644 --- a/lib/os/windows/zlib-1.2.3/gzio.c +++ b/lib/os/windows/zlib-1.2.3/gzio.c @@ -132,7 +132,7 @@ gz_open( if (s->path == NULL) { return (destroy(s), (gzFile)Z_NULL); } - spl_strlcpy(s->path, path, strlen(path) + 1); /* do this early for debugging */ + strlcpy(s->path, path, strlen(path) + 1); /* do this early for debugging */ s->mode = '\0'; do { @@ -1094,9 +1094,9 @@ gzerror( s->msg = (char *)ALLOC(strlen(s->path) + strlen(m) + 3); if (s->msg == Z_NULL) return ((const char *)ERR_MSG(Z_MEM_ERROR)); - spl_strlcpy(s->msg, s->path, strlen(s->path) + strlen(m) + 3); - spl_strlcat(s->msg, ": ", strlen(s->path) + strlen(m) + 3); - spl_strlcat(s->msg, m, strlen(s->path) + strlen(m) + 3); + strlcpy(s->msg, s->path, strlen(s->path) + strlen(m) + 3); + strlcat(s->msg, ": ", strlen(s->path) + strlen(m) + 3); + strlcat(s->msg, m, strlen(s->path) + strlen(m) + 3); return ((const char *)s->msg); } diff --git a/module/icp/core/kcf_mech_tabs.c b/module/icp/core/kcf_mech_tabs.c index d9f185eea8b0..a4a5380925e3 100644 --- a/module/icp/core/kcf_mech_tabs.c +++ b/module/icp/core/kcf_mech_tabs.c @@ -177,72 +177,72 @@ kcf_init_mech_tabs(void) /* Then the pre-defined mechanism entries */ /* Two digests */ - (void) spl_strlcpy(kcf_digest_mechs_tab[0].me_name, SUN_CKM_MD5, + (void) strlcpy(kcf_digest_mechs_tab[0].me_name, SUN_CKM_MD5, CRYPTO_MAX_MECH_NAME); kcf_digest_mechs_tab[0].me_threshold = kcf_md5_threshold; - (void) spl_strlcpy(kcf_digest_mechs_tab[1].me_name, SUN_CKM_SHA1, + (void) strlcpy(kcf_digest_mechs_tab[1].me_name, SUN_CKM_SHA1, CRYPTO_MAX_MECH_NAME); kcf_digest_mechs_tab[1].me_threshold = kcf_sha1_threshold; /* The symmetric ciphers in various modes */ - (void) spl_strlcpy(kcf_cipher_mechs_tab[0].me_name, SUN_CKM_DES_CBC, + (void) strlcpy(kcf_cipher_mechs_tab[0].me_name, SUN_CKM_DES_CBC, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[0].me_threshold = kcf_des_threshold; - (void) spl_strlcpy(kcf_cipher_mechs_tab[1].me_name, SUN_CKM_DES3_CBC, + (void) strlcpy(kcf_cipher_mechs_tab[1].me_name, SUN_CKM_DES3_CBC, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[1].me_threshold = kcf_des3_threshold; - (void) spl_strlcpy(kcf_cipher_mechs_tab[2].me_name, SUN_CKM_DES_ECB, + (void) strlcpy(kcf_cipher_mechs_tab[2].me_name, SUN_CKM_DES_ECB, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[2].me_threshold = kcf_des_threshold; - (void) spl_strlcpy(kcf_cipher_mechs_tab[3].me_name, SUN_CKM_DES3_ECB, + (void) strlcpy(kcf_cipher_mechs_tab[3].me_name, SUN_CKM_DES3_ECB, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[3].me_threshold = kcf_des3_threshold; - (void) spl_strlcpy(kcf_cipher_mechs_tab[4].me_name, SUN_CKM_BLOWFISH_CBC, + (void) strlcpy(kcf_cipher_mechs_tab[4].me_name, SUN_CKM_BLOWFISH_CBC, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[4].me_threshold = kcf_bf_threshold; - (void) spl_strlcpy(kcf_cipher_mechs_tab[5].me_name, SUN_CKM_BLOWFISH_ECB, + (void) strlcpy(kcf_cipher_mechs_tab[5].me_name, SUN_CKM_BLOWFISH_ECB, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[5].me_threshold = kcf_bf_threshold; - (void) spl_strlcpy(kcf_cipher_mechs_tab[6].me_name, SUN_CKM_AES_CBC, + (void) strlcpy(kcf_cipher_mechs_tab[6].me_name, SUN_CKM_AES_CBC, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[6].me_threshold = kcf_aes_threshold; - (void) spl_strlcpy(kcf_cipher_mechs_tab[7].me_name, SUN_CKM_AES_ECB, + (void) strlcpy(kcf_cipher_mechs_tab[7].me_name, SUN_CKM_AES_ECB, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[7].me_threshold = kcf_aes_threshold; - (void) spl_strlcpy(kcf_cipher_mechs_tab[8].me_name, SUN_CKM_RC4, + (void) strlcpy(kcf_cipher_mechs_tab[8].me_name, SUN_CKM_RC4, CRYPTO_MAX_MECH_NAME); kcf_cipher_mechs_tab[8].me_threshold = kcf_rc4_threshold; /* 4 HMACs */ - (void) spl_strlcpy(kcf_mac_mechs_tab[0].me_name, SUN_CKM_MD5_HMAC, + (void) strlcpy(kcf_mac_mechs_tab[0].me_name, SUN_CKM_MD5_HMAC, CRYPTO_MAX_MECH_NAME); kcf_mac_mechs_tab[0].me_threshold = kcf_md5_threshold; - (void) spl_strlcpy(kcf_mac_mechs_tab[1].me_name, SUN_CKM_MD5_HMAC_GENERAL, + (void) strlcpy(kcf_mac_mechs_tab[1].me_name, SUN_CKM_MD5_HMAC_GENERAL, CRYPTO_MAX_MECH_NAME); kcf_mac_mechs_tab[1].me_threshold = kcf_md5_threshold; - (void) spl_strlcpy(kcf_mac_mechs_tab[2].me_name, SUN_CKM_SHA1_HMAC, + (void) strlcpy(kcf_mac_mechs_tab[2].me_name, SUN_CKM_SHA1_HMAC, CRYPTO_MAX_MECH_NAME); kcf_mac_mechs_tab[2].me_threshold = kcf_sha1_threshold; - (void) spl_strlcpy(kcf_mac_mechs_tab[3].me_name, SUN_CKM_SHA1_HMAC_GENERAL, + (void) strlcpy(kcf_mac_mechs_tab[3].me_name, SUN_CKM_SHA1_HMAC_GENERAL, CRYPTO_MAX_MECH_NAME); kcf_mac_mechs_tab[3].me_threshold = kcf_sha1_threshold; /* 1 random number generation pseudo mechanism */ - (void) spl_strlcpy(kcf_misc_mechs_tab[0].me_name, SUN_RANDOM, + (void) strlcpy(kcf_misc_mechs_tab[0].me_name, SUN_RANDOM, CRYPTO_MAX_MECH_NAME); kcf_mech_hash = mod_hash_create_strhash_nodtr("kcf mech2id hash", diff --git a/module/icp/spi/kcf_spi.c b/module/icp/spi/kcf_spi.c index edfb9765002c..96b9ea4130d5 100644 --- a/module/icp/spi/kcf_spi.c +++ b/module/icp/spi/kcf_spi.c @@ -606,7 +606,7 @@ init_prov_mechs(crypto_provider_info_t *info, kcf_provider_desc_t *desc) rand_mi = &desc->pd_mechanisms[mcount - 1]; bzero(rand_mi, sizeof (crypto_mech_info_t)); - (void) spl_strlcpy(rand_mi->cm_mech_name, SUN_RANDOM, + (void) strlcpy(rand_mi->cm_mech_name, SUN_RANDOM, CRYPTO_MAX_MECH_NAME); rand_mi->cm_func_group_mask = CRYPTO_FG_RANDOM; } else { diff --git a/module/os/windows/debug.c b/module/os/windows/debug.c index be8c349be086..241d993486e2 100644 --- a/module/os/windows/debug.c +++ b/module/os/windows/debug.c @@ -126,12 +126,30 @@ printBuffer(const char *fmt, ...) va_list args; va_start(args, fmt); char buf[max_line_length]; + size_t prefix_len; + + /* + * "%p" emits 16 hex digits on x64, so the prefix is 18 characters and + * needs 19 bytes with its terminator. Take the length back from the + * buffer rather than assuming it, so the offset used below cannot + * disagree with what was actually written. + */ _snprintf_s(buf, sizeof (buf), _TRUNCATE, "%p: ", PsGetCurrentThread()); - - int tmp = _vsnprintf_s(&buf[17], sizeof (buf), max_line_length, - fmt, args); - if (tmp >= max_line_length) { - _snprintf_s(&buf[17], 17, _TRUNCATE, "buffer too small"); + prefix_len = strlen(buf); + + /* + * The destination is &buf[prefix_len], so the capacity remaining is + * sizeof (buf) - prefix_len, not sizeof (buf). + * + * The count is _TRUNCATE: _vsnprintf_s null-terminates on truncation + * itself and returns -1, so test tmp < 0. The previous + * "tmp >= max_line_length" could never be true. + */ + int tmp = _vsnprintf_s(&buf[prefix_len], sizeof (buf) - prefix_len, + _TRUNCATE, fmt, args); + if (tmp < 0) { + _snprintf_s(&buf[prefix_len], sizeof (buf) - prefix_len, + _TRUNCATE, "buffer too small"); } KeAcquireSpinLock(&cbuf_spin, &level); diff --git a/module/os/windows/spl/spl-kmem.c b/module/os/windows/spl/spl-kmem.c index 4fad95d648c1..77c0d6ce7d99 100644 --- a/module/os/windows/spl/spl-kmem.c +++ b/module/os/windows/spl/spl-kmem.c @@ -3560,7 +3560,7 @@ kmem_cache_create( /* * Set cache properties. */ - (void) spl_strlcpy(cp->cache_name, name, KMEM_CACHE_NAMELEN + 1); + (void) strlcpy(cp->cache_name, name, KMEM_CACHE_NAMELEN + 1); strident_canon(cp->cache_name, KMEM_CACHE_NAMELEN + 1); cp->cache_bufsize = bufsize; cp->cache_align = align; @@ -6613,7 +6613,16 @@ kmem_asdprintf(const char *fmt, ...) return (ptr); } -#define SPL_VSNPRINTF_PROBE_MIN 256 +/* + * Sized so that Tier 2 covers every log line this driver actually emits, + * because Tier 3 is the only tier that allocates and __dprintf() - which + * measures with buf == NULL, so it always reaches at least Tier 2 - is + * reachable from inside the kmem allocators themselves. kmem_error() + * calls dprintf() directly. At 256 the metaslab_load message, 311 + * characters, fell through to Tier 3 on every emission, putting a + * kmem_alloc()/kmem_free() pair inside the allocator's own error path. + */ +#define SPL_VSNPRINTF_PROBE_MIN 1024 /* * 1 MiB: roughly 256x the largest single formatted string anywhere * in this tree today (PAGE_SIZE == 4096, in zfs_fletcher.c). No real @@ -6666,15 +6675,14 @@ spl_vsnprintf(char *buf, size_t size, const char *fmt, va_list args) } /* - * Tier 2: a small on-stack probe. Still IRQL-safe (no - * allocation) - every real caller in this tree writes a buffer - * under a few hundred bytes (the one known exception, - * module/lua/lstrlib.c's Lua channel-program formatting, is - * intentionally unbounded and falls through to Tier 3), so this - * is what makes every measure-only caller (buf==NULL, e.g. - * __dprintf's first call, kmem_asprintf(), kmem_vasprintf()) - * avoid the allocator entirely in the overwhelmingly common - * case. + * Tier 2: an on-stack probe, sized by SPL_VSNPRINTF_PROBE_MIN. + * Still IRQL-safe (no allocation), and it is what makes every + * measure-only caller (buf == NULL, e.g. __dprintf's first call, + * kmem_asprintf(), kmem_vasprintf()) avoid the allocator + * entirely. Sized to cover the log lines this driver actually + * emits rather than "a few hundred bytes" - see the constant. + * module/lua/lstrlib.c's Lua channel-program formatting is + * intentionally unbounded and still falls through to Tier 3. */ args_copy = args; ret = _vsnprintf_s(stackbuf, sizeof (stackbuf), _TRUNCATE, fmt, @@ -6683,7 +6691,8 @@ spl_vsnprintf(char *buf, size_t size, const char *fmt, va_list args) return (ret); /* - * Tier 3: only reached when even a 256-byte probe truncates. + * Tier 3: only reached when even the SPL_VSNPRINTF_PROBE_MIN stack + * probe truncates. * This is the only tier that allocates, so it is the only tier * that can violate IRQL rules (KM_SLEEP can block) - guard it * explicitly here, at the one place that actually needs it, @@ -6736,8 +6745,13 @@ kmem_asprintf(const char *fmt, ...) size = spl_vsnprintf(NULL, 0, fmt, adx); va_end(adx); + /* + * Degrade a failed measurement to an empty string rather than + * returning NULL: callers of this function do not check, because + * KM_SLEEP cannot fail. See kmem_vasprintf(), which matches. + */ if (size < 0) - return (NULL); /* honest failure, not KMEM_ZERO_SIZE_PTR */ + size = 0; size++; buf = kmem_alloc(size, KM_SLEEP); @@ -6759,21 +6773,27 @@ kmem_vasprintf(const char *fmt, va_list ap) { char *ptr; int size; - int r = -1; + /* + * spl_vsnprintf() returns the length the result requires, so one + * measuring call sizes the buffer exactly and there is no retry and + * no free on the success path. ap is reused for the write below: + * x64 va_list is a plain pointer passed by value, so a callee cannot + * advance the caller's copy - the assumption spl_vsnprintf() already + * documents for its own internal copies. + * + * A negative measurement degrades to an empty string rather than + * NULL. Callers of this function and of kmem_asprintf() are shared + * with the Linux and FreeBSD ports, where KM_SLEEP cannot fail and + * the result is never checked - see kcf_spi.c:241, spl-kstat.c:576 + * and spl-procfs-list.c:234, all of which use the result directly. + */ size = spl_vsnprintf(NULL, 0, fmt, ap); - if ((size >= 0) && (size < INT_MAX)) { - ptr = (char *)kmem_alloc(size + 1, KM_SLEEP); // +1 for null - if (ptr) { - r = spl_vsnprintf(ptr, size + 1, fmt, ap); // +1 for null - if ((r < 0) || (r > size)) { - kmem_free(ptr, size); - r = -1; - } - } - } else { - ptr = 0; - } + if (size < 0) + size = 0; + + ptr = (char *)kmem_alloc((size_t)size + 1, KM_SLEEP); // +1 for null + (void) spl_vsnprintf(ptr, (size_t)size + 1, fmt, ap); // +1 for null return (ptr); } diff --git a/module/os/windows/spl/spl-kstat.c b/module/os/windows/spl/spl-kstat.c index 61579cf812dd..dacda7fd5fcf 100644 --- a/module/os/windows/spl/spl-kstat.c +++ b/module/os/windows/spl/spl-kstat.c @@ -795,7 +795,7 @@ void kstat_set_string(char *dst, const char *src) { bzero(dst, KSTAT_STRLEN); - (void) spl_strlcpy(dst, src, KSTAT_STRLEN); + (void) strlcpy(dst, src, KSTAT_STRLEN); } void diff --git a/module/os/windows/spl/spl-taskq.c b/module/os/windows/spl/spl-taskq.c index a521ea40982a..556184be695e 100644 --- a/module/os/windows/spl/spl-taskq.c +++ b/module/os/windows/spl/spl-taskq.c @@ -2458,7 +2458,7 @@ taskq_create_common(const char *name, int instance, int nthreads, pri_t pri, * Make sure the name is 0-terminated, and conforms to the rules for * C indentifiers */ - (void) spl_strlcpy(tq->tq_name, name, TASKQ_NAMELEN + 1); + (void) strlcpy(tq->tq_name, name, TASKQ_NAMELEN + 1); strident_canon(tq->tq_name, TASKQ_NAMELEN + 1); tq->tq_flags = flags | TASKQ_CHANGING; diff --git a/module/os/windows/zfs/zfs_ctldir.c b/module/os/windows/zfs/zfs_ctldir.c index 488c5f30f87d..122487df9c49 100644 --- a/module/os/windows/zfs/zfs_ctldir.c +++ b/module/os/windows/zfs/zfs_ctldir.c @@ -995,8 +995,8 @@ zfsctl_snapshot_name(zfsvfs_t *zfsvfs, const char *snap_name, int len, if ((strlen(full_name) + 1 + strlen(snap_name)) >= len) return (SET_ERROR(ENAMETOOLONG)); - (void) spl_strlcat(full_name, "@", len); - (void) spl_strlcat(full_name, snap_name, len); + (void) strlcat(full_name, "@", len); + (void) strlcat(full_name, snap_name, len); return (0); } diff --git a/module/os/windows/zfs/zfs_debug.c b/module/os/windows/zfs/zfs_debug.c index 36c3fd09e99b..ca14dfd96492 100644 --- a/module/os/windows/zfs/zfs_debug.c +++ b/module/os/windows/zfs/zfs_debug.c @@ -138,7 +138,13 @@ zfs_dbgmsg_fini(void) kstat_delete(zfs_dbgmsg_kstat); while ((zdm = list_remove_head(&zfs_dbgmsgs)) != NULL) { - int size = sizeof (zfs_dbgmsg_t) + strlen(zdm->zdm_msg); + /* + * Free with the size recorded at allocation, as + * zfs_dbgmsg_purge() already does. Recomputing it from the + * stored message re-derives a length that no longer has to + * match what was allocated. + */ + int size = zdm->zdm_size; kmem_free(zdm, size); zfs_dbgmsg_size -= size; } @@ -184,7 +190,12 @@ __zfs_dbgmsg(char *buf) zfs_dbgmsg_t *zdm = kmem_zalloc(size, KM_SLEEP); zdm->zdm_size = size; zdm->zdm_timestamp = gethrestime_sec(); - strlcpy(zdm->zdm_msg, buf, size); + /* + * The bound is the room at zdm_msg, not the size of the whole + * allocation: zdm_msg starts offsetof(zfs_dbgmsg_t, zdm_msg) bytes + * in, so only size minus that many bytes exist there. + */ + strlcpy(zdm->zdm_msg, buf, size - offsetof(zfs_dbgmsg_t, zdm_msg)); mutex_enter(&zfs_dbgmsgs_lock); list_insert_tail(&zfs_dbgmsgs, zdm); @@ -254,8 +265,18 @@ __dprintf(boolean_t dprint, const char *file, const char *func, int roger = 0; va_start(adx, fmt); - i = snprintf(buf, size, "%s%s:%d:%s(): ", + (void) snprintf(buf, size, "%s%s:%d:%s(): ", prefix, newfile, line, func); + /* + * Take the prefix length from the buffer, not from snprintf()'s + * return value. spl_snprintf() returns the length the result + * required, which on truncation exceeds what it wrote - that would + * put buf + i past the end of buf and make size - i negative, which + * converts to a huge size_t for the call below. strlen() is the real + * prefix length whether or not the write was truncated, and always + * leaves size - i >= 1. + */ + i = (int)strlen(buf); roger = spl_vsnprintf(buf + i, size - i, fmt, adx); va_end(adx); diff --git a/module/os/windows/zfs/zfs_ioctl_os.c b/module/os/windows/zfs/zfs_ioctl_os.c index 26155c33c8c4..37836a86d1de 100644 --- a/module/os/windows/zfs/zfs_ioctl_os.c +++ b/module/os/windows/zfs/zfs_ioctl_os.c @@ -139,7 +139,7 @@ NTSTATUS zpool_zfs_get_metrics(PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_ perf->zpool_allocated = 0; perf->zpool_size = 0; perf->zfs_volSize = 0; - spl_strlcpy(perf->zpoolHealthState, "", sizeof(perf->zpoolHealthState)); + memset(perf->zpoolHealthState, 0, sizeof(perf->zpoolHealthState)); perf->l2arc_alloc_size = 0; perf->l2arc_space = 0; perf->special_mirror_alloc_size = 0; @@ -185,7 +185,7 @@ NTSTATUS zpool_zfs_get_metrics(PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_ perf->zpool_allocated = pool_alloc; perf->zpool_size = pool_size; - spl_strlcpy(perf->zpoolHealthState, healthState, + strlcpy(perf->zpoolHealthState, healthState, sizeof (perf->zpoolHealthState)); } else diff --git a/module/os/windows/zfs/zfs_vnops_windows.c b/module/os/windows/zfs/zfs_vnops_windows.c index 4b498fe41a1c..de7a6629f1d3 100644 --- a/module/os/windows/zfs/zfs_vnops_windows.c +++ b/module/os/windows/zfs/zfs_vnops_windows.c @@ -298,7 +298,7 @@ stream_parse(char *filename, char **streamname) // We now ADD ":$DATA" to the stream name. size_t remaining = PATH_MAX - (*streamname - filename); - if (spl_strlcat(*streamname, ":$DATA", remaining) >= remaining) + if (strlcat(*streamname, ":$DATA", remaining) >= remaining) return (SET_ERROR(ENAMETOOLONG)); return (0); @@ -2281,11 +2281,39 @@ BufferUserBuffer(IN OUT PIRP Irp, IN ULONG BufferLength) // describing the users input buffer, which we will now snapshot. // if (Irp->AssociatedIrp.SystemBuffer == NULL) { + PVOID buf; + UserBuffer = MapUserBuffer(Irp); - Irp->AssociatedIrp.SystemBuffer = - spl_ExAllocatePoolZero(NonPagedPoolNx, - BufferLength, + + /* + * FsRtlAllocatePoolWithQuotaTag(), which this replaced, does + * two things beyond allocating: it charges the calling + * process's pool quota, and it raises an exception on failure + * rather than returning NULL. That is why the code below has + * no NULL check - there was never a NULL to check. + * + * Its documented replacement, ExAllocatePool2 with + * POOL_FLAG_USE_QUOTA, is gated behind NTDDI_VERSION >= + * NTDDI_WIN10_VB in the WDK headers, above this project's + * WDK_WINVER target of 0x0601; using it would raise the + * driver's minimum supported Windows version tree-wide. + * Reproduce both effects with the lower-level primitives + * FsRtlAllocatePoolWithQuotaTag is itself built on instead. + * + * Dropping the quota charge would also let a user-mode caller + * drive unbounded non-paged pool allocation with none of it + * accounted to the requesting process. + */ + PsChargePoolQuota(PsGetCurrentProcess(), NonPagedPoolNx, + BufferLength); + buf = ExAllocatePoolUninitialized(NonPagedPoolNx, BufferLength, 'qtaf'); + if (buf == NULL) { + PsReturnPoolQuota(PsGetCurrentProcess(), NonPagedPoolNx, + BufferLength); + ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES); + } + Irp->AssociatedIrp.SystemBuffer = buf; // // Set the flags so that the completion code knows to // deallocate the buffer. diff --git a/module/zcommon/zfs_fletcher.c b/module/zcommon/zfs_fletcher.c index 00bb44365a5d..4255b495b951 100644 --- a/module/zcommon/zfs_fletcher.c +++ b/module/zcommon/zfs_fletcher.c @@ -897,16 +897,32 @@ fletcher_4_param_get(char *buffer, zfs_kernel_param_t *unused) const uint32_t impl = IMPL_READ(fletcher_4_impl_chosen); char *fmt; int cnt = 0; + int len; + + /* + * spl_snprintf() returns the length the result required, which on + * truncation is larger than what it wrote. Accumulating that directly + * would push cnt past PAGE_SIZE, making buffer + cnt point outside + * buffer and PAGE_SIZE - cnt a negative int that converts to a huge + * size_t, defeating the bound on the next call. Stop at the first + * truncation instead, which keeps cnt < PAGE_SIZE throughout. + */ /* list fastest */ fmt = IMPL_FMT(impl, IMPL_FASTEST); - cnt += spl_snprintf(buffer + cnt, PAGE_SIZE - cnt, fmt, "fastest"); + len = spl_snprintf(buffer + cnt, PAGE_SIZE - cnt, fmt, "fastest"); + if (len < 0 || len >= PAGE_SIZE - cnt) + return (cnt); + cnt += len; /* list all supported implementations */ for (uint32_t i = 0; i < fletcher_4_supp_impls_cnt; ++i) { fmt = IMPL_FMT(impl, i); - cnt += spl_snprintf(buffer + cnt, PAGE_SIZE - cnt, fmt, + len = spl_snprintf(buffer + cnt, PAGE_SIZE - cnt, fmt, fletcher_4_supp_impls[i]->name); + if (len < 0 || len >= PAGE_SIZE - cnt) + break; + cnt += len; } return (cnt); diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index 31153df201cf..e2a3efe37ac0 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -3000,8 +3000,8 @@ dmu_send_estimate_fast(dsl_dataset_t *origds, dsl_dataset_t *fromds, char dsname[ZFS_MAX_DATASET_NAME_LEN + 6]; dsl_dataset_name(origds, dsname); - (void) spl_strlcat(dsname, "/", sizeof (dsname)); - (void) spl_strlcat(dsname, recv_clone_name, sizeof (dsname)); + (void) strlcat(dsname, "/", sizeof (dsname)); + (void) strlcat(dsname, recv_clone_name, sizeof (dsname)); err = dsl_dataset_hold(origds->ds_dir->dd_pool, dsname, FTAG, &ds); diff --git a/module/zfs/dsl_dir.c b/module/zfs/dsl_dir.c index 48b4970eb5f7..0a40175cd3cd 100644 --- a/module/zfs/dsl_dir.c +++ b/module/zfs/dsl_dir.c @@ -441,7 +441,7 @@ getcomponent(const char *path, char *component, const char **nextp) if (p - path >= ZFS_MAX_DATASET_NAME_LEN) return (SET_ERROR(ENAMETOOLONG)); - (void) spl_strlcpy(component, path, (p - path) + 1); + (void) strlcpy(component, path, (p - path) + 1); p++; } else if (p[0] == '@') { /* @@ -454,7 +454,7 @@ getcomponent(const char *path, char *component, const char **nextp) if (p - path >= ZFS_MAX_DATASET_NAME_LEN) return (SET_ERROR(ENAMETOOLONG)); - (void) spl_strlcpy(component, path, (p - path) + 1); + (void) strlcpy(component, path, (p - path) + 1); } else { panic("invalid p=%p", (void *)p); } diff --git a/module/zfs/dsl_prop.c b/module/zfs/dsl_prop.c index 75afbaf78de3..8f1605ec6129 100644 --- a/module/zfs/dsl_prop.c +++ b/module/zfs/dsl_prop.c @@ -58,7 +58,14 @@ dodefault(zfs_prop_t prop, int intsz, int numints, void *buf) if (intsz != 1) return (SET_ERROR(EOVERFLOW)); - (void) spl_strlcpy(buf, zfs_prop_default_string(prop), + /* + * strncpy(), which this replaced, zero-filled the whole of + * buf once the source was exhausted; strlcpy() writes only up + * to the terminator and leaves the tail as it found it. buf + * reaches userland through "zfs get", so restore the fill. + */ + bzero(buf, numints); + (void) strlcpy(buf, zfs_prop_default_string(prop), numints); } else { if (intsz != 8 || numints < 1) @@ -1029,7 +1036,7 @@ dsl_prop_get_all_impl(objset_t *mos, uint64_t propobj, if (flags & DSL_PROP_GET_LOCAL) continue; - (void) spl_strlcpy(buf, za.za_name, + (void) strlcpy(buf, za.za_name, (suffix - za.za_name) + 1); propname = buf; diff --git a/module/zfs/spa_misc.c b/module/zfs/spa_misc.c index 0e3063ab9297..a4c794b03582 100644 --- a/module/zfs/spa_misc.c +++ b/module/zfs/spa_misc.c @@ -1677,7 +1677,7 @@ spa_altroot(spa_t *spa, char *buf, size_t buflen) if (spa->spa_root == NULL) buf[0] = '\0'; else - (void) spl_strlcpy(buf, spa->spa_root, buflen); + (void) strlcpy(buf, spa->spa_root, buflen); } int diff --git a/module/zfs/zcp_get.c b/module/zfs/zcp_get.c index 1d334b7783bb..d6e5f1082c81 100644 --- a/module/zfs/zcp_get.c +++ b/module/zfs/zcp_get.c @@ -611,7 +611,7 @@ parse_userquota_prop(const char *prop_name, zfs_userquota_prop_t *type, */ int domain_len = strrchr(cp, '-') - cp; domain_val = kmem_alloc(domain_len + 1, KM_SLEEP); - (void) spl_strlcpy(domain_val, cp, domain_len + 1); + (void) strlcpy(domain_val, cp, domain_len + 1); cp += domain_len + 1; (void) ddi_strtoll(cp, &end, 10, (longlong_t *)rid); diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index c266aac80a62..fb92a33d58f2 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -745,7 +745,7 @@ zfs_get_parent(const char *datasetname, char *parent, int parentsize) /* * Remove the @bla or /bla from the end of the name to get the parent. */ - (void) spl_strlcpy(parent, datasetname, parentsize); + (void) strlcpy(parent, datasetname, parentsize); cp = strrchr(parent, '@'); if (cp != NULL) { cp[0] = '\0'; diff --git a/module/zfs/zio_inject.c b/module/zfs/zio_inject.c index aacff6a6142f..6e4a97a1b5bd 100644 --- a/module/zfs/zio_inject.c +++ b/module/zfs/zio_inject.c @@ -894,7 +894,7 @@ zio_inject_list_next(int *id, char *name, size_t buflen, if (handler) { *record = handler->zi_record; *id = handler->zi_id; - (void) spl_strlcpy(name, spa_name(handler->zi_spa), buflen); + (void) strlcpy(name, spa_name(handler->zi_spa), buflen); ret = 0; } else { ret = SET_ERROR(ENOENT);