Skip to content

Commit 057c705

Browse files
committed
further restrict safe set
1 parent 4a1cad7 commit 057c705

2 files changed

Lines changed: 27 additions & 16 deletions

File tree

src/java/org/apache/cassandra/db/ColumnFamilyStore.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1896,12 +1896,13 @@ private void validateSnapshotName(String snapshotName)
18961896
FILENAME_LENGTH, snapshotName.length(), snapshotName));
18971897
}
18981898

1899-
// Allowed characters follow the AWS S3 "Safe characters" set documented at
1900-
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines :
1901-
// 0-9 a-z A-Z ! - _ . * ' ( )
1902-
// The path separator '/' is intentionally excluded,
1899+
// Allowed characters are a conservative subset of the AWS S3 "Safe characters" set
1900+
// (https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines):
1901+
// 0-9 a-z A-Z - _ .
1902+
// The remaining S3-safe characters (! * ' ( )) are intentionally excluded as they are
1903+
// shell-significant and error-prone in paths, and the path separator '/' is excluded too,
19031904
// which is what blocks traversal attempts such as "../../mysnapshot"
1904-
if (!Pattern.compile("[a-zA-Z0-9!_.*'()-]+").matcher(snapshotName).matches())
1905+
if (!Pattern.compile("[a-zA-Z0-9_.-]+").matcher(snapshotName).matches())
19051906
{
19061907
throw new IllegalArgumentException("Snapshot name contains illegal characters: " + snapshotName);
19071908
}

test/unit/org/apache/cassandra/db/SnapshotTest.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,8 @@ public void testSnapshotNameValidation()
7373
assertThatCode(() -> cfs.snapshot("a_tag_1and_something2-more")).doesNotThrowAnyException();
7474
assertThatCode(() -> cfs.snapshot(repeat('a', SchemaConstants.FILENAME_LENGTH))).doesNotThrowAnyException();
7575

76-
// AWS S3 "Safe characters" accepted by the relaxed allowlist:
77-
// ! . * ' ( )
78-
// See https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html#object-key-guidelines
76+
// Only alphanumerics, '-', '_' and '.' are accepted.
7977
assertThatCode(() -> cfs.snapshot("snap.2026-05-20")).doesNotThrowAnyException();
80-
assertThatCode(() -> cfs.snapshot("important!")).doesNotThrowAnyException();
81-
assertThatCode(() -> cfs.snapshot("backup*")).doesNotThrowAnyException();
82-
assertThatCode(() -> cfs.snapshot("o'snap")).doesNotThrowAnyException();
83-
assertThatCode(() -> cfs.snapshot("snap(1)")).doesNotThrowAnyException();
84-
assertThatCode(() -> cfs.snapshot("!._-*'()")).doesNotThrowAnyException();
8578
// Dots embedded in a name are not traversal: with '/' excluded, "a..tag" is just a literal directory.
8679
assertThatCode(() -> cfs.snapshot("a..tag")).doesNotThrowAnyException();
8780

@@ -91,12 +84,26 @@ public void testSnapshotNameValidation()
9184
.hasMessage("Snapshot name must not be more than 255 characters long for " +
9285
"resolved snapshot name (got 256 characters for \"" + tooLong + "\")");
9386

94-
// '/' is not in the S3-safe set; this is what kills traversal attempts like "../../mysnapshot".
87+
// '/' is not in the allowed set; this is what kills traversal attempts like "../../mysnapshot".
9588
assertThatThrownBy(() -> cfs.snapshot("a" + sep + "tag"))
9689
.isInstanceOf(IllegalArgumentException.class)
9790
.hasMessage("Snapshot name cannot contain " + sep);
9891

99-
// Other characters outside the S3-safe set must still be rejected.
92+
// The shell-significant S3 "safe" characters (! * ' ( )) are deliberately NOT allowed.
93+
assertThatThrownBy(() -> cfs.snapshot("important!"))
94+
.isInstanceOf(IllegalArgumentException.class)
95+
.hasMessage("Snapshot name contains illegal characters: important!");
96+
assertThatThrownBy(() -> cfs.snapshot("backup*"))
97+
.isInstanceOf(IllegalArgumentException.class)
98+
.hasMessage("Snapshot name contains illegal characters: backup*");
99+
assertThatThrownBy(() -> cfs.snapshot("o'snap"))
100+
.isInstanceOf(IllegalArgumentException.class)
101+
.hasMessage("Snapshot name contains illegal characters: o'snap");
102+
assertThatThrownBy(() -> cfs.snapshot("snap(1)"))
103+
.isInstanceOf(IllegalArgumentException.class)
104+
.hasMessage("Snapshot name contains illegal characters: snap(1)");
105+
106+
// Other characters outside the allowed set must still be rejected.
100107
assertThatThrownBy(() -> cfs.snapshot("a tag"))
101108
.isInstanceOf(IllegalArgumentException.class)
102109
.hasMessage("Snapshot name contains illegal characters: a tag");
@@ -118,9 +125,12 @@ public void testSnapshotNameValidation()
118125
{
119126
p.set(CassandraRelevantProperties.SNAPSHOT_NAME_VALIDATION, false);
120127

121-
// Previously-rejected characters are now accepted: space, ':', and other non-S3-safe chars.
128+
// The character check is bypassed entirely: space, ':', and the now-disallowed
129+
// shell-significant characters (! * ' ( )) are all accepted.
122130
assertThatCode(() -> cfs.snapshot("a tag")).doesNotThrowAnyException();
123131
assertThatCode(() -> cfs.snapshot("a:tag")).doesNotThrowAnyException();
132+
assertThatCode(() -> cfs.snapshot("important!")).doesNotThrowAnyException();
133+
assertThatCode(() -> cfs.snapshot("snap(1)")).doesNotThrowAnyException();
124134

125135
// Path separator and "." / ".." rejections are unconditional — they guard against
126136
// traversal regardless of the toggle.

0 commit comments

Comments
 (0)