Steps to reproduce
This is a code-level bug found via static analysis (flawfinder CWE-362/CWE-20, CERT POS30-C). Two readlink("/proc/self/exe") call sites have correctness issues.
Bug 1 — Undersized buffer in engine/src/flutter/fml/platform/linux/paths_linux.cc:12-19:
- Deploy a Flutter application to a Linux system where the executable path exceeds 255 bytes (e.g. a deeply nested directory or long directory names).
- The application calls
fml::paths::GetExecutablePath().
- The returned path is silently truncated to 255 bytes because the buffer is hardcoded to 255 instead of
PATH_MAX (4096).
Bug 2 — Missing error check in engine/src/flutter/shell/platform/common/path_utils.cc:24-31:
- Run a Flutter desktop application on a Linux system where
/proc is not mounted or /proc/self/exe is otherwise unavailable (e.g. certain container configurations, restricted sandboxes).
readlink("/proc/self/exe") returns -1.
- The code checks
length > PATH_MAX which does not catch -1.
std::string(buffer, (size_t)-1) is called — this is undefined behavior ((size_t)-1 = 18446744073709551615 on 64-bit), causing a crash or massive allocation attempt.
Both bugs have been present since the engine monorepo merge (7e0bed752f3, 2023-04-25). path_utils.cc dates to 2015 (ad9b1352171).
Existing issue search: We searched for related issues before filing. The following are related but do not identify these specific bugs:
Expected results
GetExecutablePath() should use a PATH_MAX-sized buffer and return {false, ""} on any readlink error.
GetExecutableDirectory() should return an empty path on any readlink error, without triggering undefined behavior.
Both should match Chromium's ReadSymbolicLink() pattern in base/files/file_util_posix.cc:
char buf[PATH_MAX];
ssize_t count = ::readlink(symlink_path.value().c_str(), buf, std::size(buf));
bool error = count <= 0;
if (error) {
target_path->clear();
return false;
}
*target_path = FilePath(FilePath::StringType(buf, static_cast<size_t>(count)));
Actual results
Bug 1 (paths_linux.cc): Executable paths longer than 255 bytes are silently truncated. The application proceeds with an incorrect path, potentially leading to failures resolving assets, ICU data, or the Dart VM snapshot.
Bug 2 (path_utils.cc): When readlink fails (returns -1), the code constructs std::string(buffer, (size_t)-1) which is undefined behavior. In practice this causes either:
- An
std::bad_alloc exception (attempting to allocate ~18 exabytes)
- A segfault
- Memory corruption
The current code:
// paths_linux.cc — buffer too small
const int path_size = 255; // should be PATH_MAX (4096)
char path[path_size] = {0};
auto read_size = ::readlink("/proc/self/exe", path, path_size);
// path_utils.cc — missing error check
ssize_t length = readlink("/proc/self/exe", buffer, sizeof(buffer));
if (length > PATH_MAX) { // does not catch length == -1
return std::filesystem::path();
}
std::filesystem::path executable_path(std::string(buffer, length)); // UB when length == -1
Code sample
Code sample
This is a C++ engine bug, not reproducible from Dart. The affected code:
engine/src/flutter/fml/platform/linux/paths_linux.cc (buffer too small):
std::pair<bool, std::string> GetExecutablePath() {
const int path_size = 255; // BUG: should be PATH_MAX (4096)
char path[path_size] = {0};
auto read_size = ::readlink("/proc/self/exe", path, path_size);
if (read_size == -1) {
return {false, ""};
}
return {true, std::string{path, static_cast<size_t>(read_size)}};
}
engine/src/flutter/shell/platform/common/path_utils.cc (missing error check):
std::filesystem::path GetExecutableDirectory() {
char buffer[PATH_MAX + 1];
ssize_t length = readlink("/proc/self/exe", buffer, sizeof(buffer));
if (length > PATH_MAX) { // BUG: does not catch length == -1
return std::filesystem::path();
}
std::filesystem::path executable_path(std::string(buffer, length)); // UB when length is -1
return executable_path.remove_filename();
}
Chromium's correct implementation (base/files/file_util_posix.cc:695-719):
bool ReadSymbolicLink(const FilePath& symlink_path, FilePath* target_path) {
DCHECK(!symlink_path.empty());
DCHECK(target_path);
char buf[PATH_MAX];
ssize_t count = ::readlink(symlink_path.value().c_str(), buf, std::size(buf));
bool error = count <= 0;
if (error) {
target_path->clear();
return false;
}
*target_path = FilePath(FilePath::StringType(buf, static_cast<size_t>(count)));
return true;
}
Screenshots or Video
Screenshots / Video demonstration
Not applicable — this is a C++ engine code bug found by static analysis, not a visual issue.
Logs
Logs
Static analysis output that identified the bugs:
$ flawfinder engine/src/flutter/fml/platform/linux/paths_linux.cc
engine/src/flutter/fml/platform/linux/paths_linux.cc:15:22: [5] (race) readlink:
This accepts filename arguments; if an attacker can move those files or
change the link content, a race condition results. Also, it does not
terminate with ASCII NUL. (CWE-362, CWE-20). Reconsider approach.
$ flawfinder engine/src/flutter/shell/platform/common/path_utils.cc
engine/src/flutter/shell/platform/common/path_utils.cc:26:20: [5] (race) readlink:
This accepts filename arguments; if an attacker can move those files or
change the link content, a race condition results. Also, it does not
terminate with ASCII NUL. (CWE-362, CWE-20). Reconsider approach.
Manual review against CERT POS30-C ("Use the readlink() function properly") confirmed:
paths_linux.cc: 255-byte buffer violates the standard (should be PATH_MAX)
path_utils.cc: Missing -1 return check leads to undefined behavior
Note: The CWE-362 (TOCTOU race) flagged by flawfinder is a false positive for /proc/self/exe specifically — it is a kernel-managed symlink that cannot be redirected by userspace. The real bugs are the undersized buffer and the missing error check.
Flutter Doctor output
Doctor output
[!] Flutter (Channel [user-branch], 3.43.0-1.0.pre-391, on NixOS 26.05 (Yarara) 6.19.9, locale en_US.UTF-8) [51ms]
! Flutter version 3.43.0-1.0.pre-391 on channel [user-branch] at /home/das/Downloads/flutter
Currently on an unknown channel. Run `flutter channel` to switch to an official channel.
If that doesn't fix the issue, reinstall Flutter by following instructions at https://flutter.dev/setup.
• Framework revision c589dfffda (16 hours ago), 2026-03-31 22:16:58 -0400
• Engine revision be1e70f0a8
• Dart version 3.12.0 (build 3.12.0-304.0.dev)
• DevTools version 2.57.0-dev.0
[!] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
• Android SDK at /home/das/Android/Sdk
[✓] Chrome - develop for the web
• CHROME_EXECUTABLE = /nix/store/5fyi5df2lfhmvjgvlkdm5b46rbwxibsy-google-chrome-146.0.7680.153/bin/google-chrome-stable
[✗] Linux toolchain - develop for Linux desktop
✗ clang++ is required for Linux development.
✗ CMake is required for Linux development.
✗ ninja is required for Linux development.
[✓] Connected device (2 available)
• Linux (desktop) • linux • linux-x64 • NixOS 26.05 (Yarara) 6.19.9
• Chrome (web) • chrome • web-javascript • Google Chrome 146.0.7680.153
[✓] Network resources
• All expected network resources are available.
! Doctor found issues in 3 categories.
Steps to reproduce
This is a code-level bug found via static analysis (flawfinder CWE-362/CWE-20, CERT POS30-C). Two
readlink("/proc/self/exe")call sites have correctness issues.Bug 1 — Undersized buffer in
engine/src/flutter/fml/platform/linux/paths_linux.cc:12-19:fml::paths::GetExecutablePath().PATH_MAX(4096).Bug 2 — Missing error check in
engine/src/flutter/shell/platform/common/path_utils.cc:24-31:/procis not mounted or/proc/self/exeis otherwise unavailable (e.g. certain container configurations, restricted sandboxes).readlink("/proc/self/exe")returns-1.length > PATH_MAXwhich does not catch-1.std::string(buffer, (size_t)-1)is called — this is undefined behavior ((size_t)-1= 18446744073709551615 on 64-bit), causing a crash or massive allocation attempt.Both bugs have been present since the engine monorepo merge (
7e0bed752f3, 2023-04-25).path_utils.ccdates to 2015 (ad9b1352171).Existing issue search: We searched for related issues before filing. The following are related but do not identify these specific bugs:
readlinkfailures on Linux, but does not identify the missing error check inpath_utils.ccthat would cause undefined behavior in exactly that scenario.readlink -fissues in CocoaPods scripts on Xcode 14.3. Unrelated (shell script, not C++ engine code).Expected results
GetExecutablePath()should use aPATH_MAX-sized buffer and return{false, ""}on anyreadlinkerror.GetExecutableDirectory()should return an empty path on anyreadlinkerror, without triggering undefined behavior.Both should match Chromium's
ReadSymbolicLink()pattern inbase/files/file_util_posix.cc:Actual results
Bug 1 (
paths_linux.cc): Executable paths longer than 255 bytes are silently truncated. The application proceeds with an incorrect path, potentially leading to failures resolving assets, ICU data, or the Dart VM snapshot.Bug 2 (
path_utils.cc): Whenreadlinkfails (returns-1), the code constructsstd::string(buffer, (size_t)-1)which is undefined behavior. In practice this causes either:std::bad_allocexception (attempting to allocate ~18 exabytes)The current code:
Code sample
Code sample
This is a C++ engine bug, not reproducible from Dart. The affected code:
engine/src/flutter/fml/platform/linux/paths_linux.cc(buffer too small):engine/src/flutter/shell/platform/common/path_utils.cc(missing error check):Chromium's correct implementation (
base/files/file_util_posix.cc:695-719):Screenshots or Video
Screenshots / Video demonstration
Not applicable — this is a C++ engine code bug found by static analysis, not a visual issue.
Logs
Logs
Static analysis output that identified the bugs:
Manual review against CERT POS30-C ("Use the readlink() function properly") confirmed:
paths_linux.cc: 255-byte buffer violates the standard (should bePATH_MAX)path_utils.cc: Missing-1return check leads to undefined behaviorNote: The CWE-362 (TOCTOU race) flagged by flawfinder is a false positive for
/proc/self/exespecifically — it is a kernel-managed symlink that cannot be redirected by userspace. The real bugs are the undersized buffer and the missing error check.Flutter Doctor output
Doctor output