[clang] [clang][Sema] Add buffer-size checks for unistd I/O functions (PR #224979)
Denys Fedoryshchenko via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 2 03:01:06 PDT 2026
https://github.com/nuclearcat updated https://github.com/llvm/llvm-project/pull/224979
>From 73bdecb251d36aedf3181313845e2ab00d413d3f Mon Sep 17 00:00:00 2001
From: Denys Fedoryshchenko <denys.f at collabora.com>
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 <colin.kinloch at collabora.com>
Signed-off-by: Denys Fedoryshchenko <denys.f at collabora.com>
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 c5eed2b035e66..5ff12fcf5ea11 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -298,6 +298,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 c6286df50f97c..8e79849ab0e86 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 0531dfa877fbd..16dce409529a0 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 1ad491a10e603..db525f8f88cf4 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 0000000000000..77f5656e9c20c
--- /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 at -1 {{incompatible redeclaration of library function 'getcwd'}}
+// c-note at -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 0000000000000..aa4140139b64e
--- /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 at -2 {{incompatible redeclaration of library function 'getcwd'}}
+// expected-note at -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 0000000000000..2d7e37debf7d1
--- /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 0000000000000..f9d5e363a7fe7
--- /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 at -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 0000000000000..35c086d48110d
--- /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 73a10070008f4..bc4b31dbffcd4 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");
More information about the cfe-commits
mailing list