Skip to content

Commit 2cb4e43

Browse files
authored
Merge pull request #62619 from nextcloud/backport/62600/stable33
[stable33] chore(previews): add file signature check before opening files
2 parents ccb965d + 9c2757b commit 2cb4e43

11 files changed

Lines changed: 120 additions & 7 deletions

File tree

lib/private/Preview/Bitmap.php

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ abstract class Bitmap extends ProviderV2 {
3030
*/
3131
abstract protected function getAllowedMimeTypes(): string;
3232

33+
/**
34+
* @return list<string>
35+
*/
36+
abstract protected function getMagicStrings(): array;
37+
38+
abstract protected function getImagickFormatHint(): string;
39+
3340
/**
3441
* {@inheritDoc}
3542
*/
@@ -62,7 +69,7 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
6269
//new bitmap image object
6370
$image = new \OCP\Image();
6471
$image->loadFromData((string)$bp);
65-
//check if image object is valid
72+
// Check if image object is valid
6673
return $image->valid() ? $image : null;
6774
}
6875

@@ -85,15 +92,19 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
8592
private function getResizedPreview($tmpPath, $maxX, $maxY) {
8693
$bp = new Imagick();
8794

95+
if (!$this->isMagicStringSupported($tmpPath)) {
96+
throw new \Exception('Invalid image type: magic string not recognized');
97+
}
98+
8899
// Validate mime type
89-
$bp->pingImage($tmpPath . '[0]');
100+
$bp->pingImage($this->getImagickFormatHint() . ':' . $tmpPath . '[0]');
90101
$mimeType = $bp->getImageMimeType();
91102
if (!preg_match($this->getAllowedMimeTypes(), $mimeType)) {
92103
throw new \Exception('File mime type does not match the preview provider: ' . $mimeType);
93104
}
94105

95106
// Layer 0 contains either the bitmap or a flat representation of all vector layers
96-
$bp->readImage($tmpPath . '[0]');
107+
$bp->readImage($this->getImagickFormatHint() . ':' . $tmpPath . '[0]');
97108

98109
$bp = $this->resize($bp, $maxX, $maxY);
99110

@@ -102,6 +113,22 @@ private function getResizedPreview($tmpPath, $maxX, $maxY) {
102113
return $bp;
103114
}
104115

116+
private function isMagicStringSupported(string $filepath): bool {
117+
$signatures = $this->getMagicStrings();
118+
if (empty($signatures)) {
119+
return true;
120+
}
121+
$length = array_reduce($signatures, static fn (int $carry, string $signature) => max($carry, strlen($signature)), 0);
122+
$firstBytes = file_get_contents($filepath, false, null, 0, $length);
123+
foreach ($signatures as $signature) {
124+
if (str_starts_with($firstBytes, $signature)) {
125+
return true;
126+
}
127+
}
128+
129+
return false;
130+
}
131+
105132
/**
106133
* Returns a resized \Imagick object
107134
*

lib/private/Preview/Font.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,17 @@ public function getMimeType(): string {
2222
protected function getAllowedMimeTypes(): string {
2323
return '/(application|image)\/(?:font-sfnt|x-font|x-otf|x-ttf|x-pfb$)/';
2424
}
25+
26+
#[\Override]
27+
protected function getMagicStrings(): array {
28+
return [
29+
"\x00\x01\x00\x00\x00", // TTF
30+
'OTTO', // OTF
31+
];
32+
}
33+
34+
#[\Override]
35+
protected function getImagickFormatHint(): string {
36+
return 'ttf';
37+
}
2538
}

lib/private/Preview/HEIC.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,14 @@ private function getResizedPreview($tmpPath, $maxX, $maxY) {
9797

9898
// Some HEIC files just contain (or at least are identified as) other formats
9999
// like JPEG. We just need to check if the image is safe to process.
100-
$bp->pingImage($tmpPath . '[0]');
100+
$bp->pingImage('heic:' . $tmpPath . '[0]');
101101
$mimeType = $bp->getImageMimeType();
102102
if (!preg_match('/^image\/(x-)?(png|jpeg|gif|bmp|tiff|webp|hei(f|c)|avif)$/', $mimeType)) {
103103
throw new \Exception('File mime type does not match the preview provider: ' . $mimeType);
104104
}
105105

106106
// Layer 0 contains either the bitmap or a flat representation of all vector layers
107-
$bp->readImage($tmpPath . '[0]');
107+
$bp->readImage('heic:' . $tmpPath . '[0]');
108108

109109
// Fix orientation from EXIF
110110
$bp->autoOrient();

lib/private/Preview/IMagickSupport.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@ public function __construct(ICacheFactory $cacheFactory) {
2424
}
2525

2626
public function hasExtension(): bool {
27-
return false;
2827
return !is_null($this->imagick);
2928
}
3029

3130
public function supportsFormat(string $format): bool {
32-
return false;
3331
if (is_null($this->imagick)) {
3432
return false;
3533
}

lib/private/Preview/Illustrator.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ public function getMimeType(): string {
2222
protected function getAllowedMimeTypes(): string {
2323
return '/application\/(illustrator|pdf)/';
2424
}
25+
26+
#[\Override]
27+
protected function getMagicStrings(): array {
28+
return ["\x25\x50\x44\x46"];
29+
}
30+
31+
#[\Override]
32+
protected function getImagickFormatHint(): string {
33+
return 'ai';
34+
}
2535
}

lib/private/Preview/PDF.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ public function getMimeType(): string {
2222
protected function getAllowedMimeTypes(): string {
2323
return '/application\/pdf/';
2424
}
25+
26+
#[\Override]
27+
protected function getMagicStrings(): array {
28+
return ['%PDF-'];
29+
}
30+
31+
#[\Override]
32+
protected function getImagickFormatHint(): string {
33+
return 'pdf';
34+
}
2535
}

lib/private/Preview/Photoshop.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ public function getMimeType(): string {
2222
protected function getAllowedMimeTypes(): string {
2323
return '/(application|image)\/(x-photoshop|x-psd)/';
2424
}
25+
26+
#[\Override]
27+
protected function getMagicStrings(): array {
28+
return ['8BPS'];
29+
}
30+
31+
#[\Override]
32+
protected function getImagickFormatHint(): string {
33+
return 'psd';
34+
}
2535
}

lib/private/Preview/Postscript.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,14 @@ public function getMimeType(): string {
2222
protected function getAllowedMimeTypes(): string {
2323
return '/(application\/postscript|image\/x-eps)/';
2424
}
25+
26+
#[\Override]
27+
protected function getMagicStrings(): array {
28+
return ['%!PS'];
29+
}
30+
31+
#[\Override]
32+
protected function getImagickFormatHint(): string {
33+
return 'ps';
34+
}
2535
}

lib/private/Preview/SGI.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,14 @@ public function getMimeType(): string {
2121
protected function getAllowedMimeTypes(): string {
2222
return '/image\/(x-)?sgi/';
2323
}
24+
25+
#[\Override]
26+
protected function getMagicStrings(): array {
27+
return ["\x01\xDA"];
28+
}
29+
30+
#[\Override]
31+
protected function getImagickFormatHint(): string {
32+
return 'sgi';
33+
}
2434
}

lib/private/Preview/TGA.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,14 @@ public function getMimeType(): string {
2121
protected function getAllowedMimeTypes(): string {
2222
return '/image\/(x-)?t(ar)?ga/';
2323
}
24+
25+
#[\Override]
26+
protected function getMagicStrings(): array {
27+
return [];
28+
}
29+
30+
#[\Override]
31+
protected function getImagickFormatHint(): string {
32+
return 'tga';
33+
}
2434
}

0 commit comments

Comments
 (0)