mirror of https://github.com/nextcloud/server
Merge pull request #30291 from nextcloud/image-memory-limit
Prevent loading images that would require too much memory.
This commit is contained in:
commit
c47406ad3c
|
@ -1043,6 +1043,16 @@ $CONFIG = [
|
|||
*/
|
||||
'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
|
||||
*
|
||||
|
|
|
@ -46,6 +46,10 @@ use OCP\IImage;
|
|||
* 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 */
|
||||
|
@ -563,6 +567,71 @@ class OC_Image implements \OCP\IImage {
|
|||
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.
|
||||
*
|
||||
|
@ -578,6 +647,9 @@ class OC_Image implements \OCP\IImage {
|
|||
switch ($iType) {
|
||||
case IMAGETYPE_GIF:
|
||||
if (imagetypes() & IMG_GIF) {
|
||||
if (!$this->checkImageSize($imagePath)) {
|
||||
return false;
|
||||
}
|
||||
$this->resource = imagecreatefromgif($imagePath);
|
||||
if ($this->resource) {
|
||||
// Preserve transparency
|
||||
|
@ -592,6 +664,9 @@ class OC_Image implements \OCP\IImage {
|
|||
break;
|
||||
case IMAGETYPE_JPEG:
|
||||
if (imagetypes() & IMG_JPG) {
|
||||
if (!$this->checkImageSize($imagePath)) {
|
||||
return false;
|
||||
}
|
||||
if (getimagesize($imagePath) !== false) {
|
||||
$this->resource = @imagecreatefromjpeg($imagePath);
|
||||
} else {
|
||||
|
@ -603,6 +678,9 @@ class OC_Image implements \OCP\IImage {
|
|||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
if (imagetypes() & IMG_PNG) {
|
||||
if (!$this->checkImageSize($imagePath)) {
|
||||
return false;
|
||||
}
|
||||
$this->resource = @imagecreatefrompng($imagePath);
|
||||
if ($this->resource) {
|
||||
// Preserve transparency
|
||||
|
@ -617,6 +695,9 @@ class OC_Image implements \OCP\IImage {
|
|||
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']);
|
||||
|
@ -624,6 +705,9 @@ class OC_Image implements \OCP\IImage {
|
|||
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']);
|
||||
|
@ -634,6 +718,9 @@ class OC_Image implements \OCP\IImage {
|
|||
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']);
|
||||
|
@ -666,7 +753,11 @@ class OC_Image implements \OCP\IImage {
|
|||
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;
|
||||
|
@ -689,6 +780,9 @@ class OC_Image implements \OCP\IImage {
|
|||
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);
|
||||
|
@ -717,6 +811,9 @@ class OC_Image implements \OCP\IImage {
|
|||
}
|
||||
$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);
|
||||
|
@ -793,6 +890,10 @@ class OC_Image implements \OCP\IImage {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!$this->checkImageMemory($meta['width'], $meta['height'])) {
|
||||
fclose($fh);
|
||||
return false;
|
||||
}
|
||||
// create gd image
|
||||
$im = imagecreatetruecolor($meta['width'], $meta['height']);
|
||||
if ($im == false) {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 103 B |
|
@ -142,6 +142,10 @@ class ImageTest extends \Test\TestCase {
|
|||
->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'));
|
||||
|
@ -363,4 +367,17 @@ class ImageTest extends \Test\TestCase {
|
|||
$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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue