https://github.com/venk-ks created https://github.com/llvm/llvm-project/pull/228194
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 >From 4a47d7f8a662d9913e264f586e2894a10f440179 Mon Sep 17 00:00:00 2001 From: Venkatesh Srinivasan <[email protected]> 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 | 24 ++++ .../warn-fortify-source-send-not-builtin.c | 110 ++++++++++++++++++ clang/test/Sema/warn-fortify-source.c | 33 ++++++ 5 files changed, 184 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..8c3bcc9ab17cd 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1500,6 +1500,30 @@ 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 AddrPointeeTy = TheCall->getArg(4)->getType()->getPointeeType(); + if (AddrPointeeTy.isNull()) + return; + const RecordDecl *RD = AddrPointeeTy->getAsRecordDecl(); + if (!RD || !RD->getIdentifier() || RD->getName() != "sockaddr" || + !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..9c6ef2a2f7806 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,34 @@ 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, 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, 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); +} + void call_poll(void) { struct pollfd fds[2]; struct pollfd single_fd; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
