Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions fuzzing/replay/file_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

#include <cerrno>
#include <cstdio>
#include <set>
#include <string>
#include <utility>

#include "absl/functional/function_ref.h"
#include "absl/status/status.h"
Expand All @@ -36,7 +38,36 @@ namespace {

absl::Status TraverseDirectory(
absl::string_view path,
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback) {
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback,
std::set<std::pair<dev_t, ino_t>>& visited);

absl::Status YieldFilesInternal(
absl::string_view path,
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback,
std::set<std::pair<dev_t, ino_t>>& visited) {
struct stat path_stat;
if (stat(std::string(path).c_str(), &path_stat) < 0) {
return ErrnoStatus(absl::StrCat("could not stat ", path), errno);
}
callback(path, path_stat);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

callback is here called unconditionally including on directories. This likely breaks existing behavior and fails the test for the file utility functions. You can run them on your end with the command bazel test //fuzzing/replay:file_util_test or bazel test //... to run all tests.

The direction to detect cycles on a pair of path_stat.st_dev, path_stat.st_ino makes sense to me.

if (S_ISDIR(path_stat.st_mode)) {
auto dir_id = std::make_pair(path_stat.st_dev, path_stat.st_ino);
if (!visited.count(dir_id)) {
// Prevent infinite recursion by tracking visited directories (dev,inode).
visited.insert(dir_id);
absl::Status status = TraverseDirectory(path, callback, visited);
if (!status.ok()) {
return status;
}
}
}
return absl::OkStatus();
}

absl::Status TraverseDirectory(
absl::string_view path,
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback,
std::set<std::pair<dev_t, ino_t>>& visited) {
DIR* dir = opendir(std::string(path).c_str());
if (!dir) {
return ErrnoStatus(absl::StrCat("could not open directory ", path), errno);
Expand All @@ -58,7 +89,7 @@ absl::Status TraverseDirectory(
continue;
}
const std::string entry_path = absl::StrCat(path, "/", entry_name);
status.Update(YieldFiles(entry_path, callback));
status.Update(YieldFilesInternal(entry_path, callback, visited));
}
closedir(dir);
return status;
Expand All @@ -69,15 +100,8 @@ absl::Status TraverseDirectory(
absl::Status YieldFiles(
absl::string_view path,
absl::FunctionRef<void(absl::string_view, const struct stat&)> callback) {
struct stat path_stat;
if (stat(std::string(path).c_str(), &path_stat) < 0) {
return ErrnoStatus(absl::StrCat("could not stat ", path), errno);
}
if (S_ISDIR(path_stat.st_mode)) {
return TraverseDirectory(path, callback);
}
callback(path, path_stat);
return absl::OkStatus();
std::set<std::pair<dev_t, ino_t>> visited;
return YieldFilesInternal(path, callback, visited);
}

absl::Status SetFileContents(absl::string_view path,
Expand Down
28 changes: 28 additions & 0 deletions fuzzing/replay/file_util_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <string>
#include <vector>
Expand Down Expand Up @@ -113,6 +116,31 @@ TEST(YieldFilesTest, YieldsHiddenFilesAndDirs) {
EXPECT_THAT(collected_paths, testing::SizeIs(2));
}

TEST(YieldFilesTest, DoesNotRecurseThroughSymlinkLoop) {
const std::string root_dir =
absl::StrCat(getenv("TEST_TMPDIR"), "/symlink-loop-root");
ASSERT_EQ(mkdir(root_dir.c_str(), 0755), 0);
const std::string dir_a = absl::StrCat(root_dir, "/dirA");
ASSERT_EQ(mkdir(dir_a.c_str(), 0755), 0);
const std::string dir_b = absl::StrCat(root_dir, "/dirB");
ASSERT_EQ(mkdir(dir_b.c_str(), 0755), 0);

const std::string link_to_b = absl::StrCat(dir_a, "/toB");
if (symlink(dir_b.c_str(), link_to_b.c_str()) != 0) {
GTEST_SKIP() << "symlink unsupported in this environment: "
<< std::strerror(errno);
}
const std::string link_to_a = absl::StrCat(dir_b, "/toA");
ASSERT_EQ(symlink(dir_a.c_str(), link_to_a.c_str()), 0);

std::vector<std::string> collected_paths;
const absl::Status status =
YieldFiles(root_dir, CollectPathsCallback(&collected_paths));
EXPECT_TRUE(status.ok());
EXPECT_GT(collected_paths.size(), 0);
EXPECT_LE(collected_paths.size(), 6);
Comment on lines +140 to +141
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests would need one or two normal files after adjusting the callback handling. Also, is there a reason to assert a range of size values instead of the exact number of expected files?

}

} // namespace

} // namespace fuzzing