Introduction: The Overlooked Character That Opened a Door
In TensorFlow's data service component, we discovered a high-severity path traversal vulnerability in tensorflow/core/data/service/dispatcher_impl.cc at line 155 and surrounding validation logic. The dispatcher's ValidateDatasetId() function was supposed to prevent attackers from escaping the root directory where dataset snapshots are stored—but it had a critical blind spot: it only rejected forward slashes (/), completely missing backslash (\) characters.
This seemingly minor omission created a two-step exploitation chain. An attacker could craft a dataset ID like ..\\..\\etc\\passwd and pass it through the gRPC API. On non-Windows platforms (Linux, macOS, etc.), when this path was later resolved by CleanPath() or used in file operations, the backslashes would be interpreted as escape sequences or preserved in ways that allowed directory traversal. The snapshot manager would validate that the cleaned path started with ../ but wouldn't catch embedded .. segments that backslash escaping could preserve.
Why this matters: The data service handles sensitive machine learning dataset snapshots. An attacker exploiting this could read files outside the intended snapshot directory, potentially accessing model weights, training data, or other sensitive artifacts.
The Vulnerability Explained: When Validation Assumes Too Much
Let's examine the vulnerable code from the original dispatcher_impl.cc:
absl::Status ValidateDatasetId(const std::string& dataset_id) {
if (absl::StrContains(dataset_id, '/')) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid dataset ID: ", dataset_id,
". Dataset IDs must not contain '/'."));
}
#if defined(_WIN32)
if (absl::StrContains(dataset_id, '\\') ||
absl::StrContains(dataset_id, ':')) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid dataset ID: ", dataset_id,
". Dataset IDs must not contain '\\' or ':'."));
}
#endif
return absl::OkStatus();
}
The problem: The backslash check only executes on Windows (#if defined(_WIN32)). On Linux/macOS, a dataset ID containing backslashes passes validation without any rejection. An attacker can submit:
dataset_id = "..\\..\\etc\\passwd"
The function returns OkStatus() on non-Windows systems because the backslash check is platform-gated. When this dataset_id is later used in file path operations, the backslashes can:
- Serve as escape characters in certain contexts (like when paths are parsed by snapshot management code)
- Remain in the path string and confuse
CleanPath()if it doesn't normalize backslashes on all platforms - Evade the suffix validation that checks
if (cleaned_path.find("../") == string::npos)
The snapshot manager's validation was also insufficient:
// Original snapshot validation (incomplete)
if (absl::StrContains(cleaned_path, "../")) {
return absl::InvalidArgumentError("Invalid path");
}
This checks if the cleaned path contains the ../ prefix but doesn't verify that the resolved path stays within the root directory after all transformations.
Attack scenario:
1. Attacker sends RegisterDataset gRPC request with dataset_id = "..\\..\\sensitive_data"
2. ValidateDatasetId() passes on Linux (backslash check skipped)
3. Dispatcher stores the dataset with this ID
4. Later, when creating snapshots, the path is constructed as /data/snapshots/..\\..\\sensitive_data
5. Depending on path resolution logic, the backslashes allow escape to /data/sensitive_data or higher directories
6. Attacker retrieves files outside the intended snapshot directory
The Fix: Platform-Independent Validation
The fix moves backslash validation outside the Windows-only block, ensuring uniform protection on all platforms:
absl::Status ValidateDatasetId(const std::string& dataset_id) {
if (absl::StrContains(dataset_id, '/')) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid dataset ID: ", dataset_id,
". Dataset IDs must not contain '/'."));
}
+ if (absl::StrContains(dataset_id, '\\')) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid dataset ID: ", dataset_id,
+ ". Dataset IDs must not contain '\\'."));
+ }
#if defined(_WIN32)
- if (absl::StrContains(dataset_id, '\\') ||
- absl::StrContains(dataset_id, ':')) {
+ if (absl::StrContains(dataset_id, ':')) {
return absl::InvalidArgumentError(
absl::StrCat("Invalid dataset ID: ", dataset_id,
- ". Dataset IDs must not contain '\\' or ':'."));
+ ". Dataset IDs must not contain ':'."));
}
#endif
return absl::OkStatus();
}
Key changes:
-
Lines 208-211 (new): Backslash check now runs on all platforms, not just Windows. Any dataset ID containing
\is rejected withInvalidArgumentError. -
Line 216 (removed from Windows block): The duplicate backslash check is removed from the Windows-specific section since it's now handled universally.
-
Lines 218-220 (simplified): Windows validation now only checks for
:(colon), which is a reserved character on Windows filesystems.
Why this works:
- Universal protection: The backslash is rejected the same way on Linux, macOS, and Windows
- Defense in depth: Combined with the existing forward-slash check, this prevents both common path traversal sequences (../ and ..\)
- No false positives: Dataset IDs are typically alphanumeric with underscores or hyphens; legitimate use cases never need backslashes
The test regression suite confirms the fix:
TEST_P(PathTraversalTest, DatasetIdMustNotEscapeRootDirectory) {
std::string dataset_id = GetParam();
absl::Status status = tensorflow::data::ValidateDatasetId(dataset_id);
if (dataset_id == "valid_dataset") {
EXPECT_TRUE(status.ok());
} else {
EXPECT_FALSE(status.ok());
EXPECT_TRUE(absl::IsInvalidArgument(status));
}
}
INSTANTIATE_TEST_SUITE_P(
AdversarialInputs,
PathTraversalTest,
::testing::Values(
"..\\..\\etc\\passwd", // Exact exploit
"....//....//etc/passwd", // Embedded traversal
"%2e%2e%2fetc%2fpasswd", // URL encoded
"valid_dataset" // Valid baseline
)
);
All adversarial inputs now return InvalidArgument status, while legitimate dataset IDs pass through.
Prevention & Best Practices
1. Never Assume Path Semantics Are Platform-Independent
Backslashes are Windows path separators, but they can be meaningful on Unix-like systems too. When validating paths or path components, reject both / and \ uniformly, regardless of the platform.
2. Whitelist Valid Characters Instead of Blacklisting
The current fix rejects backslashes and forward slashes, but an even stronger approach is to whitelist allowed characters:
// Better: Whitelist alphanumerics, underscores, hyphens only
bool IsValidDatasetId(const std::string& id) {
for (char c : id) {
if (!std::isalnum(c) && c != '_' && c != '-') {
return false;
}
}
return true;
}
3. Use absl::CleanPath() + Directory Verification
After constructing a path, verify it stays within the intended root:
std::string root = "/data/snapshots";
std::string user_path = absl::CleanPath(absl::StrCat(root, "/", dataset_id));
// Verify the resolved path stays within root
if (!absl::StrContains(user_path, root)) {
return absl::InvalidArgumentError("Path escapes root directory");
}
4. Static Analysis Integration
Use tools like Semgrep or Orbis AppSec to detect this pattern automatically:
rules:
- id: path-traversal-insufficient-validation
patterns:
- pattern: |
if (absl::StrContains($path, '/')) {
return absl::InvalidArgumentError(...);
}
// Missing backslash check
// ...
StrCat($root, "/", $user_input)
message: "Path validation missing backslash check"
languages: [cpp]
severity: HIGH
5. Test with Platform-Specific Exploit Cases
Include regression tests that cover both forward and backward slash variants, especially when the code runs on multiple platforms.
Key Takeaways
-
Never gatekeep security behind platform-specific preprocessor directives: The Windows-only backslash check meant non-Windows systems had no defense against
\-based path traversal. -
Path components are not just data: Dataset IDs, filenames, and path segments carry semantic meaning. A character that's "harmless" on one platform (
\on Unix) can be dangerous when it reaches path-handling code. -
Validation must be uniform, not platform-aware: If a character is unsafe for a path component on any platform, reject it on all platforms.
-
The
CleanPath()function is not sufficient alone: Even after normalization, downstream code must verify the result stays within boundaries. Validation is a layered defense. -
Backslash validation belonged at the entry point, not behind conditional compilation: The dispatcher should have rejected backslashes universally in
ValidateDatasetId(), not delegated it to platform-specific blocks.
How Orbis AppSec Detected This
Source: Dataset ID parameter in GetOrRegisterDataset gRPC request (user-controlled input from API calls)
Sink: File path construction in snapshot_manager.cc where the dataset_id is concatenated into snapshot directory paths without sufficient validation
Missing control: ValidateDatasetId() only checked for forward slashes on all platforms, relegating backslash validation to Windows-only code (#if defined(_WIN32))
CWE: CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Fix: Move the backslash check outside the Windows-specific preprocessor block, adding lines 208-211 to reject backslashes uniformly on all platforms before platform-specific validation
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
Path traversal vulnerabilities thrive on incomplete assumptions—in this case, the assumption that backslash validation was Windows-only. The TensorFlow data service fix demonstrates a critical lesson: security controls must be applied uniformly across all platforms, and path-related validation cannot rely on preprocessor directives to selectively enable or disable critical checks.
As developers, when we implement path validation, we should:
1. Reject both / and \ universally
2. Test on multiple platforms
3. Use whitelist validation when possible
4. Verify final resolved paths against root directory boundaries
5. Integrate static analysis to catch these patterns early
The three-line fix in dispatcher_impl.cc prevents remote directory traversal in TensorFlow's snapshot service. By moving backslash validation to the universal code path, the team closed a gap that platform-specific assumptions had left open.