Skip to content

Commit 6de541f

Browse files
committed
restrict safe set
1 parent cf7cb27 commit 6de541f

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
@@ -2189,12 +2189,13 @@ private void validateSnapshotName(String snapshotName)
21892189
FILENAME_LENGTH, snapshotName.length(), snapshotName));
21902190
}
21912191

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

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

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

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

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

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

97-
// Other characters outside the S3-safe set must still be rejected.
90+
// The shell-significant S3 "safe" characters (! * ' ( )) are deliberately NOT allowed.
91+
assertThatThrownBy(() -> cfs.snapshot("important!"))
92+
.isInstanceOf(IllegalArgumentException.class)
93+
.hasMessage("Snapshot name contains illegal characters: important!");
94+
assertThatThrownBy(() -> cfs.snapshot("backup*"))
95+
.isInstanceOf(IllegalArgumentException.class)
96+
.hasMessage("Snapshot name contains illegal characters: backup*");
97+
assertThatThrownBy(() -> cfs.snapshot("o'snap"))
98+
.isInstanceOf(IllegalArgumentException.class)
99+
.hasMessage("Snapshot name contains illegal characters: o'snap");
100+
assertThatThrownBy(() -> cfs.snapshot("snap(1)"))
101+
.isInstanceOf(IllegalArgumentException.class)
102+
.hasMessage("Snapshot name contains illegal characters: snap(1)");
103+
104+
// Other characters outside the allowed set must still be rejected.
98105
assertThatThrownBy(() -> cfs.snapshot("a tag"))
99106
.isInstanceOf(IllegalArgumentException.class)
100107
.hasMessage("Snapshot name contains illegal characters: a tag");
@@ -114,9 +121,12 @@ public void testSnapshotNameValidation()
114121

115122
try (WithProperties p = new WithProperties().set(CassandraRelevantProperties.SNAPSHOT_NAME_VALIDATION, false))
116123
{
117-
// Previously-rejected characters are now accepted: space, ':', and other non-S3-safe chars.
124+
// The character check is bypassed entirely: space, ':', and the now-disallowed
125+
// shell-significant characters (! * ' ( )) are all accepted.
118126
assertThatCode(() -> cfs.snapshot("a tag")).doesNotThrowAnyException();
119127
assertThatCode(() -> cfs.snapshot("a:tag")).doesNotThrowAnyException();
128+
assertThatCode(() -> cfs.snapshot("important!")).doesNotThrowAnyException();
129+
assertThatCode(() -> cfs.snapshot("snap(1)")).doesNotThrowAnyException();
120130

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

0 commit comments

Comments
 (0)