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 <[email protected]> Date: Thu, 1 Oct 2026 18:47:55 +0000 Subject: [PATCH 1/2] [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 9a9b8447786dfd..0f2bd80d54ac31 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 c6286df50f97c5..e18403b59a5081 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 0531dfa877fbdf..b5f3808d99f00d 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 00000000000000..009756948e3ccb --- /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 73a10070008f44..643e3d03a02a49 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; >From f8fb369b81467e14ddf1c5df4f99242e4cbff827 Mon Sep 17 00:00:00 2001 From: Venkatesh Srinivasan <[email protected]> Date: Fri, 9 Oct 2026 20:07:30 +0000 Subject: [PATCH 2/2] Address review comments on send/sendto fortify checks --- clang/lib/Sema/SemaChecking.cpp | 21 ++++---- .../warn-fortify-source-send-not-builtin.c | 48 ++----------------- clang/test/Sema/warn-fortify-source.c | 22 ++++++--- 3 files changed, 31 insertions(+), 60 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index b5f3808d99f00d..faaae02ebd656f 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1510,17 +1510,20 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, !TheCall->getArg(3)->getType()->isIntegerType()) return; if (BuiltinID == Builtin::BIsendto) { + // Under _GNU_SOURCE in C mode, glibc's <sys/socket.h> declares sendto's + // dest_addr parameter as a transparent_union (__CONST_SOCKADDR_ARG) whose + // first field is 'const struct sockaddr *'. 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 (UnionRD && UnionRD->isUnion() && + UnionRD->hasAttr<TransparentUnionAttr>() && !UnionRD->field_empty()) + AddrTy = UnionRD->field_begin()->getType(); + 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; } diff --git a/clang/test/Sema/warn-fortify-source-send-not-builtin.c b/clang/test/Sema/warn-fortify-source-send-not-builtin.c index 009756948e3ccb..76a03d7de2094f 100644 --- a/clang/test/Sema/warn-fortify-source-send-not-builtin.c +++ b/clang/test/Sema/warn-fortify-source-send-not-builtin.c @@ -1,11 +1,6 @@ // 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 @@ -22,51 +17,14 @@ 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) { +void call_mismatched_send(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); + (void)send(fd, buf, 20); + (void)sendto(fd, buf, 20, 0, (const struct other_addr *)0, 0); } #else static ssize_t send(int fd, const void *buf, size_t len, int flags) { diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c index 643e3d03a02a49..3b492141ff40aa 100644 --- a/clang/test/Sema/warn-fortify-source.c +++ b/clang/test/Sema/warn-fortify-source.c @@ -41,8 +41,20 @@ 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); +#ifndef __cplusplus +// Under _GNU_SOURCE in C mode, glibc's <sys/socket.h> declares sendto's +// dest_addr parameter as a transparent_union (__CONST_SOCKADDR_ARG) so callers +// can pass struct sockaddr_in *, etc. without casting to struct sockaddr *. +struct sockaddr_in; +typedef union { + const struct sockaddr *__sockaddr__; + const struct sockaddr_in *__sockaddr_in__; +} __CONST_SOCKADDR_ARG __attribute__((transparent_union)); +ssize_t sendto(int, const void *, size_t, int, __CONST_SOCKADDR_ARG, socklen_t); +#else ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); +#endif typedef unsigned long nfds_t; #endif int poll(struct pollfd *, nfds_t, int); @@ -375,15 +387,13 @@ void call_send_runtime(int fd, int n) { } #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) { +void call_sendto_transparent_union(int fd, const struct sockaddr *addr, + const struct sockaddr_in *addr_in) { 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}} + sendto(fd, buf, 10, 0, addr_in, 0); + sendto(fd, buf, 11, 0, addr_in, 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
