[clang] [Clang][Sema] Add fortify warnings for realpath (PR #230695)
Venkatesh Srinivasan via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 9 15:46:33 PDT 2026
https://github.com/venk-ks updated https://github.com/llvm/llvm-project/pull/230695
>From a151873c7c840653a9a8da700a6f11b4aef9e493 Mon Sep 17 00:00:00 2001
From: Venkatesh Srinivasan <venk at google.com>
Date: Fri, 9 Oct 2026 22:39:08 +0000
Subject: [PATCH 1/2] [Clang][Sema] Add fortify warnings for realpath
Teach -Wfortify-source to diagnose when realpath is called with a non-null destination buffer whose statically known size is smaller than PATH_MAX bytes.
Part of #142230
Assisted-by: Gemini
---
clang/docs/ReleaseNotes.md | 3 ++
clang/include/clang/Basic/Builtins.td | 6 +++
.../clang/Basic/DiagnosticSemaKinds.td | 5 ++
clang/lib/Sema/SemaChecking.cpp | 53 +++++++++++++++++++
clang/test/Sema/warn-fortify-source.c | 19 +++++++
5 files changed, 86 insertions(+)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 96d5e630e80dac..3a8d6712cbc605 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -359,6 +359,9 @@ features cannot lower the translation-unit ABI level;
- `-Wfortify-source` now diagnoses when `poll`, `ppoll`, or `ppoll64` is called
with a descriptor count whose total size exceeds the `fds` array size.
+- `-Wfortify-source` now diagnoses when `realpath` is called with a destination
+ buffer smaller than `PATH_MAX` bytes.
+
- The `cannot overload a member function` diagnostic now describes the previous
declaration first, matching the order in which the declarations appear in the
source. (#GH219803)
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index cd92a36049e32d..cc42c155d4b9db 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -3297,6 +3297,12 @@ def AlignedAlloc : LibBuiltin<"stdlib.h"> {
let Prototype = "void*(size_t, size_t)";
}
+// POSIX stdlib.h
+def Realpath : LibBuiltin<"stdlib.h"> {
+ let Spellings = ["realpath"];
+ let Prototype = "char*(char const* restrict, char* restrict)";
+}
+
// C99 string.h
def MemCpy : LibBuiltin<"string.h"> {
let Spellings = ["memcpy"];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 70f25c78a6f4a9..dea096c64395fc 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1059,6 +1059,11 @@ def warn_fortify_source_overread : Warning<
"size %1, but the size is %2">,
InGroup<FortifySource>;
+def warn_fortify_source_buffer_too_small : Warning<
+ "'%0' may overflow; destination buffer has size %1, but at least %2 bytes "
+ "are required">,
+ InGroup<FortifySource>;
+
def err_function_start_invalid_type: Error<
"argument must be a function">;
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 0531dfa877fbdf..5569401c025b41 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -56,6 +56,7 @@
#include "clang/Basic/SyncScope.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/Lexer.h" // TODO: Extract static functions to fix layering.
+#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Ownership.h"
@@ -1319,6 +1320,49 @@ class FortifiedBufferChecker {
const DiagnoseAsBuiltinAttr *DABAttr;
unsigned SizeTypeWidth;
};
+
+static std::optional<uint64_t> tryExpandMacroAsUInt(StringRef Macro,
+ const Preprocessor &PP) {
+ const IdentifierInfo *MacroII = PP.getIdentifierInfo(Macro);
+ if (!MacroII)
+ return std::nullopt;
+ const MacroInfo *MI = PP.getMacroInfo(MacroII);
+ if (!MI || MI->tokens_empty())
+ return std::nullopt;
+
+ ArrayRef<Token> Tokens = MI->tokens();
+ while (Tokens.size() >= 2 && Tokens.front().is(tok::l_paren) &&
+ Tokens.back().is(tok::r_paren))
+ Tokens = Tokens.drop_front().drop_back();
+ if (Tokens.size() != 1 || !Tokens.front().is(tok::numeric_constant))
+ return std::nullopt;
+
+ bool InvalidSpelling = false;
+ SmallString<32> Buffer;
+ StringRef ValueStr = PP.getSpelling(Tokens.front(), Buffer, &InvalidSpelling);
+ if (InvalidSpelling)
+ return std::nullopt;
+
+ ValueStr = ValueStr.rtrim("uUlL");
+ uint64_t Val;
+ if (ValueStr.getAsInteger(/*Radix=*/0, Val))
+ return std::nullopt;
+ return Val;
+}
+
+static std::optional<uint64_t> getPathMaxValue(const ASTContext &Ctx,
+ const Preprocessor &PP) {
+ if (std::optional<uint64_t> MacroVal = tryExpandMacroAsUInt("PATH_MAX", PP))
+ return MacroVal;
+
+ const llvm::Triple &T = Ctx.getTargetInfo().getTriple();
+ if (T.isOSLinux() || T.isOSFuchsia() || T.isOSAIX() || T.isOSHaiku())
+ return 4096;
+ if (T.isOSDarwin() || T.isOSFreeBSD() || T.isOSNetBSD() || T.isOSOpenBSD() ||
+ T.isOSSolaris())
+ return 1024;
+ return std::nullopt;
+}
} // anonymous namespace
void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
@@ -1601,6 +1645,15 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
BufferSize = Checker.ComputeSizeArgument(0);
break;
}
+ case Builtin::BIrealpath: {
+ std::optional<uint64_t> PathMax = getPathMaxValue(Context, PP);
+ if (!PathMax)
+ return;
+ DiagID = diag::warn_fortify_source_buffer_too_small;
+ AccessSize = llvm::APSInt::getUnsigned(*PathMax).extOrTrunc(SizeTypeWidth);
+ BufferSize = Checker.ComputeSizeArgument(1);
+ break;
+ }
// memchr(buf, val, size)
case Builtin::BImemchr:
case Builtin::BI__builtin_memchr: {
diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c
index 73a10070008f44..0873432e8df581 100644
--- a/clang/test/Sema/warn-fortify-source.c
+++ b/clang/test/Sema/warn-fortify-source.c
@@ -49,6 +49,7 @@ void bzero(void *dst, size_t n);
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);
+char *realpath(const char *path, char *resolved_path);
#ifdef __cplusplus
}
@@ -173,6 +174,24 @@ void call_fread_fwrite_fgets(FILE *fp) {
fgets(src, 0, fp);
}
+void call_realpath(const char *path, char *unknown_buf) {
+ char ok_buf[1024];
+ char small_buf[1023];
+ realpath(path, (char *)0);
+ realpath(path, unknown_buf);
+ realpath(path, ok_buf);
+ realpath(path, small_buf); // expected-warning {{'realpath' may overflow; destination buffer has size 1023, but at least 1024 bytes are required}}
+}
+
+#define PATH_MAX (4096)
+void call_realpath_macro(const char *path) {
+ char ok_buf[4096];
+ char small_buf[4095];
+ realpath(path, ok_buf);
+ realpath(path, small_buf); // expected-warning {{'realpath' may overflow; destination buffer has size 4095, but at least 4096 bytes are required}}
+}
+#undef PATH_MAX
+
void call_snprintf(double d, int n) {
char buf[10];
__builtin_snprintf(buf, 10, "merp");
>From d15c0ee9143158d615890c5638b325dfd4d80000 Mon Sep 17 00:00:00 2001
From: Venkatesh Srinivasan <venk at google.com>
Date: Fri, 9 Oct 2026 22:46:21 +0000
Subject: [PATCH 2/2] Simplify realpath PATH_MAX lookup to use target triple
directly
---
clang/lib/Sema/SemaChecking.cpp | 60 ++++++---------------------
clang/test/Sema/warn-fortify-source.c | 9 ----
2 files changed, 12 insertions(+), 57 deletions(-)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 5569401c025b41..ad84b5e84581e1 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -56,7 +56,6 @@
#include "clang/Basic/SyncScope.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/Lexer.h" // TODO: Extract static functions to fix layering.
-#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Ownership.h"
@@ -1320,49 +1319,6 @@ class FortifiedBufferChecker {
const DiagnoseAsBuiltinAttr *DABAttr;
unsigned SizeTypeWidth;
};
-
-static std::optional<uint64_t> tryExpandMacroAsUInt(StringRef Macro,
- const Preprocessor &PP) {
- const IdentifierInfo *MacroII = PP.getIdentifierInfo(Macro);
- if (!MacroII)
- return std::nullopt;
- const MacroInfo *MI = PP.getMacroInfo(MacroII);
- if (!MI || MI->tokens_empty())
- return std::nullopt;
-
- ArrayRef<Token> Tokens = MI->tokens();
- while (Tokens.size() >= 2 && Tokens.front().is(tok::l_paren) &&
- Tokens.back().is(tok::r_paren))
- Tokens = Tokens.drop_front().drop_back();
- if (Tokens.size() != 1 || !Tokens.front().is(tok::numeric_constant))
- return std::nullopt;
-
- bool InvalidSpelling = false;
- SmallString<32> Buffer;
- StringRef ValueStr = PP.getSpelling(Tokens.front(), Buffer, &InvalidSpelling);
- if (InvalidSpelling)
- return std::nullopt;
-
- ValueStr = ValueStr.rtrim("uUlL");
- uint64_t Val;
- if (ValueStr.getAsInteger(/*Radix=*/0, Val))
- return std::nullopt;
- return Val;
-}
-
-static std::optional<uint64_t> getPathMaxValue(const ASTContext &Ctx,
- const Preprocessor &PP) {
- if (std::optional<uint64_t> MacroVal = tryExpandMacroAsUInt("PATH_MAX", PP))
- return MacroVal;
-
- const llvm::Triple &T = Ctx.getTargetInfo().getTriple();
- if (T.isOSLinux() || T.isOSFuchsia() || T.isOSAIX() || T.isOSHaiku())
- return 4096;
- if (T.isOSDarwin() || T.isOSFreeBSD() || T.isOSNetBSD() || T.isOSOpenBSD() ||
- T.isOSSolaris())
- return 1024;
- return std::nullopt;
-}
} // anonymous namespace
void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
@@ -1646,12 +1602,20 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
break;
}
case Builtin::BIrealpath: {
- std::optional<uint64_t> PathMax = getPathMaxValue(Context, PP);
- if (!PathMax)
+ BufferSize = Checker.ComputeSizeArgument(1);
+ if (!BufferSize)
+ return;
+ const llvm::Triple &T = Context.getTargetInfo().getTriple();
+ uint64_t PathMax = 0;
+ if (T.isOSLinux() || T.isOSFuchsia() || T.isOSAIX() || T.isOSHaiku())
+ PathMax = 4096;
+ else if (T.isOSDarwin() || T.isOSFreeBSD() || T.isOSNetBSD() ||
+ T.isOSOpenBSD() || T.isOSSolaris())
+ PathMax = 1024;
+ else
return;
DiagID = diag::warn_fortify_source_buffer_too_small;
- AccessSize = llvm::APSInt::getUnsigned(*PathMax).extOrTrunc(SizeTypeWidth);
- BufferSize = Checker.ComputeSizeArgument(1);
+ AccessSize = llvm::APSInt::getUnsigned(PathMax).extOrTrunc(SizeTypeWidth);
break;
}
// memchr(buf, val, size)
diff --git a/clang/test/Sema/warn-fortify-source.c b/clang/test/Sema/warn-fortify-source.c
index 0873432e8df581..1d8fb7f7d80b9f 100644
--- a/clang/test/Sema/warn-fortify-source.c
+++ b/clang/test/Sema/warn-fortify-source.c
@@ -183,15 +183,6 @@ void call_realpath(const char *path, char *unknown_buf) {
realpath(path, small_buf); // expected-warning {{'realpath' may overflow; destination buffer has size 1023, but at least 1024 bytes are required}}
}
-#define PATH_MAX (4096)
-void call_realpath_macro(const char *path) {
- char ok_buf[4096];
- char small_buf[4095];
- realpath(path, ok_buf);
- realpath(path, small_buf); // expected-warning {{'realpath' may overflow; destination buffer has size 4095, but at least 4096 bytes are required}}
-}
-#undef PATH_MAX
-
void call_snprintf(double d, int n) {
char buf[10];
__builtin_snprintf(buf, 10, "merp");
More information about the cfe-commits
mailing list