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
10 changes: 10 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,16 @@
*/
'preview_max_filesize_image' => 50,

/**
* max memory for generating image previews with imagegd (default behavior)
* Reads the image dimensions from the header and assumes 32 bits per pixel.
* If creating the image would allocate more memory, preview generation will
* be disabled and the default mimetype icon is shown. Set to -1 for no limit.
*
* Defaults to ``128`` megabytes
*/
'preview_max_memory' => 128,

/**
* custom path for LibreOffice/OpenOffice binary
*
Expand Down
103 changes: 102 additions & 1 deletion lib/private/legacy/OC_Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
* Class for basic image manipulation
*/
class OC_Image implements \OCP\IImage {

// Default memory limit for images to load (128 MBytes).
protected const DEFAULT_MEMORY_LIMIT = 128;

/** @var false|resource|\GdImage */
protected $resource = false; // tmp resource.
/** @var int */
Expand Down Expand Up @@ -564,6 +568,71 @@ public function loadFromFileHandle($handle) {
return false;
}

/**
* Check if allocating an image with the given size is allowed.
*
* @param int $width The image width.
* @param int $height The image height.
* @return bool true if allocating is allowed, false otherwise
*/
private function checkImageMemory($width, $height) {
$memory_limit = $this->config->getSystemValueInt('preview_max_memory', self::DEFAULT_MEMORY_LIMIT);
if ($memory_limit < 0) {
// Not limited.
return true;
}

// Assume 32 bits per pixel.
if ($width * $height * 4 > $memory_limit * 1024 * 1024) {
$this->logger->debug('Image size of ' . $width . 'x' . $height . ' would exceed allowed memory limit of ' . $memory_limit);
return false;
}

return true;
}

/**
* Check if loading an image file from the given path is allowed.
*
* @param string $path The path to a local file.
* @return bool true if allocating is allowed, false otherwise
*/
private function checkImageSize($path) {
$size = getimagesize($path);
if (!$size) {
return true;
}

$width = $size[0];
$height = $size[1];
if (!$this->checkImageMemory($width, $height)) {
return false;
}

return true;
}

/**
* Check if loading an image from the given data is allowed.
*
* @param string $data A string of image data as read from a file.
* @return bool true if allocating is allowed, false otherwise
*/
private function checkImageDataSize($data) {
$size = getimagesizefromstring($data);
if (!$size) {
return true;
}

$width = $size[0];
$height = $size[1];
if (!$this->checkImageMemory($width, $height)) {
return false;
}

return true;
}

/**
* Loads an image from a local file.
*
Expand All @@ -579,6 +648,9 @@ public function loadFromFile($imagePath = false) {
switch ($iType) {
case IMAGETYPE_GIF:
if (imagetypes() & IMG_GIF) {
if (!$this->checkImageSize($imagePath)) {
return false;
}
$this->resource = imagecreatefromgif($imagePath);
if ($this->resource) {
// Preserve transparency
Expand All @@ -593,6 +665,9 @@ public function loadFromFile($imagePath = false) {
break;
case IMAGETYPE_JPEG:
if (imagetypes() & IMG_JPG) {
if (!$this->checkImageSize($imagePath)) {
return false;
}
if (getimagesize($imagePath) !== false) {
$this->resource = @imagecreatefromjpeg($imagePath);
} else {
Expand All @@ -604,6 +679,9 @@ public function loadFromFile($imagePath = false) {
break;
case IMAGETYPE_PNG:
if (imagetypes() & IMG_PNG) {
if (!$this->checkImageSize($imagePath)) {
return false;
}
$this->resource = @imagecreatefrompng($imagePath);
if ($this->resource) {
// Preserve transparency
Expand All @@ -618,13 +696,19 @@ public function loadFromFile($imagePath = false) {
break;
case IMAGETYPE_XBM:
if (imagetypes() & IMG_XPM) {
if (!$this->checkImageSize($imagePath)) {
return false;
}
$this->resource = @imagecreatefromxbm($imagePath);
} else {
$this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, ['app' => 'core']);
}
break;
case IMAGETYPE_WBMP:
if (imagetypes() & IMG_WBMP) {
if (!$this->checkImageSize($imagePath)) {
return false;
}
$this->resource = @imagecreatefromwbmp($imagePath);
} else {
$this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, ['app' => 'core']);
Expand All @@ -635,6 +719,9 @@ public function loadFromFile($imagePath = false) {
break;
case IMAGETYPE_WEBP:
if (imagetypes() & IMG_WEBP) {
if (!$this->checkImageSize($imagePath)) {
return false;
}
$this->resource = @imagecreatefromwebp($imagePath);
} else {
$this->logger->debug('OC_Image->loadFromFile, webp images not supported: ' . $imagePath, ['app' => 'core']);
Expand Down Expand Up @@ -667,7 +754,11 @@ public function loadFromFile($imagePath = false) {
default:

// this is mostly file created from encrypted file
$this->resource = imagecreatefromstring(file_get_contents($imagePath));
$data = file_get_contents($imagePath);
if (!$this->checkImageDataSize($data)) {
return false;
}
$this->resource = imagecreatefromstring($data);
$iType = IMAGETYPE_PNG;
$this->logger->debug('OC_Image->loadFromFile, Default', ['app' => 'core']);
break;
Expand All @@ -690,6 +781,9 @@ public function loadFromData($str) {
if (!is_string($str)) {
return false;
}
if (!$this->checkImageDataSize($str)) {
return false;
}
$this->resource = @imagecreatefromstring($str);
if ($this->fileInfo) {
$this->mimeType = $this->fileInfo->buffer($str);
Expand Down Expand Up @@ -718,6 +812,9 @@ public function loadFromBase64($str) {
}
$data = base64_decode($str);
if ($data) { // try to load from string data
if (!$this->checkImageDataSize($data)) {
return false;
}
$this->resource = @imagecreatefromstring($data);
if ($this->fileInfo) {
$this->mimeType = $this->fileInfo->buffer($data);
Expand Down Expand Up @@ -794,6 +891,10 @@ private function imagecreatefrombmp($fileName) {
}
}
}
if (!$this->checkImageMemory($meta['width'], $meta['height'])) {
fclose($fh);
return false;
}
// create gd image
$im = imagecreatetruecolor($meta['width'], $meta['height']);
if ($im == false) {
Expand Down
Binary file added tests/data/testimage-badheader.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions tests/lib/ImageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ public function testData() {
->method('getAppValue')
->with('preview', 'jpeg_quality', 90)
->willReturn(null);
$config->expects($this->once())
->method('getSystemValueInt')
->with('preview_max_memory', 128)
->willReturn(128);
$img = new \OC_Image(null, null, $config);
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage.jpg');
$raw = imagecreatefromstring(file_get_contents(OC::$SERVERROOT.'/tests/data/testimage.jpg'));
Expand Down Expand Up @@ -363,4 +367,17 @@ public function testConvert($mimeType) {
$img->save($tempFile, $mimeType);
$this->assertEquals($mimeType, image_type_to_mime_type(exif_imagetype($tempFile)));
}

public function testMemoryLimitFromFile() {
$img = new \OC_Image();
$img->loadFromFile(OC::$SERVERROOT.'/tests/data/testimage-badheader.jpg');
$this->assertFalse($img->valid());
}

public function testMemoryLimitFromData() {
$data = file_get_contents(OC::$SERVERROOT.'/tests/data/testimage-badheader.jpg');
$img = new \OC_Image();
$img->loadFromData($data);
$this->assertFalse($img->valid());
}
}