[clang] [Clang][Sema] Add fortify warnings for send and sendto (PR #228194)
Venkatesh Srinivasan via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 1 12:54:20 PDT 2026
https://github.com/venk-ks updated https://github.com/llvm/llvm-project/pull/228194
>From 64316b365bdf174fb291409512f6a6b4bad96a37 Mon Sep 17 00:00:00 2001
From: Venkatesh Srinivasan <venk at google.com>
Date: Thu, 1 Oct 2026 18:47:55 +0000
Subject: [PATCH] [Clang][Sema] Add fortify warnings for send and sendto
Teach -Wfortify-source to diagnose when send or sendto is called with an
explicit length argument larger than the known source buffer size,
using diag::warn_fortify_source_overread.
Part of #142230
Assisted-by: Gemini
---
clang/docs/ReleaseNotes.md | 3 +-
clang/include/clang/Basic/Builtins.td | 15 +++
clang/lib/Sema/SemaChecking.cpp | 30 +++++
.../warn-fortify-source-send-not-builtin.c | 110 ++++++++++++++++++
clang/test/Sema/warn-fortify-source.c | 48 ++++++++
5 files changed, 205 insertions(+), 1 deletion(-)
create mode 100644 clang/test/Sema/warn-fortify-source-send-not-builtin.c
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 9a9b8447786df..0f2bd80d54ac3 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -302,7 +302,8 @@ features cannot lower the translation-unit ABI level;
`__builtin_strlcpy` is called with a size argument larger than the destination buffer.
- `-Wfortify-source` now diagnoses when `recv` or `recvfrom` is called with a
- size argument larger than the destination buffer.
+ size argument larger than the destination buffer, or when `send` or `sendto`
+ is called with a size argument larger than the source buffer.
- `-Wfortify-source` now diagnoses when `poll`, `ppoll`, or `ppoll64` is called
with a descriptor count whose total size exceeds the `fds` array size.
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index c6286df50f97c..e18403b59a508 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -3890,6 +3890,21 @@ def RecvFrom : LibBuiltin<"sys/socket.h"> {
let Prototype = "";
}
+def Send : LibBuiltin<"sys/socket.h"> {
+ let Spellings = ["send"];
+ let Attributes = [IgnoreSignature];
+ // ssize_t(int, const void*, size_t, int); ssize_t is target-specific
+ let Prototype = "";
+}
+
+def SendTo : LibBuiltin<"sys/socket.h"> {
+ let Spellings = ["sendto"];
+ let Attributes = [IgnoreSignature];
+ // ssize_t(int, const void*, size_t, int, const struct sockaddr*, socklen_t);
+ // ssize_t, struct sockaddr, and socklen_t are target-specific
+ let Prototype = "";
+}
+
// POSIX poll.h
def Poll : LibBuiltin<"poll.h"> {
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 0531dfa877fbd..b5f3808d99f00 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1500,6 +1500,36 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
break;
}
+ case Builtin::BIsend:
+ case Builtin::BIsendto: {
+ unsigned ExpectedArgs = BuiltinID == Builtin::BIsend ? 4 : 6;
+ if (TheCall->getNumArgs() != ExpectedArgs ||
+ !TheCall->getArg(0)->getType()->isIntegerType() ||
+ !TheCall->getArg(1)->getType()->isPointerType() ||
+ !TheCall->getArg(2)->getType()->isIntegerType() ||
+ !TheCall->getArg(3)->getType()->isIntegerType())
+ return;
+ if (BuiltinID == Builtin::BIsendto) {
+ QualType AddrTy = TheCall->getArg(4)->getType();
+ const RecordDecl *UnionRD = AddrTy->getAsRecordDecl();
+ if (!UnionRD || !UnionRD->isUnion() ||
+ !UnionRD->hasAttr<TransparentUnionAttr>()) {
+ QualType AddrPointeeTy = AddrTy->getPointeeType();
+ if (AddrPointeeTy.isNull())
+ return;
+ const RecordDecl *RD = AddrPointeeTy->getAsRecordDecl();
+ if (!RD || !RD->getIdentifier() || RD->getName() != "sockaddr")
+ return;
+ }
+ if (!TheCall->getArg(5)->getType()->isIntegerType())
+ return;
+ }
+ DiagID = diag::warn_fortify_source_overread;
+ AccessSize = Checker.ComputeExplicitObjectSizeArgument(2);
+ BufferSize = Checker.ComputeSizeArgument(1);
+ break;
+ }
+
case Builtin::BIpoll:
case Builtin::BIppoll:
case Builtin::BIppoll64: {
diff --git a/clang/test/Sema/warn-fortify-source-send-not-builtin.c b/clang/test/Sema/warn-fortify-source-send-not-builtin.c
new file mode 100644
index 0000000000000..009756948e3cc
--- /dev/null
+++ b/clang/test/Sema/warn-fortify-source-send-not-builtin.c
@@ -0,0 +1,110 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -verify
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -verify
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DMISMATCHED_SIG -verify
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DMISMATCHED_SIG -verify
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DMISMATCHED_SIG2 -verify
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DMISMATCHED_SIG2 -verify
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c %s -DMISMATCHED_SIG3 -verify
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ %s -DMISMATCHED_SIG3 -verify
+// expected-no-diagnostics
+
+// Declarations that are not the POSIX send/sendto should not trigger
+// -Wfortify-source diagnostics:
+// * a file-local send/sendto with internal linkage;
+// * in C++, a send/sendto without C language linkage;
+// * a C-linkage function whose argument count or parameter types do not match
+// POSIX send/sendto.
+
+typedef unsigned long size_t;
+typedef long ssize_t;
+typedef unsigned int socklen_t;
+struct sockaddr;
+struct other_addr;
+
+#ifdef MISMATCHED_SIG
+#ifdef __cplusplus
+extern "C" {
+#endif
+int send(int fd, const void *buf, size_t len);
+int sendto(int fd, const void *buf, size_t len, int flags);
+#ifdef __cplusplus
+}
+#endif
+
+void call_mismatched_send(int fd) {
+ char buf[10];
+ (void)send(fd, buf, 20);
+ (void)sendto(fd, buf, 20, 0);
+}
+#elif defined(MISMATCHED_SIG2)
+#ifdef __cplusplus
+extern "C" {
+#endif
+int send(const void *fd, const void *buf, size_t len, int flags);
+int sendto(int fd, const void *buf, size_t len, int flags,
+ const struct other_addr *addr, socklen_t addrlen);
+#ifdef __cplusplus
+}
+#endif
+
+void call_mismatched_send2(void) {
+ char buf[10];
+ (void)send(buf, buf, 20, 0);
+ (void)sendto(0, buf, 20, 0, (const struct other_addr *)0, 0);
+}
+#elif defined(MISMATCHED_SIG3)
+#ifdef __cplusplus
+extern "C" {
+#endif
+int send(int fd, const void *buf, size_t len, const void *flags);
+int sendto(int fd, const void *buf, size_t len, int flags,
+ const struct sockaddr *addr, const socklen_t *addrlen);
+#ifdef __cplusplus
+}
+#endif
+
+void call_mismatched_send3(int fd) {
+ char buf[10];
+ (void)send(fd, buf, 20, (const void *)0);
+ (void)sendto(fd, buf, 20, 0, (const struct sockaddr *)0, (const socklen_t *)0);
+}
+#else
+static ssize_t send(int fd, const void *buf, size_t len, int flags) {
+ (void)fd;
+ (void)buf;
+ (void)len;
+ (void)flags;
+ return 0;
+}
+
+static ssize_t sendto(int fd, const void *buf, size_t len, int flags,
+ const struct sockaddr *addr, socklen_t addrlen) {
+ (void)fd;
+ (void)buf;
+ (void)len;
+ (void)flags;
+ (void)addr;
+ (void)addrlen;
+ return 0;
+}
+
+void call_static_send(int fd) {
+ char buf[10];
+ (void)send(fd, buf, 20, 0);
+ (void)sendto(fd, buf, 20, 0, (const struct sockaddr *)0, 0);
+}
+
+#ifdef __cplusplus
+namespace user {
+ssize_t send(int, const void *, size_t, int);
+ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *,
+ socklen_t);
+
+void call(int fd) {
+ char buf[10];
+ (void)user::send(fd, buf, 20, 0);
+ (void)user::sendto(fd, buf, 20, 0, nullptr, 0);
+}
+} // namespace user
+#endif
+#endif
diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c
index 73a10070008f4..643e3d03a02a4 100644
--- a/clang/test/Sema/warn-fortify-source.c
+++ b/clang/test/Sema/warn-fortify-source.c
@@ -33,11 +33,16 @@ extern int sprintf(char *str, const char *format, ...);
// Also test the Windows winsock2.h signature where len is a signed int.
int recv(int, char *, int, int);
int recvfrom(int, char *, int, int, struct sockaddr *, int *);
+int send(int, const char *, int, int);
+int sendto(int, const char *, int, int, const struct sockaddr *, int);
typedef unsigned int nfds_t;
#else
void *memcpy(void *dst, const void *src, size_t c);
ssize_t recv(int, void *, size_t, int);
ssize_t recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *);
+ssize_t send(int, const void *, size_t, int);
+ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *,
+ socklen_t);
typedef unsigned long nfds_t;
#endif
int poll(struct pollfd *, nfds_t, int);
@@ -339,6 +344,49 @@ void call_recv_runtime(int fd, int n) {
recvfrom(fd, buf, n, 0, (struct sockaddr *)0, 0);
}
+void call_send(int fd) {
+ char buf[10];
+ send(fd, buf, 0, 0);
+ send(fd, buf, 10, 0);
+ send(fd, buf, 11, 0); // expected-warning {{'send' will always read past the end of the source buffer; source buffer has size 10, but the size is 11}}
+ send(fd, buf, -1, 0); // expected-warning {{'send' will always read past the end of the source buffer; source buffer has size 10, but the size is 18446744073709551615}}
+}
+
+void call_sendto(int fd) {
+ char buf[10];
+ sendto(fd, buf, 0, 0, (const struct sockaddr *)0, 0);
+ sendto(fd, buf, 10, 0, (const struct sockaddr *)0, 0);
+ sendto(fd, buf, 11, 0, (const struct sockaddr *)0, 0); // expected-warning {{'sendto' will always read past the end of the source buffer; source buffer has size 10, but the size is 11}}
+ sendto(fd, buf, -1, 0, (const struct sockaddr *)0, 0); // expected-warning {{'sendto' will always read past the end of the source buffer; source buffer has size 10, but the size is 18446744073709551615}}
+}
+
+void call_send_subobject(int fd) {
+ struct {
+ char first[10];
+ char second[20];
+ } s;
+ send(fd, s.first, 35, 0); // expected-warning {{'send' will always read past the end of the source buffer; source buffer has size 30, but the size is 35}}
+}
+
+void call_send_runtime(int fd, int n) {
+ char buf[10];
+ send(fd, buf, n, 0);
+ sendto(fd, buf, n, 0, (const struct sockaddr *)0, 0);
+}
+
+#if !defined(__cplusplus) && !defined(USE_BUILTINS)
+typedef union {
+ const struct sockaddr *__sockaddr__;
+} __CONST_SOCKADDR_ARG __attribute__((__transparent_union__));
+ssize_t sendto(int, const void *, size_t, int, __CONST_SOCKADDR_ARG, socklen_t);
+
+void call_sendto_transparent_union(int fd, const struct sockaddr *addr) {
+ char buf[10];
+ sendto(fd, buf, 10, 0, addr, 0);
+ sendto(fd, buf, 11, 0, addr, 0); // expected-warning {{'sendto' will always read past the end of the source buffer; source buffer has size 10, but the size is 11}}
+}
+#endif
+
void call_poll(void) {
struct pollfd fds[2];
struct pollfd single_fd;
More information about the cfe-commits
mailing list