mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 20:08:18 +08:00
fix(files): reject symlinks when reloading generated downloads
This commit is contained in:
parent
52a2843bdf
commit
47066c6091
@ -506,12 +506,21 @@ public class GeneratedFileCache {
|
|||||||
Path bin = storageDir.resolve(id).normalize();
|
Path bin = storageDir.resolve(id).normalize();
|
||||||
Path meta = storageDir.resolve(id + META_SUFFIX).normalize();
|
Path meta = storageDir.resolve(id + META_SUFFIX).normalize();
|
||||||
// Containment guard — id is already validated, this is defence in depth.
|
// Containment guard — id is already validated, this is defence in depth.
|
||||||
if (!bin.startsWith(storageDir) || !Files.isRegularFile(bin) || !Files.isRegularFile(meta)) {
|
if (!bin.startsWith(storageDir) || !Files.isRegularFile(bin, LinkOption.NOFOLLOW_LINKS)
|
||||||
|
|| !Files.isRegularFile(meta, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Metadata parsed = parseMeta(Files.readString(meta), id);
|
// Refuse leaf symlinks again at open, including replacement after the
|
||||||
byte[] bytes = Files.readAllBytes(bin);
|
// regular-file check. Parent-directory ownership is a separate boundary.
|
||||||
|
Metadata parsed;
|
||||||
|
try (var input = Files.newInputStream(meta, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
parsed = parseMeta(new String(input.readAllBytes(), StandardCharsets.UTF_8), id);
|
||||||
|
}
|
||||||
|
byte[] bytes;
|
||||||
|
try (var input = Files.newInputStream(bin, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
bytes = input.readAllBytes();
|
||||||
|
}
|
||||||
return new Entry(bytes, parsed.filename(), parsed.mimeType(), parsed.expireAt(),
|
return new Entry(bytes, parsed.filename(), parsed.mimeType(), parsed.expireAt(),
|
||||||
parsed.workspaceId(), parsed.ownerUserId(), parsed.conversationId());
|
parsed.workspaceId(), parsed.ownerUserId(), parsed.conversationId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import org.junit.jupiter.api.io.TempDir;
|
|||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@ -18,6 +20,32 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
*/
|
*/
|
||||||
class GeneratedFileCachePersistenceTest {
|
class GeneratedFileCachePersistenceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void coldDownloadRejectsSymbolicLinkToExternalContent(@TempDir Path dir) throws IOException {
|
||||||
|
Path storage = Files.createDirectory(dir.resolve("cache"));
|
||||||
|
var cache = new GeneratedFileCache(storage);
|
||||||
|
String id = cache.put("original".getBytes(StandardCharsets.UTF_8), "report.txt", "text/plain");
|
||||||
|
Path external = Files.writeString(dir.resolve("private.txt"), "outside content");
|
||||||
|
Files.delete(storage.resolve(id));
|
||||||
|
Files.createSymbolicLink(storage.resolve(id), external);
|
||||||
|
|
||||||
|
assertTrue(new GeneratedFileCache(storage).get(id).isEmpty());
|
||||||
|
assertEquals("outside content", Files.readString(external));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void coldDownloadRejectsSymbolicLinkToExternalMetadata(@TempDir Path dir) throws IOException {
|
||||||
|
Path storage = Files.createDirectory(dir.resolve("cache"));
|
||||||
|
var cache = new GeneratedFileCache(storage);
|
||||||
|
String id = cache.put("original".getBytes(StandardCharsets.UTF_8), "report.txt", "text/plain");
|
||||||
|
Path metadata = storage.resolve(id + ".meta");
|
||||||
|
Path external = Files.move(metadata, dir.resolve("outside.meta"));
|
||||||
|
Files.createSymbolicLink(metadata, external);
|
||||||
|
|
||||||
|
assertTrue(new GeneratedFileCache(storage).get(id).isEmpty());
|
||||||
|
assertTrue(Files.isRegularFile(external));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void callerCannotChangeRegisteredVersionThroughInputBytes(@TempDir Path dir) {
|
void callerCannotChangeRegisteredVersionThroughInputBytes(@TempDir Path dir) {
|
||||||
var cache = new GeneratedFileCache(dir);
|
var cache = new GeneratedFileCache(dir);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user