Skip to content

Commit 3b8b6af

Browse files
committed
prevent symlink loop recursion in directory traversal
1 parent a63c5f0 commit 3b8b6af

2 files changed

Lines changed: 63 additions & 11 deletions

File tree

fuzzing/replay/file_util.cc

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
#include <cerrno>
2323
#include <cstdio>
24+
#include <set>
2425
#include <string>
26+
#include <utility>
2527

2628
#include "absl/functional/function_ref.h"
2729
#include "absl/status/status.h"
@@ -36,7 +38,36 @@ namespace {
3638

3739
absl::Status TraverseDirectory(
3840
absl::string_view path,
39-
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback) {
41+
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback,
42+
std::set<std::pair<dev_t, ino_t>>& visited);
43+
44+
absl::Status YieldFilesInternal(
45+
absl::string_view path,
46+
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback,
47+
std::set<std::pair<dev_t, ino_t>>& visited) {
48+
struct stat path_stat;
49+
if (stat(std::string(path).c_str(), &path_stat) < 0) {
50+
return ErrnoStatus(absl::StrCat("could not stat ", path), errno);
51+
}
52+
callback(path, path_stat);
53+
if (S_ISDIR(path_stat.st_mode)) {
54+
auto dir_id = std::make_pair(path_stat.st_dev, path_stat.st_ino);
55+
if (!visited.count(dir_id)) {
56+
// Prevent infinite recursion by tracking visited directories (dev,inode).
57+
visited.insert(dir_id);
58+
absl::Status status = TraverseDirectory(path, callback, visited);
59+
if (!status.ok()) {
60+
return status;
61+
}
62+
}
63+
}
64+
return absl::OkStatus();
65+
}
66+
67+
absl::Status TraverseDirectory(
68+
absl::string_view path,
69+
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback,
70+
std::set<std::pair<dev_t, ino_t>>& visited) {
4071
DIR* dir = opendir(std::string(path).c_str());
4172
if (!dir) {
4273
return ErrnoStatus(absl::StrCat("could not open directory ", path), errno);
@@ -58,7 +89,7 @@ absl::Status TraverseDirectory(
5889
continue;
5990
}
6091
const std::string entry_path = absl::StrCat(path, "/", entry_name);
61-
status.Update(YieldFiles(entry_path, callback));
92+
status.Update(YieldFilesInternal(entry_path, callback, visited));
6293
}
6394
closedir(dir);
6495
return status;
@@ -69,15 +100,8 @@ absl::Status TraverseDirectory(
69100
absl::Status YieldFiles(
70101
absl::string_view path,
71102
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback) {
72-
struct stat path_stat;
73-
if (stat(std::string(path).c_str(), &path_stat) < 0) {
74-
return ErrnoStatus(absl::StrCat("could not stat ", path), errno);
75-
}
76-
if (S_ISDIR(path_stat.st_mode)) {
77-
return TraverseDirectory(path, callback);
78-
}
79-
callback(path, path_stat);
80-
return absl::OkStatus();
103+
std::set<std::pair<dev_t, ino_t>> visited;
104+
return YieldFilesInternal(path, callback, visited);
81105
}
82106

83107
absl::Status SetFileContents(absl::string_view path,

fuzzing/replay/file_util_test.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616

1717
#include <sys/stat.h>
1818
#include <sys/types.h>
19+
#include <unistd.h>
1920

21+
#include <cerrno>
2022
#include <cstdlib>
23+
#include <cstring>
2124
#include <functional>
2225
#include <string>
2326
#include <vector>
@@ -113,6 +116,31 @@ TEST(YieldFilesTest, YieldsHiddenFilesAndDirs) {
113116
EXPECT_THAT(collected_paths, testing::SizeIs(2));
114117
}
115118

119+
TEST(YieldFilesTest, DoesNotRecurseThroughSymlinkLoop) {
120+
const std::string root_dir =
121+
absl::StrCat(getenv("TEST_TMPDIR"), "/symlink-loop-root");
122+
ASSERT_EQ(mkdir(root_dir.c_str(), 0755), 0);
123+
const std::string dir_a = absl::StrCat(root_dir, "/dirA");
124+
ASSERT_EQ(mkdir(dir_a.c_str(), 0755), 0);
125+
const std::string dir_b = absl::StrCat(root_dir, "/dirB");
126+
ASSERT_EQ(mkdir(dir_b.c_str(), 0755), 0);
127+
128+
const std::string link_to_b = absl::StrCat(dir_a, "/toB");
129+
if (symlink(dir_b.c_str(), link_to_b.c_str()) != 0) {
130+
GTEST_SKIP() << "symlink unsupported in this environment: "
131+
<< std::strerror(errno);
132+
}
133+
const std::string link_to_a = absl::StrCat(dir_b, "/toA");
134+
ASSERT_EQ(symlink(dir_a.c_str(), link_to_a.c_str()), 0);
135+
136+
std::vector<std::string> collected_paths;
137+
const absl::Status status =
138+
YieldFiles(root_dir, CollectPathsCallback(&collected_paths));
139+
EXPECT_TRUE(status.ok());
140+
EXPECT_GT(collected_paths.size(), 0);
141+
EXPECT_LE(collected_paths.size(), 6);
142+
}
143+
116144
} // namespace
117145

118146
} // namespace fuzzing

0 commit comments

Comments
 (0)