https://github.com/nuclearcat updated https://github.com/llvm/llvm-project/pull/224979
>From 078bb3809db9dccae910287503e148f1120b23f7 Mon Sep 17 00:00:00 2001 From: Denys Fedoryshchenko <[email protected]> Date: Mon, 21 Sep 2026 03:28:40 +0300 Subject: [PATCH] [clang][Sema] Add buffer-size checks for unistd I/O functions Diagnose destination buffer-size mismatches in read, pread(64), readlink(at), and getcwd, and source over-reads in write and pwrite(64) under -Wfortify-source. Register library builtins with IgnoreSignature and empty prototypes for target-dependent signatures. Give getcwd a complete prototype. Guard checks by argument count and types, including character pointees for path and character-buffer arguments, to accommodate libc differences without diagnosing unrelated declarations. Add regression coverage for prototype checks, signed counts, target size_t types, and warning-group controls. All 30 selected Sema and Static Analyzer tests pass with assertions enabled. Split from #196499, based on #161737. Co-authored-by: Colin Kinloch <[email protected]> Signed-off-by: Denys Fedoryshchenko <[email protected]> Assisted-By: Codex OpenAI --- clang/docs/ReleaseNotes.md | 5 + clang/include/clang/Basic/Builtins.td | 67 +++++ clang/lib/Sema/SemaChecking.cpp | 60 +++++ clang/test/Analysis/taint-generic.c | 2 +- .../Sema/warn-fortify-source-char-pointers.c | 52 ++++ .../Sema/warn-fortify-source-prototype-gate.c | 246 ++++++++++++++++++ .../Sema/warn-fortify-source-signed-count.c | 52 ++++ .../Sema/warn-fortify-source-undeclared.c | 20 ++ clang/test/Sema/warn-fortify-source-write.c | 32 +++ clang/test/Sema/warn-fortify-source.c | 67 +++++ 10 files changed, 602 insertions(+), 1 deletion(-) create mode 100644 clang/test/Sema/warn-fortify-source-char-pointers.c create mode 100644 clang/test/Sema/warn-fortify-source-prototype-gate.c create mode 100644 clang/test/Sema/warn-fortify-source-signed-count.c create mode 100644 clang/test/Sema/warn-fortify-source-undeclared.c create mode 100644 clang/test/Sema/warn-fortify-source-write.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 7ec126a065ae5ab..b3b3f8cd8597f4b 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -307,6 +307,11 @@ features cannot lower the translation-unit ABI level; ### Improvements to Clang's diagnostics +- `-Wfortify-source` now diagnoses destination buffer-size mismatches in calls to + `read`, `pread`, `pread64`, `readlink`, `readlinkat`, and `getcwd` when the buffer + size and byte count are statically known. It also diagnoses source buffer + over-reads in `write`, `pwrite`, and `pwrite64`. + - `-Wfortify-source` now diagnoses when `strlcat`, `__builtin_strlcat`, `strlcpy`, or `__builtin_strlcpy` is called with a size argument larger than the destination buffer. diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 7a59aaa133d5229..5252fab7250de0a 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -3865,6 +3865,73 @@ def VFork : LibBuiltin<"unistd.h"> { let Prototype = "pid_t()"; } +// Except for getcwd, these unistd I/O signatures vary across targets (ssize_t, +// off_t and count types). Require a declaration rather than synthesizing a +// prototype for those functions. + +def Read : LibBuiltin<"unistd.h"> { + let Spellings = ["read"]; + let Attributes = [IgnoreSignature]; + // ssize_t(int, void*, size_t); return and count types are target-specific + let Prototype = ""; +} + +def Write : LibBuiltin<"unistd.h"> { + let Spellings = ["write"]; + let Attributes = [IgnoreSignature]; + // ssize_t(int, void const*, size_t); return and count types are target-specific + let Prototype = ""; +} + +def PRead : LibBuiltin<"unistd.h"> { + let Spellings = ["pread"]; + let Attributes = [IgnoreSignature]; + // ssize_t(int, void*, size_t, off_t); ssize_t and off_t are target-specific + let Prototype = ""; +} + +def PRead64 : LibBuiltin<"unistd.h"> { + let Spellings = ["pread64"]; + let Attributes = [IgnoreSignature]; + // ssize_t(int, void*, size_t, off64_t); ssize_t and off64_t are target-specific + let Prototype = ""; +} + +def PWrite : LibBuiltin<"unistd.h"> { + let Spellings = ["pwrite"]; + let Attributes = [IgnoreSignature]; + // ssize_t(int, void const*, size_t, off_t); + // ssize_t and off_t are target-specific + let Prototype = ""; +} + +def PWrite64 : LibBuiltin<"unistd.h"> { + let Spellings = ["pwrite64"]; + let Attributes = [IgnoreSignature]; + // ssize_t(int, void const*, size_t, off64_t); + // ssize_t and off64_t are target-specific + let Prototype = ""; +} + +def ReadLink : LibBuiltin<"unistd.h"> { + let Spellings = ["readlink"]; + let Attributes = [IgnoreSignature]; + // ssize_t(char const*, char*, size_t); ssize_t is target-specific + let Prototype = ""; +} + +def ReadLinkAt : LibBuiltin<"unistd.h"> { + let Spellings = ["readlinkat"]; + let Attributes = [IgnoreSignature]; + // ssize_t(int, char const*, char*, size_t); ssize_t is target-specific + let Prototype = ""; +} + +def GetCwd : LibBuiltin<"unistd.h"> { + let Spellings = ["getcwd"]; + let Prototype = "char*(char*, size_t)"; +} + // POSIX sys/stat.h def Umask : LibBuiltin<"sys/stat.h"> { diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 0531dfa877fbdf4..16dce409529a029 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1487,6 +1487,66 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, break; } + case Builtin::BIread: + case Builtin::BIpread: + case Builtin::BIpread64: + case Builtin::BIreadlink: + case Builtin::BIreadlinkat: + case Builtin::BIgetcwd: { + unsigned BufIdx = 1; + if (BuiltinID == Builtin::BIgetcwd) + BufIdx = 0; + else if (BuiltinID == Builtin::BIreadlinkat) + BufIdx = 2; + unsigned CountIdx = BufIdx + 1; + unsigned ExpectedArgs = CountIdx + 1; + if (BuiltinID == Builtin::BIpread || BuiltinID == Builtin::BIpread64) + ++ExpectedArgs; + if (TheCall->getNumArgs() != ExpectedArgs || + !TheCall->getArg(BufIdx)->getType()->isPointerType() || + !TheCall->getArg(CountIdx)->getType()->isIntegerType()) + return; + if ((BuiltinID == Builtin::BIgetcwd || BuiltinID == Builtin::BIreadlink || + BuiltinID == Builtin::BIreadlinkat) && + !TheCall->getArg(BufIdx)->getType()->getPointeeType()->isCharType()) + return; + if (BuiltinID != Builtin::BIgetcwd && BuiltinID != Builtin::BIreadlink && + !TheCall->getArg(0)->getType()->isIntegerType()) + return; + if (BuiltinID == Builtin::BIreadlink || + BuiltinID == Builtin::BIreadlinkat) { + QualType PathTy = + TheCall->getArg(BufIdx - 1)->getType()->getPointeeType(); + if (PathTy.isNull() || !PathTy->isCharType()) + return; + } + if ((BuiltinID == Builtin::BIpread || BuiltinID == Builtin::BIpread64) && + !TheCall->getArg(3)->getType()->isIntegerType()) + return; + DiagID = diag::warn_fortify_source_size_mismatch; + AccessSize = Checker.ComputeExplicitObjectSizeArgument(CountIdx); + BufferSize = Checker.ComputeSizeArgument(BufIdx); + break; + } + + case Builtin::BIwrite: + case Builtin::BIpwrite: + case Builtin::BIpwrite64: { + unsigned ExpectedArgs = BuiltinID == Builtin::BIwrite ? 3 : 4; + if (TheCall->getNumArgs() != ExpectedArgs || + !TheCall->getArg(0)->getType()->isIntegerType() || + !TheCall->getArg(1)->getType()->isPointerType() || + !TheCall->getArg(2)->getType()->isIntegerType()) + return; + if (BuiltinID != Builtin::BIwrite && + !TheCall->getArg(3)->getType()->isIntegerType()) + return; + DiagID = diag::warn_fortify_source_overread; + AccessSize = Checker.ComputeExplicitObjectSizeArgument(2); + BufferSize = Checker.ComputeSizeArgument(1); + break; + } + case Builtin::BIrecv: case Builtin::BIrecvfrom: { unsigned ExpectedArgs = BuiltinID == Builtin::BIrecv ? 4 : 6; diff --git a/clang/test/Analysis/taint-generic.c b/clang/test/Analysis/taint-generic.c index 1ad491a10e60397..db525f8f88cf4b6 100644 --- a/clang/test/Analysis/taint-generic.c +++ b/clang/test/Analysis/taint-generic.c @@ -384,7 +384,7 @@ void testStructArray(void) { __builtin_memset(&tainted, 0, sizeof(tainted)); // If we taint element 1, we should not raise an alert on taint for element 0 or element 2 - read(sock, &tainted[1], sizeof(tainted)); + read(sock, &tainted[1], sizeof(tainted[1])); clang_analyzer_isTainted_int(tainted[0].length); // expected-warning {{NO}} clang_analyzer_isTainted_int(tainted[2].length); // expected-warning {{NO}} } diff --git a/clang/test/Sema/warn-fortify-source-char-pointers.c b/clang/test/Sema/warn-fortify-source-char-pointers.c new file mode 100644 index 000000000000000..77f5656e9c20c4c --- /dev/null +++ b/clang/test/Sema/warn-fortify-source-char-pointers.c @@ -0,0 +1,52 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DWRONG_BUFFER -DPOINTEE=int -verify=expected,c -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DWRONG_BUFFER -DPOINTEE=int -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DWRONG_BUFFER -DPOINTEE=void -verify=expected,c -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DWRONG_BUFFER -DPOINTEE=void -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DPOINTEE=int -verify=expected,c -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DPOINTEE=int -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DPOINTEE=void -verify=expected,c -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DPOINTEE=void -verify -Werror + +typedef __SIZE_TYPE__ size_t; + +#ifdef WRONG_BUFFER +typedef const char *path_t; +typedef POINTEE *buffer_t; +#else +typedef const POINTEE *path_t; +typedef char *buffer_t; +#endif + +#ifdef __cplusplus +extern "C" { +#endif +long readlink(path_t, buffer_t, size_t); +long readlinkat(int, path_t, buffer_t, size_t); +#ifdef WRONG_BUFFER +char *getcwd(buffer_t, size_t); +// c-error@-1 {{incompatible redeclaration of library function 'getcwd'}} +// c-note@-2 {{'getcwd' is a builtin with type 'char *(char *, __size_t)'}} +#endif +long read(int, void *, size_t); +long write(int, const void *, size_t); +#ifdef __cplusplus +} +#endif + +// Unrelated pointer types in either the path or buffer must not trigger +// fortify diagnostics, even when the count exceeds the buffer size. +void call_unrelated(void) { + char buf[4]; + readlink((path_t)0, (buffer_t)buf, 8); + readlinkat(0, (path_t)0, (buffer_t)buf, 8); +#ifdef WRONG_BUFFER + getcwd((buffer_t)buf, 8); +#endif +} + +// The void-pointer I/O functions must still check non-character buffers. +void call_io(void) { + int buf[1]; + read(0, buf, 8); // expected-error {{'read' size argument is too large; destination buffer has size 4, but size argument is 8}} + write(0, buf, 8); // expected-error {{'write' will always read past the end of the source buffer; source buffer has size 4, but the size is 8}} +} 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 000000000000000..aa4140139b64eb3 --- /dev/null +++ b/clang/test/Sema/warn-fortify-source-prototype-gate.c @@ -0,0 +1,246 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DWRONG_ARITY -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DWRONG_ARITY -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DWRONG_BUFFER -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DWRONG_BUFFER -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DWRONG_COUNT -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DWRONG_COUNT -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DNO_BUILTIN -fno-builtin -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DNO_BUILTIN -fno-builtin -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DWRONG_FD -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DWRONG_FD -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DWRONG_OFFSET -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DWRONG_OFFSET -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DWRONG_PATH -verify -Werror +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DWRONG_PATH -verify -Werror +#if defined(__cplusplus) || defined(NO_BUILTIN) || \ + !(defined(WRONG_ARITY) || defined(WRONG_BUFFER) || defined(WRONG_COUNT)) +// expected-no-diagnostics +#endif + +// No __builtin_ aliases are provided. The library names are recognized +// unless builtin recognition is disabled. Ignore unrelated argument shapes, +// internal linkage, and C++ language linkage. +// getcwd has a complete builtin prototype, so incompatible C declarations +// receive the usual library redeclaration diagnostic. +typedef __SIZE_TYPE__ size_t; + +#if __has_builtin(__builtin_read) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(read) +#error missing library builtin +#endif +#endif +#if __has_builtin(__builtin_write) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(write) +#error missing library builtin +#endif +#endif +#if __has_builtin(__builtin_pread) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(pread) +#error missing library builtin +#endif +#endif +#if __has_builtin(__builtin_pread64) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(pread64) +#error missing library builtin +#endif +#endif +#if __has_builtin(__builtin_pwrite) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(pwrite) +#error missing library builtin +#endif +#endif +#if __has_builtin(__builtin_pwrite64) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(pwrite64) +#error missing library builtin +#endif +#endif +#if __has_builtin(__builtin_readlink) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(readlink) +#error missing library builtin +#endif +#endif +#if __has_builtin(__builtin_readlinkat) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(readlinkat) +#error missing library builtin +#endif +#endif +#if __has_builtin(__builtin_getcwd) +#error unexpected builtin alias +#endif +#ifndef NO_BUILTIN +#if !__has_builtin(getcwd) +#error missing library builtin +#endif +#endif + +#if defined(WRONG_FD) || defined(WRONG_OFFSET) || defined(WRONG_PATH) +#ifdef __cplusplus +extern "C" { +#endif +#ifdef WRONG_FD +int read(double, void *, size_t); +int write(double, const void *, size_t); +int pread(double, void *, size_t, long); +int pread64(double, void *, size_t, long long); +int pwrite(double, const void *, size_t, long); +int pwrite64(double, const void *, size_t, long long); +int readlinkat(double, const char *, char *, size_t); +#elif defined(WRONG_OFFSET) +int pread(int, void *, size_t, double); +int pread64(int, void *, size_t, double); +int pwrite(int, const void *, size_t, double); +int pwrite64(int, const void *, size_t, double); +#else +int readlink(int, char *, size_t); +int readlinkat(int, int, char *, size_t); +#endif +#ifdef __cplusplus +} +#endif + +void call_mismatched_remaining_args(void) { + char buf[4]; +#ifdef WRONG_FD + read(0, buf, 8); + write(0, buf, 8); + pread(0, buf, 8, 0); + pread64(0, buf, 8, 0); + pwrite(0, buf, 8, 0); + pwrite64(0, buf, 8, 0); + readlinkat(0, "/", buf, 8); +#elif defined(WRONG_OFFSET) + pread(0, buf, 8, 0); + pread64(0, buf, 8, 0); + pwrite(0, buf, 8, 0); + pwrite64(0, buf, 8, 0); +#else + readlink(0, buf, 8); + readlinkat(0, 0, buf, 8); +#endif +} +#elif defined(WRONG_ARITY) || defined(WRONG_BUFFER) || defined(WRONG_COUNT) || defined(NO_BUILTIN) +#ifdef WRONG_ARITY +#define LAST(x) +#else +#define LAST(x) , x +#endif +#ifdef WRONG_BUFFER +#define BUFFER int +#define BUF_ARG 0 +#else +#define BUFFER void * +#define BUF_ARG buf +#endif +#ifdef WRONG_COUNT +#define COUNT double +#else +#define COUNT size_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif +int read(int, BUFFER LAST(COUNT)); +int write(int, BUFFER LAST(COUNT)); +int pread(int, BUFFER, COUNT LAST(long)); +int pread64(int, BUFFER, COUNT LAST(long long)); +int pwrite(int, BUFFER, COUNT LAST(long)); +int pwrite64(int, BUFFER, COUNT LAST(long long)); +int readlink(const char *, BUFFER LAST(COUNT)); +int readlinkat(int, const char *, BUFFER LAST(COUNT)); +int getcwd(BUFFER LAST(COUNT)); +#if !defined(__cplusplus) && !defined(NO_BUILTIN) +// expected-error@-2 {{incompatible redeclaration of library function 'getcwd'}} +// expected-note@-3 {{'getcwd' is a builtin with type 'char *(char *, __size_t)'}} +#endif +#ifdef __cplusplus +} +#endif + +void call_mismatched(void) { + char buf[4]; + read(0, BUF_ARG LAST(8)); + write(0, BUF_ARG LAST(8)); + pread(0, BUF_ARG, 8 LAST(0)); + pread64(0, BUF_ARG, 8 LAST(0)); + pwrite(0, BUF_ARG, 8 LAST(0)); + pwrite64(0, BUF_ARG, 8 LAST(0)); + readlink("/", BUF_ARG LAST(8)); + readlinkat(0, "/", BUF_ARG LAST(8)); + getcwd(BUF_ARG LAST(8)); +} +#else +static int read(int arg0, void * arg1, size_t arg2) { return 0; } +static int write(int arg0, const void * arg1, size_t arg2) { return 0; } +static int pread(int arg0, void * arg1, size_t arg2, long arg3) { return 0; } +static int pread64(int arg0, void * arg1, size_t arg2, long long arg3) { return 0; } +static int pwrite(int arg0, const void * arg1, size_t arg2, long arg3) { return 0; } +static int pwrite64(int arg0, const void * arg1, size_t arg2, long long arg3) { return 0; } +static int readlink(const char * arg0, char * arg1, size_t arg2) { return 0; } +static int readlinkat(int arg0, const char * arg1, char * arg2, size_t arg3) { return 0; } +static int getcwd(char * arg0, size_t arg1) { return 0; } +void call_static(void) { + char buf[4]; + read(0, buf, 8); + write(0, buf, 8); + pread(0, buf, 8, 0); + pread64(0, buf, 8, 0); + pwrite(0, buf, 8, 0); + pwrite64(0, buf, 8, 0); + readlink("/", buf, 8); + readlinkat(0, "/", buf, 8); + getcwd(buf, 8); +} + +#ifdef __cplusplus +namespace user { +int read(int, void *, size_t); +int write(int, const void *, size_t); +int pread(int, void *, size_t, long); +int pread64(int, void *, size_t, long long); +int pwrite(int, const void *, size_t, long); +int pwrite64(int, const void *, size_t, long long); +int readlink(const char *, char *, size_t); +int readlinkat(int, const char *, char *, size_t); +int getcwd(char *, size_t); +void call(void) { + char buf[4]; + read(0, buf, 8); + write(0, buf, 8); + pread(0, buf, 8, 0); + pread64(0, buf, 8, 0); + pwrite(0, buf, 8, 0); + pwrite64(0, buf, 8, 0); + readlink("/", buf, 8); + readlinkat(0, "/", buf, 8); + getcwd(buf, 8); +} +} // namespace user +#endif +#endif diff --git a/clang/test/Sema/warn-fortify-source-signed-count.c b/clang/test/Sema/warn-fortify-source-signed-count.c new file mode 100644 index 000000000000000..2d7e37debf7d111 --- /dev/null +++ b/clang/test/Sema/warn-fortify-source-signed-count.c @@ -0,0 +1,52 @@ +// RUN: %clang_cc1 -triple i686-unknown-linux %s -verify=expected,signed32 +// RUN: %clang_cc1 -triple i686-unknown-linux %s -fexperimental-new-constant-interpreter -verify=expected,signed32 +// RUN: %clang_cc1 -triple i686-unknown-linux %s -DUNSIGNED_COUNT -verify=expected,unsigned32 +// RUN: %clang_cc1 -triple i686-unknown-linux %s -DUNSIGNED_COUNT -fexperimental-new-constant-interpreter -verify=expected,unsigned32 +// RUN: %clang_cc1 -triple x86_64-unknown-linux %s -verify=expected,signed64 +// RUN: %clang_cc1 -triple x86_64-unknown-linux %s -fexperimental-new-constant-interpreter -verify=expected,signed64 +// RUN: %clang_cc1 -triple x86_64-unknown-linux %s -DUNSIGNED_COUNT -verify=expected,unsigned64 +// RUN: %clang_cc1 -triple x86_64-unknown-linux %s -DUNSIGNED_COUNT -fexperimental-new-constant-interpreter -verify=expected,unsigned64 +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc %s -verify=expected,signed64 +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc %s -fexperimental-new-constant-interpreter -verify=expected,signed64 +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc %s -DUNSIGNED_COUNT -verify=expected,unsigned64 +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc %s -DUNSIGNED_COUNT -fexperimental-new-constant-interpreter -verify=expected,unsigned64 +// RUN: %clang_cc1 -triple i686-unknown-linux -x c++ %s -verify=expected,signed32 +// RUN: %clang_cc1 -triple x86_64-unknown-linux -x c++ %s -verify=expected,signed64 + +// The count and return types of read/write need not match POSIX size_t and +// ssize_t. In particular, Windows read/write use unsigned int counts on LLP64. +#ifdef UNSIGNED_COUNT +typedef unsigned int count_t; +#else +typedef int count_t; +#endif +typedef __SIZE_TYPE__ size_t; +#ifdef __cplusplus +extern "C" { +#endif +int read(int, char *, count_t); +int write(int, const char *, count_t); +char *getcwd(char *, size_t); +#ifdef __cplusplus +} +#endif + +void test_counts(count_t n) { + char buf[4]; + read(0, buf, 4); + write(0, buf, 4); + read(0, buf, 8); // expected-warning {{'read' size argument is too large; destination buffer has size 4, but size argument is 8}} + write(0, buf, 8); // expected-warning {{'write' will always read past the end of the source buffer; source buffer has size 4, but the size is 8}} + read(0, buf, n); + write(0, buf, n); + read(0, buf, -1); // signed32-warning {{size argument is 4294967295}} signed64-warning {{size argument is 18446744073709551615}} unsigned32-warning {{size argument is 4294967295}} unsigned64-warning {{size argument is 4294967295}} + write(0, buf, -1); // signed32-warning {{but the size is 4294967295}} signed64-warning {{but the size is 18446744073709551615}} unsigned32-warning {{but the size is 4294967295}} unsigned64-warning {{but the size is 4294967295}} +} + +// Unlike read/write, getcwd has a complete builtin prototype using the +// target's size_t. Check that it is recognized on ILP32, LP64, and LLP64. +void test_getcwd(void) { + char b[4]; + getcwd(b, sizeof(b)); + getcwd(b, 8); // expected-warning {{'getcwd' size argument is too large; destination buffer has size 4, but size argument is 8}} +} diff --git a/clang/test/Sema/warn-fortify-source-undeclared.c b/clang/test/Sema/warn-fortify-source-undeclared.c new file mode 100644 index 000000000000000..f9d5e363a7fe74e --- /dev/null +++ b/clang/test/Sema/warn-fortify-source-undeclared.c @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c99 %s -verify +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -verify + +// Empty builtin prototypes must not provide implicit library declarations. +// getcwd has a complete prototype and provides the usual header hint in C. +void call_undeclared(void) { + char buf[4]; + read(0, buf, 4); // expected-error {{undeclared}} + write(0, buf, 4); // expected-error {{undeclared}} + pread(0, buf, 4, 0); // expected-error {{undeclared}} + pread64(0, buf, 4, 0); // expected-error {{undeclared}} + pwrite(0, buf, 4, 0); // expected-error {{undeclared}} + pwrite64(0, buf, 4, 0); // expected-error {{undeclared}} + readlink("/", buf, 4); // expected-error {{undeclared}} + readlinkat(0, "/", buf, 4); // expected-error {{undeclared}} + getcwd(buf, 4); // expected-error {{undeclared}} +#ifndef __cplusplus + // expected-note@-2 {{include the header <unistd.h> or explicitly provide a declaration for 'getcwd'}} +#endif +} diff --git a/clang/test/Sema/warn-fortify-source-write.c b/clang/test/Sema/warn-fortify-source-write.c new file mode 100644 index 000000000000000..35c086d48110d30 --- /dev/null +++ b/clang/test/Sema/warn-fortify-source-write.c @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -Wno-stringop-overread -Werror=fortify-source -verify=fortify +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -Wno-stringop-overread -Werror=fortify-source -verify=fortify +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -Wno-fortify-source -Wstringop-overread -verify=disabled +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -Wno-fortify-source -Wstringop-overread -verify=disabled +// disabled-no-diagnostics + +typedef __SIZE_TYPE__ size_t; + +#ifdef __cplusplus +extern "C" { +#endif +long write(int, const void *, size_t); +long pwrite(int, const void *, size_t, long); +long pwrite64(int, const void *, size_t, long long); +#ifdef __cplusplus +} +#endif + +// These overread diagnostics belong to -Wfortify-source, independently of +// whether -Wstringop-overread is enabled. +void test_write(size_t n) { + char buf[4]; + write(0, buf, 4); + pwrite(0, buf, 4, 0); + pwrite64(0, buf, 4, 0); + write(0, buf, n); + pwrite(0, buf, n, 0); + pwrite64(0, buf, n, 0); + write(0, buf, 8); // fortify-error {{'write' will always read past the end of the source buffer; source buffer has size 4, but the size is 8}} + pwrite(0, buf, 8, 0); // fortify-error {{'pwrite' will always read past the end of the source buffer; source buffer has size 4, but the size is 8}} + pwrite64(0, buf, 8, 0); // fortify-error {{'pwrite64' will always read past the end of the source buffer; source buffer has size 4, but the size is 8}} +} diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c index 73a10070008f442..bc4b31dbffcd495 100644 --- a/clang/test/Sema/warn-fortify-source.c +++ b/clang/test/Sema/warn-fortify-source.c @@ -10,6 +10,8 @@ typedef unsigned long size_t; typedef long ssize_t; +typedef long off_t; +typedef long long off64_t; typedef unsigned int socklen_t; struct sockaddr; struct pollfd { @@ -50,6 +52,16 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); char *fgets(char *s, int size, FILE *stream); +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 @@ -173,6 +185,61 @@ void call_fread_fwrite_fgets(FILE *fp) { fgets(src, 0, fp); } +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' will always read past the end of the source buffer; source buffer has size 10, but the size is 20}} +} + +void call_pwrite(void) { + char buf[10]; + pwrite(0, buf, 10, 0); + pwrite(0, buf, 20, 0); // expected-warning {{'pwrite' will always read past the end of the source buffer; source buffer has size 10, but the size is 20}} +} + +void call_pwrite64(void) { + char buf[10]; + pwrite64(0, buf, 10, 0); + pwrite64(0, buf, 20, 0); // expected-warning {{'pwrite64' will always read past the end of the source buffer; source buffer has size 10, but the size 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"); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
