Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ public static boolean validFileName(String fileName) {
return true;
}

public static boolean validFileNameExcludingAstrix(String fileName) {
public static boolean validFileNameExcludingAsterisk(String fileName) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is breaking the cross cluster replication plugin build

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

for (int i = 0; i < fileName.length(); i++) {
char c = fileName.charAt(i);
if (c != '*' && INVALID_FILENAME_CHARS.contains(c)) {
Expand All @@ -331,6 +331,27 @@ public static boolean validFileNameExcludingAstrix(String fileName) {
return true;
}

public static boolean validFileNameExcludingSlash(String fileName) {
Comment thread
xuxiong1 marked this conversation as resolved.
Resolved
// Reject absolute paths
if (fileName.startsWith("/") || fileName.startsWith("\\")) {
return false;
}
// Reject invalid characters (excluding slashes)
for (int i = 0; i < fileName.length(); i++) {
char c = fileName.charAt(i);
if (c != '/' && c != '\\' && INVALID_FILENAME_CHARS.contains(c)) {
return false;
}
}
// Reject path traversal: ".." as a path segment
for (String segment : fileName.split("[/\\\\]")) {
if (segment.equals("..")) {
return false;
}
}
return true;
}

/**
* Copy the given Collection into a String array.
* The Collection must contain String elements only.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,38 @@ public void testIsDigits() {
assertFalse(Strings.isDigits("123.4"));
assertFalse(Strings.isDigits("123f"));
}

public void testValidFileNameExcludingSlash() {
// Valid cases - no invalid chars
assertTrue(Strings.validFileNameExcludingSlash("validfile"));
assertTrue(Strings.validFileNameExcludingSlash("file123"));
assertTrue(Strings.validFileNameExcludingSlash("file-name.txt"));

// Valid cases - relative slashes are allowed
assertTrue(Strings.validFileNameExcludingSlash("path/to/file"));
assertTrue(Strings.validFileNameExcludingSlash("path\\to\\file"));
assertTrue(Strings.validFileNameExcludingSlash("mixed/path\\file"));

// Invalid cases - absolute paths must be rejected
assertFalse(Strings.validFileNameExcludingSlash("/absolute/path"));
assertFalse(Strings.validFileNameExcludingSlash("\\absolute\\path"));

// Invalid cases - path traversal must be rejected
assertFalse(Strings.validFileNameExcludingSlash("../etc/passwd"));
assertFalse(Strings.validFileNameExcludingSlash("path/../../etc/passwd"));
assertFalse(Strings.validFileNameExcludingSlash("path\\..\\..\\secret"));

// Invalid cases - other invalid chars should still fail
assertFalse(Strings.validFileNameExcludingSlash("file*name"));
assertFalse(Strings.validFileNameExcludingSlash("file?name"));
assertFalse(Strings.validFileNameExcludingSlash("file\"name"));
assertFalse(Strings.validFileNameExcludingSlash("file<name"));
assertFalse(Strings.validFileNameExcludingSlash("file>name"));
assertFalse(Strings.validFileNameExcludingSlash("file|name"));
assertFalse(Strings.validFileNameExcludingSlash("file name")); // space
assertFalse(Strings.validFileNameExcludingSlash("file,name")); // comma

// Edge case - path with invalid char
assertFalse(Strings.validFileNameExcludingSlash("path/to/file*name"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ private void validateFieldPatterns(String processorTag, List<String> patterns, S
if (fieldPattern.startsWith("_")) {
validationErrors.add(patternKey + " [" + fieldPattern + "] must not start with '_'");
}
if (Strings.validFileNameExcludingAstrix(fieldPattern) == false) {
if (Strings.validFileNameExcludingAsterisk(fieldPattern) == false) {
validationErrors.add(
patternKey + " [" + fieldPattern + "] must not contain the following characters " + Strings.INVALID_FILENAME_CHARS
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1617,7 +1617,7 @@ private void validate(String name, @Nullable Settings settings, List<String> ind
if (indexPattern.startsWith("_")) {
validationErrors.add("index_pattern [" + indexPattern + "] must not start with '_'");
}
if (Strings.validFileNameExcludingAstrix(indexPattern) == false) {
if (Strings.validFileNameExcludingAsterisk(indexPattern) == false) {
validationErrors.add(
"index_pattern [" + indexPattern + "] must not contain the following characters " + Strings.INVALID_FILENAME_CHARS
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ public static FileInfo fromXContent(XContentParser parser) throws IOException {
// Verify that file information is complete
if (name == null || Strings.validFileName(name) == false) {
throw new OpenSearchParseException("missing or invalid file name [" + name + "]");
} else if (physicalName == null || Strings.validFileName(physicalName) == false) {
} else if (physicalName == null || Strings.validFileNameExcludingSlash(physicalName) == false) {
throw new OpenSearchParseException("missing or invalid physical file name [" + physicalName + "]");
} else if (length < 0) {
throw new OpenSearchParseException("missing or invalid file length");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,50 @@ public void testGetPartSize() {
assertEquals(numBytes, metadata.length());
}
}

public void testPhysicalNameWithSlash() throws IOException {
// Physical names with slashes should now be valid
String name = "foobar";
String physicalName = "path/to/_foobar"; // contains slash
long length = 100;

XContentBuilder builder = MediaTypeRegistry.contentBuilder(MediaTypeRegistry.JSON);
builder.startObject();
builder.field(FileInfo.NAME, name);
builder.field(FileInfo.PHYSICAL_NAME, physicalName);
builder.field(FileInfo.LENGTH, length);
builder.field(FileInfo.WRITTEN_BY, Version.LATEST.toString());
builder.field(FileInfo.CHECKSUM, "666");
builder.endObject();
byte[] xContent = BytesReference.toBytes(BytesReference.bytes(builder));

// Should parse successfully without throwing
final BlobStoreIndexShardSnapshot.FileInfo parsedInfo;
try (XContentParser parser = createParser(JsonXContent.jsonXContent, xContent)) {
parser.nextToken();
parsedInfo = BlobStoreIndexShardSnapshot.FileInfo.fromXContent(parser);
}
assertThat(parsedInfo.physicalName(), equalTo(physicalName));
}

public void testPhysicalNameWithPathTraversal() throws IOException {
// Path traversal and absolute paths in physical name should be rejected
String[] maliciousNames = { "../etc/passwd", "path/../../secret", "/absolute/path" };
for (String malicious : maliciousNames) {
XContentBuilder builder = MediaTypeRegistry.contentBuilder(MediaTypeRegistry.JSON);
builder.startObject();
builder.field(FileInfo.NAME, "segments_1");
builder.field(FileInfo.PHYSICAL_NAME, malicious);
builder.field(FileInfo.LENGTH, 100);
builder.field(FileInfo.WRITTEN_BY, Version.LATEST.toString());
builder.field(FileInfo.CHECKSUM, "666");
builder.endObject();
byte[] xContent = BytesReference.toBytes(BytesReference.bytes(builder));

try (XContentParser parser = createParser(JsonXContent.jsonXContent, xContent)) {
parser.nextToken();
expectThrows(OpenSearchParseException.class, () -> FileInfo.fromXContent(parser));
}
}
}
}
Loading