https://github.com/nuclearcat updated https://github.com/llvm/llvm-project/pull/196499
>From 4bc8453b9ddb4f99191239f115033da579367121 Mon Sep 17 00:00:00 2001 From: Colin Kinloch <[email protected]> Date: Thu, 2 Oct 2025 22:01:40 +0100 Subject: [PATCH 1/8] [clang][Sema] Add fortify warnings for `unistd.h` Define as builtin and check for overflows and over-reads in: * `read` * `write` * `getcwd` * `readlink` * `readlinkat` Also recognize `pread`/`pread64`/`pwrite`/`pwrite64` by name in the fortify checker. They are deliberately not declared as builtins because their prototypes use `off_t`, whose width is platform- and macro-dependent (notably `_FILE_OFFSET_BITS`); a fixed builtin signature would clash with the system header on some targets and silently disable fortify there. It also enables `ssize_t` for use in builtin signatures. Signed-off-by: Colin Kinloch <[email protected]> --- clang/include/clang/Basic/Builtins.td | 2 + .../clang/Basic/DiagnosticSemaKinds.td | 4 + clang/lib/AST/ASTContext.cpp | 9 +- clang/lib/Sema/SemaChecking.cpp | 187 +++++++++++++++++- .../Sema/warn-fortify-source-prototype-gate.c | 62 ++++++ clang/test/Sema/warn-fortify-source-typedef.c | 18 ++ clang/test/Sema/warn-fortify-source.c | 68 +++++++ clang/utils/TableGen/ClangBuiltinsEmitter.cpp | 1 + 8 files changed, 344 insertions(+), 7 deletions(-) create mode 100644 clang/test/Sema/warn-fortify-source-prototype-gate.c create mode 100644 clang/test/Sema/warn-fortify-source-typedef.c diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 40ec94ab75046..95861fc1fd5d8 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -3759,6 +3759,8 @@ def StrnCaseCmp : GNULibBuiltin<"strings.h"> { let RequiresUndef = 1; } +// POSIX unistd.h + def GNU_Exit : GNULibBuiltin<"unistd.h"> { let Spellings = ["_exit"]; let Attributes = [NoReturn]; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 5c225dc56ddd8..857d08ed9787d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -965,6 +965,10 @@ def warn_fortify_source_overflow def warn_fortify_source_size_mismatch : Warning< "'%0' size argument is too large; destination buffer has size %1," " but size argument is %2">, InGroup<FortifySource>; +def warn_fortify_destination_size_mismatch + : Warning<"'%0' size argument is too large; source buffer has size %2," + " but size argument is %1">, + InGroup<FortifySource>; def warn_fortify_strlen_overflow: Warning< "'%0' will always overflow; destination buffer has size %1," diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index a0894318dbd53..ec7eb82b8e76e 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -12696,9 +12696,12 @@ static QualType DecodeTypeFromStr(const char *&Str, const ASTContext &Context, assert(HowLong == 0 && !Signed && !Unsigned && "Bad modifiers for 'b'!"); Type = Context.BoolTy; break; - case 'z': // size_t. - assert(HowLong == 0 && !Signed && !Unsigned && "Bad modifiers for 'z'!"); - Type = Context.getSizeType(); + case 'z': // size_t and ssize_t. + assert(HowLong == 0 && "Bad modifiers for 'z'!"); + if (Signed) + Type = Context.getSignedSizeType(); + else + Type = Context.getSizeType(); break; case 'w': // wchar_t. assert(HowLong == 0 && !Signed && !Unsigned && "Bad modifiers for 'w'!"); diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 530587208cce8..8c3d64e9d9f8b 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1165,7 +1165,112 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, unsigned BuiltinID = UseDecl->getBuiltinID(/*ConsiderWrappers=*/true); - if (!BuiltinID) + // Some libc I/O functions are intentionally not Clang builtins: + // * "read", "write", "readlink", "readlinkat", and "getcwd" are common + // identifiers (or names that appear in asm-label wrappers); declaring + // them as builtins triggers -Wincompatible-library-redeclaration on + // harmless local declarations (an error under -Werror). + // * pread/pwrite prototypes use off_t, whose width is platform- and + // macro-dependent (notably _FILE_OFFSET_BITS); a fixed builtin signature + // would clash with the system header on some targets. + // Recognize them by name, but only after checking the full POSIX prototype + // so that an unrelated function happening to share the name is not + // diagnosed as if it were the libc function. + enum class LibCDispatch { + None, + Read, + Write, + PRead, + PWrite, + ReadLink, + ReadLinkAt, + GetCWD, + }; + LibCDispatch LibC = LibCDispatch::None; + StringRef LibCName; + if (!BuiltinID && FD->isExternC() && FD->getIdentifier() && + !FD->isVariadic()) { + ASTContext &Ctx = getASTContext(); + QualType IntTy = Ctx.IntTy; + QualType SizeTy = Ctx.getSizeType(); + unsigned SizeWidth = Ctx.getTypeSize(SizeTy); + QualType VoidPtrTy = Ctx.VoidPtrTy; + QualType ConstVoidPtrTy = Ctx.getPointerType(Ctx.VoidTy.withConst()); + QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); + QualType ConstCharPtrTy = Ctx.getPointerType(Ctx.CharTy.withConst()); + + auto Same = [&](QualType A, QualType B) { + return Ctx.hasSameUnqualifiedType(A, B); + }; + // ssize_t is not canonicalized to a single Clang type: glibc declares it + // as `long` while other libcs (and Clang's getIntTypeForBitwidth for + // size_t-width) pick `int` on ILP32 targets. Both are valid ssize_t + // implementations as long as they are signed integers of size_t's width. + // Match structurally rather than by canonical-type equality so the gate + // is tolerant of either libc choice. + auto IsSSizeT = [&](QualType T) { + return T->isSignedIntegerType() && Ctx.getTypeSize(T) == SizeWidth; + }; + auto P = [&](unsigned I) { return FD->getParamDecl(I)->getType(); }; + QualType Ret = FD->getReturnType(); + StringRef Name = FD->getIdentifier()->getName(); + unsigned NumArgs = TheCall->getNumArgs(); + + if (FD->getNumParams() == 2 && NumArgs == 2) { + // char *getcwd(char *, size_t) + if (Name == "getcwd" && Same(Ret, CharPtrTy) && Same(P(0), CharPtrTy) && + Same(P(1), SizeTy)) { + LibC = LibCDispatch::GetCWD; + LibCName = Name; + } + } else if (FD->getNumParams() == 3 && NumArgs == 3 && IsSSizeT(Ret)) { + if (Name == "read" && Same(P(0), IntTy) && Same(P(1), VoidPtrTy) && + Same(P(2), SizeTy)) { + LibC = LibCDispatch::Read; + LibCName = Name; + } else if (Name == "write" && Same(P(0), IntTy) && + Same(P(1), ConstVoidPtrTy) && Same(P(2), SizeTy)) { + LibC = LibCDispatch::Write; + LibCName = Name; + } else if (Name == "readlink" && Same(P(0), ConstCharPtrTy) && + Same(P(1), CharPtrTy) && Same(P(2), SizeTy)) { + LibC = LibCDispatch::ReadLink; + LibCName = Name; + } + } else if (FD->getNumParams() == 4 && NumArgs == 4 && IsSSizeT(Ret)) { + // pread/pwrite take off_t (platform-dependent width, but at least as + // wide as size_t in practice); pread64/pwrite64 take off64_t which is + // always 64-bit signed. Require a signed integer of the appropriate + // width so unrelated declarations (e.g. taking int on a 64-bit target) + // do not get matched. + QualType Off = P(3); + bool OffOK = Off->isSignedIntegerType(); + if (OffOK) { + unsigned OffWidth = Ctx.getTypeSize(Off); + if (Name == "pread64" || Name == "pwrite64") + OffOK = OffWidth == 64; + else + OffOK = OffWidth >= SizeWidth; + } + if ((Name == "pread" || Name == "pread64") && OffOK && + Same(P(0), IntTy) && Same(P(1), VoidPtrTy) && Same(P(2), SizeTy)) { + LibC = LibCDispatch::PRead; + LibCName = Name; + } else if ((Name == "pwrite" || Name == "pwrite64") && OffOK && + Same(P(0), IntTy) && Same(P(1), ConstVoidPtrTy) && + Same(P(2), SizeTy)) { + LibC = LibCDispatch::PWrite; + LibCName = Name; + } else if (Name == "readlinkat" && Same(P(0), IntTy) && + Same(P(1), ConstCharPtrTy) && Same(P(2), CharPtrTy) && + Same(P(3), SizeTy)) { + LibC = LibCDispatch::ReadLinkAt; + LibCName = Name; + } + } + } + + if (!BuiltinID && LibC == LibCDispatch::None) return; const TargetInfo &TI = getASTContext().getTargetInfo(); @@ -1253,8 +1358,19 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, std::optional<llvm::APSInt> DestinationSize; unsigned DiagID = 0; bool IsChkVariant = false; + bool IsTriggered = false; + + auto CompareSizeSourceToDest = [&]() { + return SourceSize && DestinationSize + ? std::optional<int>{llvm::APSInt::compareValues( + *SourceSize, *DestinationSize)} + : std::nullopt; + }; + + auto GetFunctionName = [&]() -> std::string { + if (LibC != LibCDispatch::None) + return LibCName.str(); - auto GetFunctionName = [&]() { std::string FunctionNameStr = getASTContext().BuiltinInfo.getName(BuiltinID); llvm::StringRef FunctionName = FunctionNameStr; @@ -1270,6 +1386,58 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, return FunctionName.str(); }; + if (LibC == LibCDispatch::Read) { + // read: ssize_t(int fd, void buf[.count], size_t count); + // Up to count(2) bytes are written into buf(1). + DiagID = diag::warn_fortify_source_size_mismatch; + SourceSize = ComputeExplicitObjectSizeArgument(2); + DestinationSize = ComputeSizeArgument(1); + IsTriggered = CompareSizeSourceToDest() > 0; + } else if (LibC == LibCDispatch::Write) { + // write: ssize_t(int, const void buf[.count], size_t count); + // Up to count(2) bytes are read from buf(1). + DiagID = diag::warn_fortify_destination_size_mismatch; + SourceSize = ComputeSizeArgument(1); + DestinationSize = ComputeExplicitObjectSizeArgument(2); + IsTriggered = CompareSizeSourceToDest() < 0; + } else if (LibC == LibCDispatch::PRead) { + // pread/pread64: ssize_t(int fd, void buf[.count], size_t count, off_t); + // Up to count(2) bytes are written into buf(1). + DiagID = diag::warn_fortify_source_size_mismatch; + SourceSize = ComputeExplicitObjectSizeArgument(2); + DestinationSize = ComputeSizeArgument(1); + IsTriggered = CompareSizeSourceToDest() > 0; + } else if (LibC == LibCDispatch::PWrite) { + // pwrite/pwrite64: ssize_t(int, const void buf[.count], size_t count, off_t); + // Up to count(2) bytes are read from buf(1). + DiagID = diag::warn_fortify_destination_size_mismatch; + SourceSize = ComputeSizeArgument(1); + DestinationSize = ComputeExplicitObjectSizeArgument(2); + IsTriggered = CompareSizeSourceToDest() < 0; + } else if (LibC == LibCDispatch::ReadLink) { + // readlink: + // ssize_t(const char *restrict, char buf[.bufsize], size_t bufsize); + // Up to bufsize(2) bytes are written into buf(1). + DiagID = diag::warn_fortify_source_size_mismatch; + SourceSize = ComputeExplicitObjectSizeArgument(2); + DestinationSize = ComputeSizeArgument(1); + IsTriggered = CompareSizeSourceToDest() > 0; + } else if (LibC == LibCDispatch::ReadLinkAt) { + // readlinkat: + // ssize_t(int, const char *restrict, char buf[.bufsize], size_t bufsize); + // Up to bufsize(3) bytes are written into buf(2). + DiagID = diag::warn_fortify_source_size_mismatch; + SourceSize = ComputeExplicitObjectSizeArgument(3); + DestinationSize = ComputeSizeArgument(2); + IsTriggered = CompareSizeSourceToDest() > 0; + } else if (LibC == LibCDispatch::GetCWD) { + // char *getcwd(char buf[.size], size_t size); + // Up to size(1) bytes are written into buf(0). + DiagID = diag::warn_fortify_source_size_mismatch; + SourceSize = ComputeExplicitObjectSizeArgument(1); + DestinationSize = ComputeSizeArgument(0); + IsTriggered = CompareSizeSourceToDest() > 0; + } else switch (BuiltinID) { default: return; @@ -1282,6 +1450,7 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, DiagID = diag::warn_fortify_strlen_overflow; SourceSize = ComputeStrLenArgument(1); DestinationSize = ComputeSizeArgument(0); + IsTriggered = CompareSizeSourceToDest() > 0; break; } @@ -1292,6 +1461,7 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, SourceSize = ComputeStrLenArgument(1); DestinationSize = ComputeExplicitObjectSizeArgument(2); IsChkVariant = true; + IsTriggered = CompareSizeSourceToDest() > 0; break; } @@ -1362,11 +1532,13 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, } else { DestinationSize = ComputeSizeArgument(0); } + IsTriggered = CompareSizeSourceToDest() > 0; break; } } return; } + case Builtin::BI__builtin___memcpy_chk: case Builtin::BI__builtin___memmove_chk: case Builtin::BI__builtin___memset_chk: @@ -1382,6 +1554,7 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, DestinationSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); IsChkVariant = true; + IsTriggered = CompareSizeSourceToDest() > 0; break; } @@ -1391,6 +1564,7 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, SourceSize = ComputeExplicitObjectSizeArgument(1); DestinationSize = ComputeExplicitObjectSizeArgument(3); IsChkVariant = true; + IsTriggered = CompareSizeSourceToDest() > 0; break; } @@ -1408,6 +1582,7 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, DiagID = diag::warn_fortify_source_size_mismatch; SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); DestinationSize = ComputeSizeArgument(0); + IsTriggered = CompareSizeSourceToDest() > 0; break; } @@ -1424,6 +1599,7 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, DiagID = diag::warn_fortify_source_overflow; SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); DestinationSize = ComputeSizeArgument(0); + IsTriggered = CompareSizeSourceToDest() > 0; break; } case Builtin::BIbcopy: @@ -1431,8 +1607,10 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, DiagID = diag::warn_fortify_source_overflow; SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); DestinationSize = ComputeSizeArgument(1); + IsTriggered = CompareSizeSourceToDest() > 0; break; } + case Builtin::BIsnprintf: case Builtin::BI__builtin_snprintf: case Builtin::BIvsnprintf: @@ -1472,11 +1650,12 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, const Expr *Dest = TheCall->getArg(0)->IgnoreCasts(); IdentifierInfo *FnInfo = FD->getIdentifier(); CheckSizeofMemaccessArgument(LenArg, Dest, FnInfo); + IsTriggered = CompareSizeSourceToDest() > 0; + break; } } - if (!SourceSize || !DestinationSize || - llvm::APSInt::compareValues(*SourceSize, *DestinationSize) <= 0) + if (!IsTriggered) return; std::string FunctionName = GetFunctionName(); diff --git a/clang/test/Sema/warn-fortify-source-prototype-gate.c b/clang/test/Sema/warn-fortify-source-prototype-gate.c new file mode 100644 index 0000000000000..2112af03e1734 --- /dev/null +++ b/clang/test/Sema/warn-fortify-source-prototype-gate.c @@ -0,0 +1,62 @@ +// RUN: %clang_cc1 -triple x86_64-apple-macosx10.14.0 %s -verify -Werror + +// Verify that the fortify dispatch for read/write/pread/pwrite/readlink/ +// readlinkat/getcwd is gated on the full POSIX prototype: a user-defined +// function that happens to share a name with one of these libc functions but +// has a different signature must not be diagnosed as if it were the libc +// function. (Reviewer-reported regression: extern-C + arity match alone +// would falsely diagnose unrelated code under -Werror.) + +typedef unsigned long size_t; +typedef long ssize_t; + +// expected-no-diagnostics + +// Variadic: prefix happens to match POSIX read, but the varargs make this +// an unrelated declaration. +ssize_t read(int, void *, size_t, ...); + +void test_read_variadic(void) { + char b[4]; + read(0, b, 8); +} + +// Wrong return type (int) for write. +int write(int fd, const char *buf, size_t n); + +void test_write_wrong_return(void) { + char buf[4]; + write(0, buf, 8); +} + +// Wrong return type (void) for readlink. +void readlink(const char *p, char *b, size_t n); + +void test_readlink_wrong_return(void) { + char b[4]; + readlink("/", b, 8); +} + +// Wrong second parameter type (int instead of char*) for getcwd. +char *getcwd(int x, size_t n); + +void test_getcwd_wrong_param(void) { + getcwd(42, 8); +} + +// pread64 with int offset on x86_64: off64_t is required to be 64-bit signed, +// so int (32-bit on x86_64) is not POSIX pread64. +ssize_t pread64(int, void *, size_t, int); + +void test_pread64_narrow_offset(void) { + char b[4]; + pread64(0, b, 8, 0); +} + +// pwrite64 with int offset: same as above. +ssize_t pwrite64(int, const void *, size_t, int); + +void test_pwrite64_narrow_offset(void) { + char b[4]; + pwrite64(0, b, 8, 0); +} diff --git a/clang/test/Sema/warn-fortify-source-typedef.c b/clang/test/Sema/warn-fortify-source-typedef.c new file mode 100644 index 0000000000000..af7c7565d7658 --- /dev/null +++ b/clang/test/Sema/warn-fortify-source-typedef.c @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -triple i686-unknown-linux %s -verify +// RUN: %clang_cc1 -triple x86_64-unknown-linux %s -verify + +// Verify that the fortify dispatch is tolerant of libc typedef choices for +// ssize_t. glibc uses `long` while other libcs (musl, bionic) may use a +// type that matches Clang's signed counterpart of size_t. On ILP32 these +// differ canonically (`long` vs `int`) even though both are 32-bit signed +// integers; the gate must accept either as ssize_t. + +typedef __SIZE_TYPE__ size_t; +typedef long ssize_t; + +ssize_t read(int, void *, size_t); + +void test_read(void) { + char b[4]; + read(0, b, 8); // expected-warning {{'read' size argument is too large; destination buffer has size 4, but size argument is 8}} +} diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c index d0b519a516545..5c4df36d2c88d 100644 --- a/clang/test/Sema/warn-fortify-source.c +++ b/clang/test/Sema/warn-fortify-source.c @@ -24,6 +24,19 @@ void *memcpy(void *dst, const void *src, size_t c); void bcopy(const void *src, void *dst, size_t n); void bzero(void *dst, size_t n); +typedef long ssize_t; +typedef long off_t; +typedef long long off64_t; +ssize_t read(int fd, void *buf, size_t count); +ssize_t write(int fd, const void *buf, size_t count); +ssize_t pread(int fd, void *buf, size_t count, off_t offset); +ssize_t pread64(int fd, void *buf, size_t count, off64_t offset); +ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset); +ssize_t pwrite64(int fd, const void *buf, size_t count, off64_t offset); +char *getcwd(char *buf, size_t size); +ssize_t readlink(const char *path, char *buf, size_t bufsize); +ssize_t readlinkat(int fd, const char *path, char *buf, size_t bufsize); + #ifdef __cplusplus } #endif @@ -116,6 +129,61 @@ void call_bcopy_bzero(void) { __builtin_bzero(dst, 11); // expected-warning {{'bzero' will always overflow; destination buffer has size 10, but size argument is 11}} } +void call_read(void) { + char buf[10]; + read(0, buf, 10); + read(0, buf, 20); // expected-warning {{'read' size argument is too large; destination buffer has size 10, but size argument is 20}} +} + +void call_pread(void) { + char buf[10]; + pread(0, buf, 10, 0); + pread(0, buf, 20, 0); // expected-warning {{'pread' size argument is too large; destination buffer has size 10, but size argument is 20}} +} + +void call_pread64(void) { + char buf[10]; + pread64(0, buf, 10, 0); + pread64(0, buf, 20, 0); // expected-warning {{'pread64' size argument is too large; destination buffer has size 10, but size argument is 20}} +} + +void call_write(void) { + char buf[10]; + write(0, buf, 10); + write(0, buf, 20); // expected-warning {{'write' size argument is too large; source buffer has size 10, but size argument is 20}} +} + +void call_pwrite(void) { + char buf[10]; + pwrite(0, buf, 10, 0); + pwrite(0, buf, 20, 0); // expected-warning {{'pwrite' size argument is too large; source buffer has size 10, but size argument is 20}} +} + +void call_pwrite64(void) { + char buf[10]; + pwrite64(0, buf, 10, 0); + pwrite64(0, buf, 20, 0); // expected-warning {{'pwrite64' size argument is too large; source buffer has size 10, but size argument is 20}} +} + +void call_getcwd(void) { + char buf[10]; + getcwd(buf, 10); + getcwd(buf, 20); // expected-warning {{'getcwd' size argument is too large; destination buffer has size 10, but size argument is 20}} +} + +void call_readlink(void) { + char buf[10]; + readlink("path", buf, 10); + readlink("path", buf, 20); // expected-warning {{'readlink' size argument is too large; destination buffer has size 10, but size argument is 20}} +} + +void call_readlinkat(void) { + char buf[10]; + readlinkat(0, "path", buf, 10); + readlinkat(0, "path", buf, 20); // expected-warning {{'readlinkat' size argument is too large; destination buffer has size 10, but size argument is 20}} +} + + void call_snprintf(double d, int n) { char buf[10]; __builtin_snprintf(buf, 10, "merp"); diff --git a/clang/utils/TableGen/ClangBuiltinsEmitter.cpp b/clang/utils/TableGen/ClangBuiltinsEmitter.cpp index c2e38c0d6aeb8..ae7f99107c03b 100644 --- a/clang/utils/TableGen/ClangBuiltinsEmitter.cpp +++ b/clang/utils/TableGen/ClangBuiltinsEmitter.cpp @@ -371,6 +371,7 @@ class PrototypeParser { .Case("short", "s") .Case("sigjmp_buf", "SJ") .Case("size_t", "z") + .Case("ssize_t", "Sz") .Case("ucontext_t", "K") .Case("uint32_t", "UZi") .Case("uint64_t", "UWi") >From cfd2089f99b970b36c54ea58ddfe1f5e7f3dd248 Mon Sep 17 00:00:00 2001 From: Colin Kinloch <[email protected]> Date: Mon, 3 Nov 2025 02:53:09 +0000 Subject: [PATCH 2/8] [clang][Sema] Add min/max operation size for fortify checks Preserve existing bcopy/bzero fortify handling under the new min/max operation-size model. bzero is handled like memset, while bcopy is handled like memcpy with reversed source/destination arguments, allowing both destination overflow and source over-read diagnostics. --- .../clang/Basic/DiagnosticSemaKinds.td | 8 +- clang/lib/Sema/SemaChecking.cpp | 699 +++++++++--------- clang/test/Sema/builtin-memcpy.c | 3 +- .../Sema/warn-fortify-source-prototype-gate.c | 15 +- clang/test/Sema/warn-fortify-source.c | 43 +- 5 files changed, 407 insertions(+), 361 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 857d08ed9787d..3ee2ec39e94df 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -962,12 +962,16 @@ def warn_builtin_chk_overflow : Warning< def warn_fortify_source_overflow : Warning<warn_builtin_chk_overflow.Summary>, InGroup<FortifySource>; +def warn_fortify_destination_over_read + : Warning<"'%0' will always over-read; source buffer has size %1," + " but size argument is %2">, + InGroup<FortifySource>; def warn_fortify_source_size_mismatch : Warning< "'%0' size argument is too large; destination buffer has size %1," " but size argument is %2">, InGroup<FortifySource>; def warn_fortify_destination_size_mismatch - : Warning<"'%0' size argument is too large; source buffer has size %2," - " but size argument is %1">, + : Warning<"'%0' size argument is too large; source buffer has size %1," + " but size argument is %2">, InGroup<FortifySource>; def warn_fortify_strlen_overflow: Warning< diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 8c3d64e9d9f8b..666f93517dff8 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1176,101 +1176,92 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, // Recognize them by name, but only after checking the full POSIX prototype // so that an unrelated function happening to share the name is not // diagnosed as if it were the libc function. - enum class LibCDispatch { - None, - Read, - Write, - PRead, - PWrite, - ReadLink, - ReadLinkAt, - GetCWD, + struct LibcFuncDesc { + StringRef Name; + QualType Return; + SmallVector<QualType, 4> Params; + std::optional<unsigned> SourceSizeIdx; // __bos buffer; reads from + std::optional<unsigned> MaxOpSizeIdx; // count arg (constant-evaluated) + std::optional<unsigned> DestSizeIdx; // __bos buffer; writes to }; - LibCDispatch LibC = LibCDispatch::None; - StringRef LibCName; + + std::optional<LibcFuncDesc> LibCMatch; if (!BuiltinID && FD->isExternC() && FD->getIdentifier() && !FD->isVariadic()) { ASTContext &Ctx = getASTContext(); QualType IntTy = Ctx.IntTy; QualType SizeTy = Ctx.getSizeType(); - unsigned SizeWidth = Ctx.getTypeSize(SizeTy); QualType VoidPtrTy = Ctx.VoidPtrTy; QualType ConstVoidPtrTy = Ctx.getPointerType(Ctx.VoidTy.withConst()); QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); QualType ConstCharPtrTy = Ctx.getPointerType(Ctx.CharTy.withConst()); + // ssize_t / off_t / off64_t aren't single Clang types (ssize_t differs + // across libcs, off_t depends on _FILE_OFFSET_BITS, off64_t is always + // 64-bit). The name match has already pinned us to POSIX intent, so + // MatchSlot below treats any integer-typed slot as compatible with any + // integer-typed argument; these aliases just keep the table readable. + QualType SSizeTy = IntTy; + QualType OffTy = IntTy; + QualType Off64Ty = IntTy; + + // Same unqualified type, or both integer types: accepts libc typedef + // divergence (ssize_t/off_t/off64_t) while still rejecting pointer or + // aggregate mismatches. + auto MatchSlot = [&](QualType Expected, QualType Actual) { + if (Ctx.hasSameUnqualifiedType(Expected, Actual)) + return true; + return Expected->isIntegerType() && Actual->isIntegerType(); + }; - auto Same = [&](QualType A, QualType B) { - return Ctx.hasSameUnqualifiedType(A, B); + auto Make = [](StringRef Name, QualType Ret, + std::initializer_list<QualType> Params, + std::optional<unsigned> Src, std::optional<unsigned> MaxOp, + std::optional<unsigned> Dest) { + return LibcFuncDesc{Name, Ret, Params, Src, MaxOp, Dest}; }; - // ssize_t is not canonicalized to a single Clang type: glibc declares it - // as `long` while other libcs (and Clang's getIntTypeForBitwidth for - // size_t-width) pick `int` on ILP32 targets. Both are valid ssize_t - // implementations as long as they are signed integers of size_t's width. - // Match structurally rather than by canonical-type equality so the gate - // is tolerant of either libc choice. - auto IsSSizeT = [&](QualType T) { - return T->isSignedIntegerType() && Ctx.getTypeSize(T) == SizeWidth; + + auto LookupLibc = [&](StringRef Name) -> std::optional<LibcFuncDesc> { + if (Name == "getcwd") + return Make(Name, CharPtrTy, {CharPtrTy, SizeTy}, std::nullopt, 1, 0); + if (Name == "read") + return Make(Name, SSizeTy, {IntTy, VoidPtrTy, SizeTy}, std::nullopt, 2, + 1); + if (Name == "write") + return Make(Name, SSizeTy, {IntTy, ConstVoidPtrTy, SizeTy}, 1, 2, + std::nullopt); + if (Name == "readlink") + return Make(Name, SSizeTy, {ConstCharPtrTy, CharPtrTy, SizeTy}, + std::nullopt, 2, 1); + if (Name == "readlinkat") + return Make(Name, SSizeTy, {IntTy, ConstCharPtrTy, CharPtrTy, SizeTy}, + std::nullopt, 3, 2); + if (Name == "pread") + return Make(Name, SSizeTy, {IntTy, VoidPtrTy, SizeTy, OffTy}, + std::nullopt, 2, 1); + if (Name == "pread64") + return Make(Name, SSizeTy, {IntTy, VoidPtrTy, SizeTy, Off64Ty}, + std::nullopt, 2, 1); + if (Name == "pwrite") + return Make(Name, SSizeTy, {IntTy, ConstVoidPtrTy, SizeTy, OffTy}, 1, 2, + std::nullopt); + if (Name == "pwrite64") + return Make(Name, SSizeTy, {IntTy, ConstVoidPtrTy, SizeTy, Off64Ty}, 1, + 2, std::nullopt); + return std::nullopt; }; - auto P = [&](unsigned I) { return FD->getParamDecl(I)->getType(); }; - QualType Ret = FD->getReturnType(); - StringRef Name = FD->getIdentifier()->getName(); - unsigned NumArgs = TheCall->getNumArgs(); - - if (FD->getNumParams() == 2 && NumArgs == 2) { - // char *getcwd(char *, size_t) - if (Name == "getcwd" && Same(Ret, CharPtrTy) && Same(P(0), CharPtrTy) && - Same(P(1), SizeTy)) { - LibC = LibCDispatch::GetCWD; - LibCName = Name; - } - } else if (FD->getNumParams() == 3 && NumArgs == 3 && IsSSizeT(Ret)) { - if (Name == "read" && Same(P(0), IntTy) && Same(P(1), VoidPtrTy) && - Same(P(2), SizeTy)) { - LibC = LibCDispatch::Read; - LibCName = Name; - } else if (Name == "write" && Same(P(0), IntTy) && - Same(P(1), ConstVoidPtrTy) && Same(P(2), SizeTy)) { - LibC = LibCDispatch::Write; - LibCName = Name; - } else if (Name == "readlink" && Same(P(0), ConstCharPtrTy) && - Same(P(1), CharPtrTy) && Same(P(2), SizeTy)) { - LibC = LibCDispatch::ReadLink; - LibCName = Name; - } - } else if (FD->getNumParams() == 4 && NumArgs == 4 && IsSSizeT(Ret)) { - // pread/pwrite take off_t (platform-dependent width, but at least as - // wide as size_t in practice); pread64/pwrite64 take off64_t which is - // always 64-bit signed. Require a signed integer of the appropriate - // width so unrelated declarations (e.g. taking int on a 64-bit target) - // do not get matched. - QualType Off = P(3); - bool OffOK = Off->isSignedIntegerType(); - if (OffOK) { - unsigned OffWidth = Ctx.getTypeSize(Off); - if (Name == "pread64" || Name == "pwrite64") - OffOK = OffWidth == 64; - else - OffOK = OffWidth >= SizeWidth; - } - if ((Name == "pread" || Name == "pread64") && OffOK && - Same(P(0), IntTy) && Same(P(1), VoidPtrTy) && Same(P(2), SizeTy)) { - LibC = LibCDispatch::PRead; - LibCName = Name; - } else if ((Name == "pwrite" || Name == "pwrite64") && OffOK && - Same(P(0), IntTy) && Same(P(1), ConstVoidPtrTy) && - Same(P(2), SizeTy)) { - LibC = LibCDispatch::PWrite; - LibCName = Name; - } else if (Name == "readlinkat" && Same(P(0), IntTy) && - Same(P(1), ConstCharPtrTy) && Same(P(2), CharPtrTy) && - Same(P(3), SizeTy)) { - LibC = LibCDispatch::ReadLinkAt; - LibCName = Name; - } + + if (auto Desc = LookupLibc(FD->getIdentifier()->getName())) { + bool Matches = FD->getNumParams() == Desc->Params.size() && + TheCall->getNumArgs() == Desc->Params.size() && + MatchSlot(Desc->Return, FD->getReturnType()); + for (unsigned I = 0; Matches && I < Desc->Params.size(); ++I) + Matches = MatchSlot(Desc->Params[I], FD->getParamDecl(I)->getType()); + if (Matches) + LibCMatch = std::move(*Desc); } } - if (!BuiltinID && LibC == LibCDispatch::None) + if (!BuiltinID && !LibCMatch) return; const TargetInfo &TI = getASTContext().getTargetInfo(); @@ -1354,22 +1345,24 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, return std::nullopt; }; + // Size of the memory read from std::optional<llvm::APSInt> SourceSize; + // Size of the memory written to std::optional<llvm::APSInt> DestinationSize; - unsigned DiagID = 0; + // Maximum operation size for detecting possible out of bounds access + std::optional<llvm::APSInt> MaxOperationSize; + // Minimum operation size for detecting definite out of bounds access + std::optional<llvm::APSInt> MinOperationSize; + + unsigned DiagOverflowID = diag::warn_fortify_source_overflow; + unsigned DiagMayOverflowID = diag::warn_fortify_source_size_mismatch; + unsigned DiagOverReadID = diag::warn_fortify_destination_over_read; + unsigned DiagMayOverReadID = diag::warn_fortify_destination_size_mismatch; bool IsChkVariant = false; - bool IsTriggered = false; - - auto CompareSizeSourceToDest = [&]() { - return SourceSize && DestinationSize - ? std::optional<int>{llvm::APSInt::compareValues( - *SourceSize, *DestinationSize)} - : std::nullopt; - }; auto GetFunctionName = [&]() -> std::string { - if (LibC != LibCDispatch::None) - return LibCName.str(); + if (LibCMatch) + return LibCMatch->Name.str(); std::string FunctionNameStr = getASTContext().BuiltinInfo.getName(BuiltinID); @@ -1386,287 +1379,297 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, return FunctionName.str(); }; - if (LibC == LibCDispatch::Read) { - // read: ssize_t(int fd, void buf[.count], size_t count); - // Up to count(2) bytes are written into buf(1). - DiagID = diag::warn_fortify_source_size_mismatch; - SourceSize = ComputeExplicitObjectSizeArgument(2); - DestinationSize = ComputeSizeArgument(1); - IsTriggered = CompareSizeSourceToDest() > 0; - } else if (LibC == LibCDispatch::Write) { - // write: ssize_t(int, const void buf[.count], size_t count); - // Up to count(2) bytes are read from buf(1). - DiagID = diag::warn_fortify_destination_size_mismatch; - SourceSize = ComputeSizeArgument(1); - DestinationSize = ComputeExplicitObjectSizeArgument(2); - IsTriggered = CompareSizeSourceToDest() < 0; - } else if (LibC == LibCDispatch::PRead) { - // pread/pread64: ssize_t(int fd, void buf[.count], size_t count, off_t); - // Up to count(2) bytes are written into buf(1). - DiagID = diag::warn_fortify_source_size_mismatch; - SourceSize = ComputeExplicitObjectSizeArgument(2); - DestinationSize = ComputeSizeArgument(1); - IsTriggered = CompareSizeSourceToDest() > 0; - } else if (LibC == LibCDispatch::PWrite) { - // pwrite/pwrite64: ssize_t(int, const void buf[.count], size_t count, off_t); - // Up to count(2) bytes are read from buf(1). - DiagID = diag::warn_fortify_destination_size_mismatch; - SourceSize = ComputeSizeArgument(1); - DestinationSize = ComputeExplicitObjectSizeArgument(2); - IsTriggered = CompareSizeSourceToDest() < 0; - } else if (LibC == LibCDispatch::ReadLink) { - // readlink: - // ssize_t(const char *restrict, char buf[.bufsize], size_t bufsize); - // Up to bufsize(2) bytes are written into buf(1). - DiagID = diag::warn_fortify_source_size_mismatch; - SourceSize = ComputeExplicitObjectSizeArgument(2); - DestinationSize = ComputeSizeArgument(1); - IsTriggered = CompareSizeSourceToDest() > 0; - } else if (LibC == LibCDispatch::ReadLinkAt) { - // readlinkat: - // ssize_t(int, const char *restrict, char buf[.bufsize], size_t bufsize); - // Up to bufsize(3) bytes are written into buf(2). - DiagID = diag::warn_fortify_source_size_mismatch; - SourceSize = ComputeExplicitObjectSizeArgument(3); - DestinationSize = ComputeSizeArgument(2); - IsTriggered = CompareSizeSourceToDest() > 0; - } else if (LibC == LibCDispatch::GetCWD) { - // char *getcwd(char buf[.size], size_t size); - // Up to size(1) bytes are written into buf(0). - DiagID = diag::warn_fortify_source_size_mismatch; - SourceSize = ComputeExplicitObjectSizeArgument(1); - DestinationSize = ComputeSizeArgument(0); - IsTriggered = CompareSizeSourceToDest() > 0; - } else - switch (BuiltinID) { - default: - return; - case Builtin::BI__builtin_strcat: - case Builtin::BIstrcat: - case Builtin::BI__builtin_stpcpy: - case Builtin::BIstpcpy: - case Builtin::BI__builtin_strcpy: - case Builtin::BIstrcpy: { - DiagID = diag::warn_fortify_strlen_overflow; - SourceSize = ComputeStrLenArgument(1); - DestinationSize = ComputeSizeArgument(0); - IsTriggered = CompareSizeSourceToDest() > 0; - break; - } - - case Builtin::BI__builtin___strcat_chk: - case Builtin::BI__builtin___stpcpy_chk: - case Builtin::BI__builtin___strcpy_chk: { - DiagID = diag::warn_fortify_strlen_overflow; - SourceSize = ComputeStrLenArgument(1); - DestinationSize = ComputeExplicitObjectSizeArgument(2); - IsChkVariant = true; - IsTriggered = CompareSizeSourceToDest() > 0; - break; - } - - case Builtin::BIscanf: - case Builtin::BIfscanf: - case Builtin::BIsscanf: { - unsigned FormatIndex = 1; - unsigned DataIndex = 2; - if (BuiltinID == Builtin::BIscanf) { - FormatIndex = 0; - DataIndex = 1; + if (LibCMatch) { + // All recognized libc I/O functions feed at most one buffer slot (read- or + // write-direction) plus a constant-evaluated count slot into the fortify + // checks; the per-function descriptor records which argument indices play + // each role. + if (auto Idx = LibCMatch->SourceSizeIdx) + SourceSize = ComputeSizeArgument(*Idx); + if (auto Idx = LibCMatch->MaxOpSizeIdx) + MaxOperationSize = ComputeExplicitObjectSizeArgument(*Idx); + if (auto Idx = LibCMatch->DestSizeIdx) + DestinationSize = ComputeSizeArgument(*Idx); + } else { + switch (BuiltinID) { + default: + return; + case Builtin::BI__builtin_strcat: + case Builtin::BIstrcat: + case Builtin::BI__builtin_stpcpy: + case Builtin::BIstpcpy: + case Builtin::BI__builtin_strcpy: + case Builtin::BIstrcpy: { + DiagOverflowID = diag::warn_fortify_strlen_overflow; + MinOperationSize = ComputeStrLenArgument(1); + DestinationSize = ComputeSizeArgument(0); + break; } - const auto *FormatExpr = - TheCall->getArg(FormatIndex)->IgnoreParenImpCasts(); + case Builtin::BI__builtin___strcat_chk: + case Builtin::BI__builtin___stpcpy_chk: + case Builtin::BI__builtin___strcpy_chk: { + DiagOverflowID = diag::warn_fortify_strlen_overflow; + MinOperationSize = ComputeStrLenArgument(1); + DestinationSize = ComputeExplicitObjectSizeArgument(2); + IsChkVariant = true; + break; + } - StringRef FormatStrRef; - size_t StrLen; - if (!ProcessFormatStringLiteral(FormatExpr, FormatStrRef, StrLen, Context)) - return; + case Builtin::BIscanf: + case Builtin::BIfscanf: + case Builtin::BIsscanf: { + unsigned FormatIndex = 1; + unsigned DataIndex = 2; + if (BuiltinID == Builtin::BIscanf) { + FormatIndex = 0; + DataIndex = 1; + } - auto Diagnose = [&](unsigned ArgIndex, unsigned DestSize, - unsigned SourceSize) { - DiagID = diag::warn_fortify_scanf_overflow; - unsigned Index = ArgIndex + DataIndex; - std::string FunctionName = GetFunctionName(); - DiagRuntimeBehavior(TheCall->getArg(Index)->getBeginLoc(), TheCall, - PDiag(DiagID) << FunctionName << (Index + 1) - << DestSize << SourceSize); - }; + const auto *FormatExpr = + TheCall->getArg(FormatIndex)->IgnoreParenImpCasts(); - auto ShiftedComputeSizeArgument = [&](unsigned Index) { - return ComputeSizeArgument(Index + DataIndex); - }; - ScanfDiagnosticFormatHandler H(ShiftedComputeSizeArgument, Diagnose); - const char *FormatBytes = FormatStrRef.data(); - analyze_format_string::ParseScanfString(H, FormatBytes, - FormatBytes + StrLen, getLangOpts(), - Context.getTargetInfo()); - - // Unlike the other cases, in this one we have already issued the diagnostic - // here, so no need to continue (because unlike the other cases, here the - // diagnostic refers to the argument number). - return; - } + StringRef FormatStrRef; + size_t StrLen; + if (!ProcessFormatStringLiteral(FormatExpr, FormatStrRef, StrLen, + Context)) + return; - case Builtin::BIsprintf: - case Builtin::BI__builtin___sprintf_chk: { - size_t FormatIndex = BuiltinID == Builtin::BIsprintf ? 1 : 3; - auto *FormatExpr = TheCall->getArg(FormatIndex)->IgnoreParenImpCasts(); + auto Diagnose = [&](unsigned ArgIndex, unsigned DestSize, + unsigned SourceSize) { + unsigned Index = ArgIndex + DataIndex; + std::string FunctionName = GetFunctionName(); + DiagRuntimeBehavior(TheCall->getArg(Index)->getBeginLoc(), TheCall, + PDiag(diag::warn_fortify_scanf_overflow) + << FunctionName << (Index + 1) << DestSize + << SourceSize); + }; - StringRef FormatStrRef; - size_t StrLen; - if (ProcessFormatStringLiteral(FormatExpr, FormatStrRef, StrLen, Context)) { - EstimateSizeFormatHandler H(FormatStrRef); + auto ShiftedComputeSizeArgument = [&](unsigned Index) { + return ComputeSizeArgument(Index + DataIndex); + }; + ScanfDiagnosticFormatHandler H(ShiftedComputeSizeArgument, Diagnose); const char *FormatBytes = FormatStrRef.data(); - if (!analyze_format_string::ParsePrintfString( - H, FormatBytes, FormatBytes + StrLen, getLangOpts(), - Context.getTargetInfo(), false)) { - DiagID = H.isKernelCompatible() - ? diag::warn_format_overflow - : diag::warn_format_overflow_non_kprintf; - SourceSize = llvm::APSInt::getUnsigned(H.getSizeLowerBound()) - .extOrTrunc(SizeTypeWidth); - if (BuiltinID == Builtin::BI__builtin___sprintf_chk) { - DestinationSize = ComputeExplicitObjectSizeArgument(2); - IsChkVariant = true; - } else { - DestinationSize = ComputeSizeArgument(0); + analyze_format_string::ParseScanfString( + H, FormatBytes, FormatBytes + StrLen, getLangOpts(), + Context.getTargetInfo()); + + // Unlike the other cases, in this one we have already issued the + // diagnostic here, so no need to continue (because unlike the other + // cases, here the diagnostic refers to the argument number). + return; + } + + case Builtin::BIsprintf: + case Builtin::BI__builtin___sprintf_chk: { + size_t FormatIndex = BuiltinID == Builtin::BIsprintf ? 1 : 3; + auto *FormatExpr = TheCall->getArg(FormatIndex)->IgnoreParenImpCasts(); + + StringRef FormatStrRef; + size_t StrLen; + if (ProcessFormatStringLiteral(FormatExpr, FormatStrRef, StrLen, + Context)) { + EstimateSizeFormatHandler H(FormatStrRef); + const char *FormatBytes = FormatStrRef.data(); + if (!analyze_format_string::ParsePrintfString( + H, FormatBytes, FormatBytes + StrLen, getLangOpts(), + Context.getTargetInfo(), false)) { + DiagOverflowID = H.isKernelCompatible() + ? diag::warn_format_overflow + : diag::warn_format_overflow_non_kprintf; + MinOperationSize = llvm::APSInt::getUnsigned(H.getSizeLowerBound()) + .extOrTrunc(SizeTypeWidth); + if (BuiltinID == Builtin::BI__builtin___sprintf_chk) { + DestinationSize = ComputeExplicitObjectSizeArgument(2); + IsChkVariant = true; + } else { + DestinationSize = ComputeSizeArgument(0); + } + break; } - IsTriggered = CompareSizeSourceToDest() > 0; - break; } + return; } - return; - } - case Builtin::BI__builtin___memcpy_chk: - case Builtin::BI__builtin___memmove_chk: - case Builtin::BI__builtin___memset_chk: - case Builtin::BI__builtin___strlcat_chk: - case Builtin::BI__builtin___strlcpy_chk: - case Builtin::BI__builtin___strncat_chk: - case Builtin::BI__builtin___strncpy_chk: - case Builtin::BI__builtin___stpncpy_chk: - case Builtin::BI__builtin___memccpy_chk: - case Builtin::BI__builtin___mempcpy_chk: { - DiagID = diag::warn_builtin_chk_overflow; - SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 2); - DestinationSize = - ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); - IsChkVariant = true; - IsTriggered = CompareSizeSourceToDest() > 0; - break; - } + case Builtin::BI__builtin___memcpy_chk: + case Builtin::BI__builtin___memmove_chk: + case Builtin::BI__builtin___mempcpy_chk: + case Builtin::BI__builtin___memccpy_chk: { + // The source buffer is the second argument; the operation reads up to + // the user-supplied length from it. + DiagOverflowID = diag::warn_builtin_chk_overflow; + MinOperationSize = + ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 2); + SourceSize = ComputeSizeArgument(1); + DestinationSize = + ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); + IsChkVariant = true; + break; + } - case Builtin::BI__builtin___snprintf_chk: - case Builtin::BI__builtin___vsnprintf_chk: { - DiagID = diag::warn_builtin_chk_overflow; - SourceSize = ComputeExplicitObjectSizeArgument(1); - DestinationSize = ComputeExplicitObjectSizeArgument(3); - IsChkVariant = true; - IsTriggered = CompareSizeSourceToDest() > 0; - break; - } + case Builtin::BI__builtin___memset_chk: + case Builtin::BI__builtin___strlcat_chk: + case Builtin::BI__builtin___strlcpy_chk: + case Builtin::BI__builtin___strncat_chk: + case Builtin::BI__builtin___strncpy_chk: + case Builtin::BI__builtin___stpncpy_chk: { + DiagOverflowID = diag::warn_builtin_chk_overflow; + MinOperationSize = + ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 2); + DestinationSize = + ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); + IsChkVariant = true; + break; + } - case Builtin::BIstrncat: - case Builtin::BI__builtin_strncat: - case Builtin::BIstrncpy: - case Builtin::BI__builtin_strncpy: - case Builtin::BIstpncpy: - case Builtin::BI__builtin_stpncpy: { - // Whether these functions overflow depends on the runtime strlen of the - // string, not just the buffer size, so emitting the "always overflow" - // diagnostic isn't quite right. We should still diagnose passing a buffer - // size larger than the destination buffer though; this is a runtime abort - // in _FORTIFY_SOURCE mode, and is quite suspicious otherwise. - DiagID = diag::warn_fortify_source_size_mismatch; - SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); - DestinationSize = ComputeSizeArgument(0); - IsTriggered = CompareSizeSourceToDest() > 0; - break; - } + case Builtin::BI__builtin___snprintf_chk: + case Builtin::BI__builtin___vsnprintf_chk: { + DiagOverflowID = diag::warn_builtin_chk_overflow; + MinOperationSize = ComputeExplicitObjectSizeArgument(1); + DestinationSize = ComputeExplicitObjectSizeArgument(3); + IsChkVariant = true; + break; + } - case Builtin::BIbzero: - case Builtin::BI__builtin_bzero: - case Builtin::BImemcpy: - case Builtin::BI__builtin_memcpy: - case Builtin::BImemmove: - case Builtin::BI__builtin_memmove: - case Builtin::BImemset: - case Builtin::BI__builtin_memset: - case Builtin::BImempcpy: - case Builtin::BI__builtin_mempcpy: { - DiagID = diag::warn_fortify_source_overflow; - SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); - DestinationSize = ComputeSizeArgument(0); - IsTriggered = CompareSizeSourceToDest() > 0; - break; - } - case Builtin::BIbcopy: - case Builtin::BI__builtin_bcopy: { - DiagID = diag::warn_fortify_source_overflow; - SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); - DestinationSize = ComputeSizeArgument(1); - IsTriggered = CompareSizeSourceToDest() > 0; - break; - } + case Builtin::BIstrncat: + case Builtin::BI__builtin_strncat: + case Builtin::BIstrncpy: + case Builtin::BI__builtin_strncpy: + case Builtin::BIstpncpy: + case Builtin::BI__builtin_stpncpy: { + // Whether these functions overflow depends on the runtime strlen of the + // string, not just the buffer size, so emitting the "always overflow" + // diagnostic isn't quite right. We should still diagnose passing a buffer + // size larger than the destination buffer though; this is a runtime abort + // in _FORTIFY_SOURCE mode, and is quite suspicious otherwise. + MaxOperationSize = + ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); + DestinationSize = ComputeSizeArgument(0); + break; + } - case Builtin::BIsnprintf: - case Builtin::BI__builtin_snprintf: - case Builtin::BIvsnprintf: - case Builtin::BI__builtin_vsnprintf: { - DiagID = diag::warn_fortify_source_size_mismatch; - SourceSize = ComputeExplicitObjectSizeArgument(1); - const auto *FormatExpr = TheCall->getArg(2)->IgnoreParenImpCasts(); - StringRef FormatStrRef; - size_t StrLen; - if (SourceSize && - ProcessFormatStringLiteral(FormatExpr, FormatStrRef, StrLen, Context)) { - EstimateSizeFormatHandler H(FormatStrRef); - const char *FormatBytes = FormatStrRef.data(); - if (!analyze_format_string::ParsePrintfString( - H, FormatBytes, FormatBytes + StrLen, getLangOpts(), - Context.getTargetInfo(), /*isFreeBSDKPrintf=*/false)) { - llvm::APSInt FormatSize = - llvm::APSInt::getUnsigned(H.getSizeLowerBound()) - .extOrTrunc(SizeTypeWidth); - if (FormatSize > *SourceSize && *SourceSize != 0) { - unsigned TruncationDiagID = - H.isKernelCompatible() ? diag::warn_format_truncation - : diag::warn_format_truncation_non_kprintf; - SmallString<16> SpecifiedSizeStr; - SmallString<16> FormatSizeStr; - SourceSize->toString(SpecifiedSizeStr, /*Radix=*/10); - FormatSize.toString(FormatSizeStr, /*Radix=*/10); - DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, - PDiag(TruncationDiagID) - << GetFunctionName() << SpecifiedSizeStr - << FormatSizeStr); + case Builtin::BImemset: + case Builtin::BI__builtin_memset: + case Builtin::BIbzero: + case Builtin::BI__builtin_bzero: { + MinOperationSize = MaxOperationSize = + ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); + DestinationSize = ComputeSizeArgument(0); + break; + } + + case Builtin::BImemcpy: + case Builtin::BI__builtin_memcpy: + case Builtin::BImemmove: + case Builtin::BI__builtin_memmove: + case Builtin::BImempcpy: + case Builtin::BI__builtin_mempcpy: { + MinOperationSize = MaxOperationSize = + ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); + DestinationSize = ComputeSizeArgument(0); + SourceSize = ComputeSizeArgument(1); + break; + } + case Builtin::BIbcopy: + case Builtin::BI__builtin_bcopy: { + MinOperationSize = MaxOperationSize = + ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); + SourceSize = ComputeSizeArgument(0); + DestinationSize = ComputeSizeArgument(1); + break; + } + + case Builtin::BIsnprintf: + case Builtin::BI__builtin_snprintf: + case Builtin::BIvsnprintf: + case Builtin::BI__builtin_vsnprintf: { + MaxOperationSize = ComputeExplicitObjectSizeArgument(1); + const auto *FormatExpr = TheCall->getArg(2)->IgnoreParenImpCasts(); + StringRef FormatStrRef; + size_t StrLen; + if (MaxOperationSize && ProcessFormatStringLiteral( + FormatExpr, FormatStrRef, StrLen, Context)) { + EstimateSizeFormatHandler H(FormatStrRef); + const char *FormatBytes = FormatStrRef.data(); + if (!analyze_format_string::ParsePrintfString( + H, FormatBytes, FormatBytes + StrLen, getLangOpts(), + Context.getTargetInfo(), /*isFreeBSDKPrintf=*/false)) { + llvm::APSInt FormatSize = + llvm::APSInt::getUnsigned(H.getSizeLowerBound()) + .extOrTrunc(SizeTypeWidth); + if (FormatSize > *MaxOperationSize && *MaxOperationSize != 0) { + unsigned TruncationDiagID = + H.isKernelCompatible() + ? diag::warn_format_truncation + : diag::warn_format_truncation_non_kprintf; + SmallString<16> SpecifiedSizeStr; + SmallString<16> FormatSizeStr; + MaxOperationSize->toString(SpecifiedSizeStr, /*Radix=*/10); + FormatSize.toString(FormatSizeStr, /*Radix=*/10); + DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, + PDiag(TruncationDiagID) + << GetFunctionName() << SpecifiedSizeStr + << FormatSizeStr); + } } } + DestinationSize = ComputeSizeArgument(0); + const Expr *LenArg = TheCall->getArg(1)->IgnoreCasts(); + const Expr *Dest = TheCall->getArg(0)->IgnoreCasts(); + IdentifierInfo *FnInfo = FD->getIdentifier(); + CheckSizeofMemaccessArgument(LenArg, Dest, FnInfo); + break; + } } - DestinationSize = ComputeSizeArgument(0); - const Expr *LenArg = TheCall->getArg(1)->IgnoreCasts(); - const Expr *Dest = TheCall->getArg(0)->IgnoreCasts(); - IdentifierInfo *FnInfo = FD->getIdentifier(); - CheckSizeofMemaccessArgument(LenArg, Dest, FnInfo); - IsTriggered = CompareSizeSourceToDest() > 0; - break; - } } - if (!IsTriggered) - return; - std::string FunctionName = GetFunctionName(); + SmallString<16> MaxOpStr; + SmallString<16> MinOpStr; + + if (MinOperationSize) + MinOperationSize->toString(MinOpStr, /*Radix=*/10); + if (MaxOperationSize) + MaxOperationSize->toString(MaxOpStr, /*Radix=*/10); + + if (DestinationSize) { + SmallString<16> DestinationStr; + DestinationSize->toString(DestinationStr, /*Radix=*/10); + // Check for definite overflow + if (MinOperationSize && + llvm::APSInt::compareValues(*MinOperationSize, *DestinationSize) > 0) { + DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, + PDiag(DiagOverflowID) + << FunctionName << DestinationStr << MinOpStr); + } + // Check for possible overflow + else if (MaxOperationSize && llvm::APSInt::compareValues( + *MaxOperationSize, *DestinationSize) > 0) { + DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, + PDiag(DiagMayOverflowID) + << FunctionName << DestinationStr << MaxOpStr); + } + } - SmallString<16> DestinationStr; - SmallString<16> SourceStr; - DestinationSize->toString(DestinationStr, /*Radix=*/10); - SourceSize->toString(SourceStr, /*Radix=*/10); - DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, - PDiag(DiagID) - << FunctionName << DestinationStr << SourceStr); + if (SourceSize) { + SmallString<16> SourceStr; + SourceSize->toString(SourceStr, /*Radix=*/10); + // Check for definite over-read + if (MinOperationSize && + llvm::APSInt::compareValues(*MinOperationSize, *SourceSize) > 0) { + DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, + PDiag(DiagOverReadID) + << FunctionName << SourceStr << MinOpStr); + + } + // Check for possible over-read + else if (MaxOperationSize && + llvm::APSInt::compareValues(*MaxOperationSize, *SourceSize) > 0) { + DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, + PDiag(DiagMayOverReadID) + << FunctionName << SourceStr << MaxOpStr); + } + } } static bool BuiltinSEHScopeCheck(Sema &SemaRef, CallExpr *TheCall, diff --git a/clang/test/Sema/builtin-memcpy.c b/clang/test/Sema/builtin-memcpy.c index 2a55e78034a02..af7f2034f3c30 100644 --- a/clang/test/Sema/builtin-memcpy.c +++ b/clang/test/Sema/builtin-memcpy.c @@ -7,7 +7,8 @@ /// Zero-sized structs should not crash. int b() { struct { } a[10]; - __builtin_memcpy(&a[2], a, 2); // c-warning {{buffer has size 0, but size argument is 2}} + __builtin_memcpy(&a[2], a, 2); // c-warning {{buffer has size 0, but size argument is 2}} \ + // c-warning {{buffer has size 0, but size argument is 2}} return 0; } diff --git a/clang/test/Sema/warn-fortify-source-prototype-gate.c b/clang/test/Sema/warn-fortify-source-prototype-gate.c index 2112af03e1734..575162dfb8664 100644 --- a/clang/test/Sema/warn-fortify-source-prototype-gate.c +++ b/clang/test/Sema/warn-fortify-source-prototype-gate.c @@ -44,19 +44,20 @@ void test_getcwd_wrong_param(void) { getcwd(42, 8); } -// pread64 with int offset on x86_64: off64_t is required to be 64-bit signed, -// so int (32-bit on x86_64) is not POSIX pread64. -ssize_t pread64(int, void *, size_t, int); +// pread64 with 'char *' buffer instead of POSIX 'void *'. Newlib-style +// syscall stubs commonly use this shape; the pointer-type mismatch trips +// the prototype gate. +int pread64(int, char *, size_t, long long); -void test_pread64_narrow_offset(void) { +void test_pread64_newlib_buffer(void) { char b[4]; pread64(0, b, 8, 0); } -// pwrite64 with int offset: same as above. -ssize_t pwrite64(int, const void *, size_t, int); +// pwrite64 with 'const char *' instead of POSIX 'const void *': same shape. +int pwrite64(int, const char *, size_t, long long); -void test_pwrite64_narrow_offset(void) { +void test_pwrite64_newlib_buffer(void) { char b[4]; pwrite64(0, b, 8, 0); } diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c index 5c4df36d2c88d..14ffd163c5d09 100644 --- a/clang/test/Sema/warn-fortify-source.c +++ b/clang/test/Sema/warn-fortify-source.c @@ -45,6 +45,8 @@ void call_memcpy(void) { char dst[10]; char src[20]; memcpy(dst, src, 20); // expected-warning {{memcpy' will always overflow; destination buffer has size 10, but size argument is 20}} + memcpy(dst, src, 21); // expected-warning {{memcpy' will always overflow; destination buffer has size 10, but size argument is 21}} + // expected-warning@-1 {{memcpy' will always over-read; source buffer has size 20, but size argument is 21}} if (sizeof(dst) == sizeof(src)) memcpy(dst, src, 20); // no warning, unreachable @@ -58,18 +60,33 @@ void call_memcpy_type(void) { struct pair p; char buf[20]; memcpy(&p.first, buf, 20); // expected-warning {{memcpy' will always overflow; destination buffer has size 8, but size argument is 20}} + memcpy(&p.first, buf, 21); // expected-warning {{memcpy' will always overflow; destination buffer has size 8, but size argument is 21}} + // expected-warning@-1 {{memcpy' will always over-read; source buffer has size 20, but size argument is 21}} +} + +void call_memcpy_chk(void) { + char dst[10]; + char src[10]; + char src4[4]; + __builtin___memcpy_chk(dst, src, 10, 10); + __builtin___memcpy_chk(dst, src, 10, 9); // expected-warning {{memcpy' will always overflow; destination buffer has size 9, but size argument is 10}} + __builtin___memcpy_chk(dst, src4, 5, 10); // expected-warning {{'memcpy' will always over-read; source buffer has size 4, but size argument is 5}} + __builtin___memmove_chk(dst, src4, 5, 10); // expected-warning {{'memmove' will always over-read; source buffer has size 4, but size argument is 5}} + __builtin___mempcpy_chk(dst, src4, 5, 10); // expected-warning {{'mempcpy' will always over-read; source buffer has size 4, but size argument is 5}} } void call_strncat(void) { char s1[10], s2[20]; __builtin_strncat(s2, s1, 20); __builtin_strncat(s1, s2, 20); // expected-warning {{'strncat' size argument is too large; destination buffer has size 10, but size argument is 20}} + __builtin_strncat(s1, "abcd", 20); // expected-warning {{'strncat' size argument is too large; destination buffer has size 10, but size argument is 20}} } void call_strncpy(void) { char s1[10], s2[20]; __builtin_strncpy(s2, s1, 20); __builtin_strncpy(s1, s2, 20); // expected-warning {{'strncpy' size argument is too large; destination buffer has size 10, but size argument is 20}} + __builtin_strncpy(s1, "abcd", 20); // expected-warning {{'strncpy' size argument is too large; destination buffer has size 10, but size argument is 20}} } void call_stpncpy(void) { @@ -107,9 +124,17 @@ void call_stpcpy(void) { __builtin_stpcpy(dst2, src); // expected-warning {{'stpcpy' will always overflow; destination buffer has size 4, but the source string has length 5 (including NUL byte)}} } +void call_stpcpy_chk(void) { + const char *const src = "abcd"; + char dst1[5]; + __builtin___stpcpy_chk(dst1, src, 5); + __builtin___stpcpy_chk(dst1, src, 4); // expected-warning {{'stpcpy' will always overflow; destination buffer has size 4, but the source string has length 5 (including NUL byte)}} +} + void call_memmove(void) { char s1[10], s2[20]; - __builtin_memmove(s2, s1, 20); + __builtin_memmove(s2, s1, 10); + __builtin_memmove(s2, s1, 20); // expected-warning {{'memmove' will always over-read; source buffer has size 10, but size argument is 20}} __builtin_memmove(s1, s2, 20); // expected-warning {{'memmove' will always overflow; destination buffer has size 10, but size argument is 20}} } @@ -323,11 +348,23 @@ template <int A, int B> void call_memcpy_dep() { char bufferA[A]; char bufferB[B]; - memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always overflow; destination buffer has size 9, but size argument is 10}} + if (sizeof(bufferA) < 10 && sizeof(bufferB) < 10) { + memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always overflow; destination buffer has size 9, but size argument is 10}} \ + // expected-warning{{'memcpy' will always over-read; source buffer has size 9, but size argument is 10}} + } else if (sizeof(bufferA) < 10) { + memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always overflow; destination buffer has size 9, but size argument is 10}} + } else if (sizeof(bufferB) < 10) { + memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always over-read; source buffer has size 9, but size argument is 10}} + } else { + memcpy(bufferA, bufferB, 10); + } + } void call_call_memcpy() { - call_memcpy_dep<10, 9>(); + call_memcpy_dep<10, 10>(); + call_memcpy_dep<10, 9>(); // expected-note {{in instantiation of function template specialization 'call_memcpy_dep<10, 9>' requested here}} call_memcpy_dep<9, 10>(); // expected-note {{in instantiation of function template specialization 'call_memcpy_dep<9, 10>' requested here}} + call_memcpy_dep<9, 9>(); // expected-note {{in instantiation of function template specialization 'call_memcpy_dep<9, 9>' requested here}} } #endif >From f5c9663ecc5da8018d25c6cb6dbf9c6331d6cfe4 Mon Sep 17 00:00:00 2001 From: Colin Kinloch <[email protected]> Date: Thu, 6 Nov 2025 00:00:53 +0000 Subject: [PATCH 3/8] [clang][Sema] Use relative line number for template warnings --- clang/test/Sema/warn-fortify-source.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c index 14ffd163c5d09..9137bf433073b 100644 --- a/clang/test/Sema/warn-fortify-source.c +++ b/clang/test/Sema/warn-fortify-source.c @@ -348,17 +348,15 @@ template <int A, int B> void call_memcpy_dep() { char bufferA[A]; char bufferB[B]; + memcpy(bufferA, bufferB, 10); if (sizeof(bufferA) < 10 && sizeof(bufferB) < 10) { - memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always overflow; destination buffer has size 9, but size argument is 10}} \ - // expected-warning{{'memcpy' will always over-read; source buffer has size 9, but size argument is 10}} + // expected-warning@-2{{'memcpy' will always overflow; destination buffer has size 9, but size argument is 10}} + // expected-warning@-3{{'memcpy' will always over-read; source buffer has size 9, but size argument is 10}} } else if (sizeof(bufferA) < 10) { - memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always overflow; destination buffer has size 9, but size argument is 10}} + // expected-warning@-5{{'memcpy' will always overflow; destination buffer has size 9, but size argument is 10}} } else if (sizeof(bufferB) < 10) { - memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always over-read; source buffer has size 9, but size argument is 10}} - } else { - memcpy(bufferA, bufferB, 10); + // expected-warning@-7{{'memcpy' will always over-read; source buffer has size 9, but size argument is 10}} } - } void call_call_memcpy() { >From 2fa23a9f4019b1e3a2e35e3b605e5edc788b204c Mon Sep 17 00:00:00 2001 From: Colin Kinloch <[email protected]> Date: Thu, 6 Nov 2025 01:58:37 +0000 Subject: [PATCH 4/8] [clang][test] Add over-read warnings to analysis tests --- clang/test/Analysis/array-struct.c | 4 ++-- clang/test/Analysis/bstring.c | 19 ++++++++++++++++++- clang/test/Analysis/malloc.c | 2 +- clang/test/Analysis/pr22954.c | 1 + clang/test/Sema/builtin-object-size.c | 2 +- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/clang/test/Analysis/array-struct.c b/clang/test/Analysis/array-struct.c index f0eba86fe71bf..691bb1348a44f 100644 --- a/clang/test/Analysis/array-struct.c +++ b/clang/test/Analysis/array-struct.c @@ -175,12 +175,12 @@ void f17(void) { x = 1; } -void read(char*); +void readp(char*); void f18(void) { char *q; char *p = (char *) __builtin_alloca(10); - read(p); + readp(p); q = p; q++; if (*q) { // no-warning diff --git a/clang/test/Analysis/bstring.c b/clang/test/Analysis/bstring.c index 810241accffa2..f940cd957457c 100644 --- a/clang/test/Analysis/bstring.c +++ b/clang/test/Analysis/bstring.c @@ -93,6 +93,9 @@ void memcpy1 (void) { char dst[10]; memcpy(dst, src, 5); // expected-warning{{Memory copy function accesses out-of-bound array element}} +#if !defined(VARIANT) || defined(USE_BUILTINS) + // expected-warning@-2{{memcpy' will always over-read; source buffer has size 4, but size argument is 5}} +#endif } void memcpy2 (void) { @@ -117,6 +120,9 @@ void memcpy4 (void) { char dst[10]; memcpy(dst+2, src+2, 3); // expected-warning{{Memory copy function accesses out-of-bound array element}} +#if !defined(VARIANT) || defined(USE_BUILTINS) + // expected-warning@-2{{memcpy' will always over-read; source buffer has size 2, but size argument is 3}} +#endif } void memcpy5(void) { @@ -219,6 +225,9 @@ void mempcpy1 (void) { char dst[10]; mempcpy(dst, src, 5); // expected-warning{{Memory copy function accesses out-of-bound array element}} +#if !defined(VARIANT) || defined(USE_BUILTINS) + // expected-warning@-2{{mempcpy' will always over-read; source buffer has size 4, but size argument is 5}} +#endif } void mempcpy2 (void) { @@ -243,6 +252,9 @@ void mempcpy4 (void) { char dst[10]; mempcpy(dst+2, src+2, 3); // expected-warning{{Memory copy function accesses out-of-bound array element}} +#if !defined(VARIANT) || defined(USE_BUILTINS) + // expected-warning@-2{{mempcpy' will always over-read; source buffer has size 2, but size argument is 3}} +#endif } void mempcpy5(void) { @@ -384,6 +396,9 @@ void memmove1 (void) { char dst[10]; memmove(dst, src, 5); // expected-warning{{out-of-bound}} +#if !defined(VARIANT) || defined(USE_BUILTINS) + // expected-warning@-2{{memmove' will always over-read; source buffer has size 4, but size argument is 5}} +#endif } void memmove2 (void) { @@ -502,6 +517,7 @@ void bcopy1 (void) { char dst[10]; bcopy(src, dst, 5); // expected-warning{{out-of-bound}} + // expected-warning@-1{{bcopy' will always over-read; source buffer has size 4, but size argument is 5}} } void bcopy2 (void) { @@ -541,6 +557,7 @@ void nocrash_on_empty_struct_memcpy(void) { __builtin_memcpy(&a[2], a, 2); // no-crash #if !defined(_WIN32) || defined(__MINGW32__) // expected-warning@-2 {{'memcpy' will always overflow; destination buffer has size 0, but size argument is 2}} - // expected-warning@-3 {{Memory copy function overflows the destination buffer}} + // expected-warning@-3 {{'memcpy' will always over-read; source buffer has size 0, but size argument is 2}} + // expected-warning@-4 {{Memory copy function overflows the destination buffer}} #endif } diff --git a/clang/test/Analysis/malloc.c b/clang/test/Analysis/malloc.c index 849ab3a3a0f37..199deda3db877 100644 --- a/clang/test/Analysis/malloc.c +++ b/clang/test/Analysis/malloc.c @@ -890,7 +890,7 @@ void overlappingMemcpyDoesNotSinkPath(char *s) { // Treat source buffer contents as escaped. void escapeSourceContents(char *s) { char *p = malloc(12); - memcpy(s, &p, 12); // no warning + memcpy(s, &p, 12); // expected-warning{{memcpy' will always over-read; source buffer has size}} void *p1 = malloc(7); char *a; diff --git a/clang/test/Analysis/pr22954.c b/clang/test/Analysis/pr22954.c index 3d1cac1972066..629c293c81784 100644 --- a/clang/test/Analysis/pr22954.c +++ b/clang/test/Analysis/pr22954.c @@ -537,6 +537,7 @@ int f262(void) { a262.s2 = strdup("hello"); char input[] = {'a', 'b', 'c', 'd'}; memcpy(a262.s1, input, -1); // expected-warning{{'memcpy' will always overflow; destination buffer has size 16, but size argument is 18446744073709551615}} + // expected-warning@-1{{'memcpy' will always over-read; source buffer has size 4, but size argument is 18446744073709551615}} clang_analyzer_eval(a262.s1[0] == 1); // expected-warning{{UNKNOWN}}\ expected-warning{{Potential leak of memory pointed to by 'a262.s2'}} clang_analyzer_eval(a262.s1[1] == 1); // expected-warning{{UNKNOWN}} diff --git a/clang/test/Sema/builtin-object-size.c b/clang/test/Sema/builtin-object-size.c index a763c24fd6620..b8a8407b54b3b 100644 --- a/clang/test/Sema/builtin-object-size.c +++ b/clang/test/Sema/builtin-object-size.c @@ -50,7 +50,7 @@ void f5(void) { char buf[10]; memset((void *)0x100000000ULL, 0, 0x1000); - memcpy((char *)NULL + 0x10000, buf, 0x10); + memcpy((char *)NULL + 0x10000, buf, 0x10); // expected-warning {{'memcpy' will always over-read; source buffer has size 10, but size argument is 16}} memcpy1((char *)NULL + 0x10000, buf, 0x10); // expected-error {{argument value 4 is outside the valid range [0, 3]}} } >From 9d2bbd7ff3e27e9bbe1fa8f4e362a0403e7ecde3 Mon Sep 17 00:00:00 2001 From: Colin Kinloch <[email protected]> Date: Thu, 6 Nov 2025 01:58:37 +0000 Subject: [PATCH 5/8] [test] Adjust libcxx and asan tests for new fortify warnings Two tests outside clang/test/Analysis need adjustment after introducing fortify warnings for unistd.h I/O: * The Windows asan EH-codegen test now triggers new fortify warnings on non-MSVC builds; pass -Wno-fortify-source there (MSVC mode is unaffected). * The libcxx __constexpr_wmemchr test passed an int character constant to a wchar_t parameter; use L'n' so the literal type matches the parameter. --- compiler-rt/test/asan/TestCases/Windows/issue64990.cpp | 2 +- .../libcxx/strings/c.strings/constexpr.cwchar.compile.pass.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler-rt/test/asan/TestCases/Windows/issue64990.cpp b/compiler-rt/test/asan/TestCases/Windows/issue64990.cpp index 5222ec6e08191..785e027a31be8 100644 --- a/compiler-rt/test/asan/TestCases/Windows/issue64990.cpp +++ b/compiler-rt/test/asan/TestCases/Windows/issue64990.cpp @@ -1,5 +1,5 @@ // Repro for the issue #64990: Asan with Windows EH generates __asan_xxx runtime calls without required funclet tokens -// RUN: %clang_cl_asan %Od %if MSVC %{ /Oi %} %s -EHsc %Fe%t +// RUN: %clang_cl_asan %Od %if MSVC %{ /Oi %} %else %{ -Wno-fortify-source %} %s -EHsc %Fe%t // RUN: not %run %t 2>&1 | FileCheck %s // UNSUPPORTED: target={{.*-windows-gnu}} diff --git a/libcxx/test/libcxx/strings/c.strings/constexpr.cwchar.compile.pass.cpp b/libcxx/test/libcxx/strings/c.strings/constexpr.cwchar.compile.pass.cpp index 02feed064eacc..7d9548e500da8 100644 --- a/libcxx/test/libcxx/strings/c.strings/constexpr.cwchar.compile.pass.cpp +++ b/libcxx/test/libcxx/strings/c.strings/constexpr.cwchar.compile.pass.cpp @@ -21,6 +21,6 @@ static_assert(std::__constexpr_wmemcmp(L"Banane", L"Bananf", 6) == -1, ""); constexpr bool test_constexpr_wmemchr() { const wchar_t str[] = L"Banane"; - return std::__constexpr_wmemchr(str, 'n', 6) == str + 2; + return std::__constexpr_wmemchr(str, L'n', 6) == str + 2; } static_assert(test_constexpr_wmemchr(), ""); >From b8cfedf708d6770a68608c526cb4f3a91df739fe Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko <[email protected]> Date: Tue, 5 May 2026 15:11:53 +0300 Subject: [PATCH 6/8] [clang][analyzer] Check SSIZE_MAX bounds for unistd I/O sizes Add StdLibraryFunctionsChecker argument constraints that reject size arguments greater than SSIZE_MAX for read, write, readlink, and readlinkat. This catches common problematic cases such as passing -1 to a size_t parameter, using very large constants, or relying on a size that is valid on one platform but exceeds SSIZE_MAX on another. This may produce new warnings for existing code that passes size arguments larger than SSIZE_MAX to these functions. Use the visible ssize_t type to derive the platform-specific maximum. For readlink and readlinkat, tighten the existing bufsize constraint from the full size_t range to SSIZE_MAX. --- .../Checkers/StdLibraryFunctionsChecker.cpp | 17 ++++++----- .../std-c-library-functions-arg-constraints.c | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index 8a3ee4443eb16..666caf31e4610 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -2073,12 +2073,17 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( std::optional<QualType> Ssize_tTy = lookupTy("ssize_t"); std::optional<RangeInt> Ssize_tMax = getMaxValue(Ssize_tTy); + auto ValidSsize_tSize = [&](ArgNo ArgN) { + return ArgumentCondition(ArgN, WithinRange, Range(0, Ssize_tMax), + "a value not greater than SSIZE_MAX"); + }; auto ReadSummary = Summary(NoEvalCall) .Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)), ReturnValueCondition(WithinRange, Range(-1, Ssize_tMax))}, - ErrnoIrrelevant); + ErrnoIrrelevant) + .ArgConstraint(ValidSsize_tSize(ArgNo(2))); // FIXME these are actually defined by POSIX and not by the C standard, we // should handle them together with the rest of the POSIX functions. @@ -3012,7 +3017,7 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( ArgTypes{ConstCharPtrRestrictTy, CharPtrRestrictTy, SizeTyCanonTy}, RetType{Ssize_tTy}), Summary(NoEvalCall) - .Case({ArgumentCondition(2, WithinRange, Range(1, IntMax)), + .Case({ArgumentCondition(2, WithinRange, Range(1, Ssize_tMax)), ReturnValueCondition(LessThanOrEq, ArgNo(2)), ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, ErrnoMustNotBeChecked, GenericSuccessMsg) @@ -3025,8 +3030,7 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .ArgConstraint(NotNull(ArgNo(1))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(1), /*BufSize=*/ArgNo(2))) - .ArgConstraint( - ArgumentCondition(2, WithinRange, Range(0, SizeMax)))); + .ArgConstraint(ValidSsize_tSize(ArgNo(2)))); // ssize_t readlinkat(int fd, const char *restrict path, // char *restrict buf, size_t bufsize); @@ -3036,7 +3040,7 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( SizeTyCanonTy}, RetType{Ssize_tTy}), Summary(NoEvalCall) - .Case({ArgumentCondition(3, WithinRange, Range(1, IntMax)), + .Case({ArgumentCondition(3, WithinRange, Range(1, Ssize_tMax)), ReturnValueCondition(LessThanOrEq, ArgNo(3)), ReturnValueCondition(WithinRange, Range(1, Ssize_tMax))}, ErrnoMustNotBeChecked, GenericSuccessMsg) @@ -3050,8 +3054,7 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .ArgConstraint(NotNull(ArgNo(2))) .ArgConstraint(BufferSize(/*Buffer=*/ArgNo(2), /*BufSize=*/ArgNo(3))) - .ArgConstraint( - ArgumentCondition(3, WithinRange, Range(0, SizeMax)))); + .ArgConstraint(ValidSsize_tSize(ArgNo(3)))); // int renameat(int olddirfd, const char *oldpath, int newdirfd, const char // *newpath); diff --git a/clang/test/Analysis/std-c-library-functions-arg-constraints.c b/clang/test/Analysis/std-c-library-functions-arg-constraints.c index 0b817dda98c72..c04d7921afb9f 100644 --- a/clang/test/Analysis/std-c-library-functions-arg-constraints.c +++ b/clang/test/Analysis/std-c-library-functions-arg-constraints.c @@ -370,3 +370,33 @@ void test_file_fd_at_functions() { (void)readlinkat(AT_FDCWD, "newpath", Buf, 10); (void)renameat(AT_FDCWD, "oldpath", AT_FDCWD, "newpath"); } + +#define SSIZE_MAX_PLUS_ONE ((size_t)1 << (sizeof(size_t) * __CHAR_BIT__ - 1)) + +void test_read_ssize_max_io_size(int fd, char *Buf) { + read(fd, Buf, SSIZE_MAX_PLUS_ONE); + // report-warning@-1{{The 3rd argument to 'read' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-warning@-2{{The 3rd argument to 'read' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-note@-3{{The 3rd argument to 'read' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} +} + +void test_write_ssize_max_io_size(int fd, char *Buf) { + write(fd, Buf, SSIZE_MAX_PLUS_ONE); + // report-warning@-1{{The 3rd argument to 'write' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-warning@-2{{The 3rd argument to 'write' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-note@-3{{The 3rd argument to 'write' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} +} + +void test_readlink_ssize_max_io_size(char *Buf) { + readlink("path", Buf, SSIZE_MAX_PLUS_ONE); + // report-warning@-1{{The 3rd argument to 'readlink' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-warning@-2{{The 3rd argument to 'readlink' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-note@-3{{The 3rd argument to 'readlink' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} +} + +void test_readlinkat_ssize_max_io_size(char *Buf) { + readlinkat(AT_FDCWD, "path", Buf, SSIZE_MAX_PLUS_ONE); + // report-warning@-1{{The 4th argument to 'readlinkat' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-warning@-2{{The 4th argument to 'readlinkat' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-note@-3{{The 4th argument to 'readlinkat' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} +} >From 0465c9241cb351c36c45b04c16730bfb5b01f590 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko <[email protected]> Date: Tue, 5 May 2026 15:14:48 +0300 Subject: [PATCH 7/8] [clang][analyzer] Add summaries for pread and pwrite Add StdLibraryFunctionsChecker summaries for pread, pread64, pwrite, and pwrite64. Reuse the existing read/write summary so the new modeled functions inherit the SSIZE_MAX size constraint and return-value bounds. Add POSIX test declarations, loaded-summary checks, and diagnostic coverage for the new summaries. Signed-off-by: Denys Fedoryshchenko <[email protected]> --- .../Checkers/StdLibraryFunctionsChecker.cpp | 29 +++++++++++++++++++ .../Inputs/std-c-library-functions-POSIX.h | 4 +++ .../Analysis/std-c-library-functions-POSIX.c | 4 +++ .../std-c-library-functions-arg-constraints.c | 28 ++++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index 666caf31e4610..b579255892084 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -3009,6 +3009,35 @@ void StdLibraryFunctionsChecker::initFunctionSummaries( .ArgConstraint( ArgumentCondition(0, WithinRange, Range(0, IntMax)))); + // ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset); + addToFunctionSummaryMap( + "pread", + Signature(ArgTypes{IntTy, VoidPtrTy, SizeTyCanonTy, Off_tTy}, + RetType{Ssize_tTy}), + ReadSummary); + + // ssize_t pread64(int fildes, void *buf, size_t nbyte, off64_t offset); + addToFunctionSummaryMap( + "pread64", + Signature(ArgTypes{IntTy, VoidPtrTy, SizeTyCanonTy, Off64_tTy}, + RetType{Ssize_tTy}), + ReadSummary); + + // ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset); + addToFunctionSummaryMap( + "pwrite", + Signature(ArgTypes{IntTy, ConstVoidPtrTy, SizeTyCanonTy, Off_tTy}, + RetType{Ssize_tTy}), + ReadSummary); + + // ssize_t pwrite64(int fildes, const void *buf, size_t nbyte, + // off64_t offset); + addToFunctionSummaryMap( + "pwrite64", + Signature(ArgTypes{IntTy, ConstVoidPtrTy, SizeTyCanonTy, Off64_tTy}, + RetType{Ssize_tTy}), + ReadSummary); + // ssize_t readlink(const char *restrict path, char *restrict buf, // size_t bufsize); addToFunctionSummaryMap( diff --git a/clang/test/Analysis/Inputs/std-c-library-functions-POSIX.h b/clang/test/Analysis/Inputs/std-c-library-functions-POSIX.h index b146068eedb08..83d753a22b347 100644 --- a/clang/test/Analysis/Inputs/std-c-library-functions-POSIX.h +++ b/clang/test/Analysis/Inputs/std-c-library-functions-POSIX.h @@ -124,6 +124,10 @@ void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) void *mmap64(void *addr, size_t length, int prot, int flags, int fd, off64_t offset); int pipe(int fildes[2]); off_t lseek(int fildes, off_t offset, int whence); +ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset); +ssize_t pread64(int fildes, void *buf, size_t nbyte, off64_t offset); +ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset); +ssize_t pwrite64(int fildes, const void *buf, size_t nbyte, off64_t offset); ssize_t readlink(const char *restrict path, char *restrict buf, size_t bufsize); ssize_t readlinkat(int fd, const char *restrict path, char *restrict buf, size_t bufsize); int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); diff --git a/clang/test/Analysis/std-c-library-functions-POSIX.c b/clang/test/Analysis/std-c-library-functions-POSIX.c index f6d88e6c1502d..619a049d79055 100644 --- a/clang/test/Analysis/std-c-library-functions-POSIX.c +++ b/clang/test/Analysis/std-c-library-functions-POSIX.c @@ -98,6 +98,10 @@ // CHECK: Loaded summary for: void *mmap64(void *addr, size_t length, int prot, int flags, int fd, off64_t offset) // CHECK: Loaded summary for: int pipe(int fildes[2]) // CHECK: Loaded summary for: off_t lseek(int fildes, off_t offset, int whence) +// CHECK: Loaded summary for: ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset) +// CHECK: Loaded summary for: ssize_t pread64(int fildes, void *buf, size_t nbyte, off64_t offset) +// CHECK: Loaded summary for: ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset) +// CHECK: Loaded summary for: ssize_t pwrite64(int fildes, const void *buf, size_t nbyte, off64_t offset) // CHECK: Loaded summary for: ssize_t readlink(const char *restrict path, char *restrict buf, size_t bufsize) // CHECK: Loaded summary for: ssize_t readlinkat(int fd, const char *restrict path, char *restrict buf, size_t bufsize) // CHECK: Loaded summary for: int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) diff --git a/clang/test/Analysis/std-c-library-functions-arg-constraints.c b/clang/test/Analysis/std-c-library-functions-arg-constraints.c index c04d7921afb9f..1247b4c43e589 100644 --- a/clang/test/Analysis/std-c-library-functions-arg-constraints.c +++ b/clang/test/Analysis/std-c-library-functions-arg-constraints.c @@ -387,6 +387,34 @@ void test_write_ssize_max_io_size(int fd, char *Buf) { // bugpath-note@-3{{The 3rd argument to 'write' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} } +void test_pread_ssize_max_io_size(int fd, char *Buf) { + pread(fd, Buf, SSIZE_MAX_PLUS_ONE, 0); + // report-warning@-1{{The 3rd argument to 'pread' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-warning@-2{{The 3rd argument to 'pread' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-note@-3{{The 3rd argument to 'pread' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} +} + +void test_pread64_ssize_max_io_size(int fd, char *Buf) { + pread64(fd, Buf, SSIZE_MAX_PLUS_ONE, 0); + // report-warning@-1{{The 3rd argument to 'pread64' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-warning@-2{{The 3rd argument to 'pread64' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-note@-3{{The 3rd argument to 'pread64' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} +} + +void test_pwrite_ssize_max_io_size(int fd, char *Buf) { + pwrite(fd, Buf, SSIZE_MAX_PLUS_ONE, 0); + // report-warning@-1{{The 3rd argument to 'pwrite' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-warning@-2{{The 3rd argument to 'pwrite' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-note@-3{{The 3rd argument to 'pwrite' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} +} + +void test_pwrite64_ssize_max_io_size(int fd, char *Buf) { + pwrite64(fd, Buf, SSIZE_MAX_PLUS_ONE, 0); + // report-warning@-1{{The 3rd argument to 'pwrite64' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-warning@-2{{The 3rd argument to 'pwrite64' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} + // bugpath-note@-3{{The 3rd argument to 'pwrite64' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} +} + void test_readlink_ssize_max_io_size(char *Buf) { readlink("path", Buf, SSIZE_MAX_PLUS_ONE); // report-warning@-1{{The 3rd argument to 'readlink' is 9223372036854775808 but should be a value not greater than SSIZE_MAX}} >From a7dc27223bf19e32c28e4ebc2f939530122682a5 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko <[email protected]> Date: Tue, 12 May 2026 01:10:22 +0300 Subject: [PATCH 8/8] [clang][docs] Add release notes for fortify-source and analyzer changes Note the new -Wfortify-source coverage of POSIX unistd.h I/O functions (read/write/pread/pwrite/readlink/readlinkat/getcwd) and the fix for source over-read detection on memcpy_chk/memmove_chk/mempcpy_chk/ memccpy_chk. Note the new unix.StdCLibraryFunctions SSIZE_MAX constraints on read/write/readlink/readlinkat and the new pread/pwrite summaries. --- clang/docs/ReleaseNotes.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index c71f7b173259f..58fc2e5b26c19 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -504,6 +504,17 @@ Improvements to Clang's diagnostics - Added ``-Wattribute-alias`` to diagnose type mismatches between an alias and its aliased function. (#GH195550) +- ``-Wfortify-source`` now diagnoses buffer-size mismatches in calls to the + POSIX ``unistd.h`` I/O functions ``read``, ``write``, ``pread``, + ``pread64``, ``pwrite``, ``pwrite64``, ``readlink``, ``readlinkat``, and + ``getcwd`` when the buffer and the size argument are both statically + known. + +- ``-Wfortify-source`` now flags source-buffer over-reads through + ``__builtin___memcpy_chk``, ``__builtin___memmove_chk``, + ``__builtin___mempcpy_chk``, and ``__builtin___memccpy_chk``, matching the + behavior already provided for the non-``_chk`` variants. + Improvements to Clang's time-trace ---------------------------------- @@ -790,6 +801,19 @@ Crash and bug fixes - Fixed ``security.VAList`` checker producing false positives when analyzing C23 code where ``va_start`` expands to ``__builtin_c23_va_start``. +Improvements +^^^^^^^^^^^^ + +- ``unix.StdCLibraryFunctions`` now diagnoses size arguments greater than + ``SSIZE_MAX`` passed to ``read``, ``write``, ``readlink``, and + ``readlinkat``. This catches common mistakes such as passing ``-1`` to a + ``size_t`` parameter or using a constant that is valid on one platform + but exceeds ``SSIZE_MAX`` on another. + +- ``unix.StdCLibraryFunctions`` now models ``pread``, ``pread64``, + ``pwrite``, and ``pwrite64`` with the same size constraint and + return-value bounds as ``read`` and ``write``. + .. comment: This is for the Static Analyzer. Using the caret `^^^` underlining for subsections: _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
