h2non / bimg

Go package for fast high-level image processing powered by libvips C library
https://pkg.go.dev/github.com/h2non/bimg?tab=doc
MIT License
2.73k stars 341 forks source link

bimg drains out the system memory in case of Pixel flood attack #394

Open narendra36 opened 2 years ago

narendra36 commented 2 years ago

Hi, I'm using bimg in one of my project to compress an image. I'm using the process function but if the image is manipulated for pixel flood attack it is stuck there only and gives VIPS warning but not producing any error. After some time it drains out the system RAM that is causing service down.

Faulty Image detail: I have an image of 5kb, 260x260 pixels. In the image itself I exchange the 260x260 values with 0xfafa x 0xfafa (so 64250x64250 pixels). Now from what I remember your service tries to convert the image once uploaded. By loading the 'whole image' into memory, it tries to allocate 4128062500 pixels into memory, flooding the memory and causing DoS.

Can anyone help me to prevent this?

h2non commented 2 years ago

Thanks for the report. I understand the pixel flood attack vector is the same as the one described here (there is also an image for reproducibility): https://hackerone.com/reports/842462

Ideally, I believe this should be addressed inside libvips, perhaps you can open an issue there, and hopefully @jcupitt can provide some insights about it.

jcupitt commented 2 years ago

Hi, that's an interesting decompression bomb. I see:

$ ls -l lottapixel.jpg 
-rw-rw-r-- 1 john john 4856 Jan 14 13:10 lottapixel.jpg
$ vipsheader -a lottapixel.jpg 
lottapixel.jpg: 64250x64250 uchar, 3 bands, srgb, jpegload
width: 64250
height: 64250
bands: 3
format: uchar
coding: none
interpretation: srgb
xoffset: 0
yoffset: 0
xres: 0.866142
yres: 0.866142
filename: lottapixel.jpg
vips-loader: jpegload
resolution-unit: in
jpeg-multiscan: 0
jpeg-chroma-subsample: 4:2:0

So 5kb on disc, but huge numbers of pixels.

I can copy like this:

$ /usr/bin/time -f %M:%e vips copy lottapixel.jpg x.jpg

(vips:152750): VIPS-WARNING **: 13:12:43.652: read gave 2 warnings

(vips:152750): VIPS-WARNING **: 13:12:43.652: VipsJpeg: Corrupt JPEG data: bad Huffman code

182804:7.90

So it copies in 180mb of memory and 8s of run time. There's a warning, but it does write an image:

$ vipsheader x.jpg
x.jpg: 64250x64250 uchar, 3 bands, srgb, jpegload
$ ls -l x.jpg 
-rw-r--r-- 1 john john 64514329 Jan 14 13:12 x.jpg

And you can view the image, though it's very boring:

Screenshot from 2022-01-14 13-17-38

(the image viewer is vipsdisp, if you're curious ... it's handy for large images)

So I think everything is working.

There are a lot of bombs like this -- it's easy to make TIFFs that blow up, eg.:

$ vips black x.tif[bitdepth=1,compression=ccittfax4] 1000000 1000000

That's a 1m x 1m pixel TIFF image. It's 200kb on disc, but it'll be a terabyte if you try to decompress it.

The simplest defence is to open the image and check the dimensions before decompressing. Perhaps (in python):

image = pyvips.Image.new_from_file(some_suspicious_file)
if float(image.width) * float(image.height) > decompression_bomb_limit:
    error("image too large")

libvips new_from_file is always fast and never allocates large amounts of memory, so this is safe.

16k x 16k pixels might be a sensible limit, perhaps?

narendra36 commented 2 years ago

Hi @jcupitt, thanks for the input. I'm getting the same warnings as you mentioned but since these are just warnings so I'm not able to catch it at code level. As you rightly mentioned, one way is just put some limit on dimensions.

But I want to know more about below point:

  1. bimg having a const defined MaxSize (maximum pixel allowed) in options.go that is approx 16k, can't we use that somehow? @h2non
  2. Since VIPS giving the above warnings that means it is a known condition, Should it not be treated as error instead of just warning?
narendra36 commented 2 years ago

Thanks for the report. I understand the pixel flood attack vector is the same as the one described here (there is also an image for reproducibility): https://hackerone.com/reports/842462

Ideally, I believe this should be addressed inside libvips, perhaps you can open an issue there, and hopefully @jcupitt can provide some insights about it.

Sure, I will open the same on libvips also.

jcupitt commented 2 years ago
2. Since VIPS giving the above warnings that means it is a known condition, Should it not be treated as error instead of just warning?

libvips is permissive by default, so it tries to load images if it possibly can. You can make it strict with the fail option, eg.:

$ vips copy lottapixel.jpg[fail] x.jpg

(vips:174339): VIPS-WARNING **: 17:04:56.219: error in tile 0 x 8
VipsJpeg: Corrupt JPEG data: bad Huffman code
$ echo $?
1

Now it'll stop on any warning and return an error code. This is not always what you want though -- what if an image triggers a harmless warning? Now your users can't upload.

I would block on number of pixels instead. It's much safer and more reliable.

libvips 8.12 deprecates fail and adds fail-on instead. You can set this to the type of thing you want to cause a load failure -- pick one of none, truncated, error, warning.

jcupitt commented 2 years ago

Sure, I will open the same on libvips also.

I don't think there's anything more libvips can do here, so there's no need for an issue.

narendra36 commented 2 years ago

Hi, @jcupitt, Thanks for the clarification. :+1:

@h2non, Can you give input on this: 1. bimg having a const defined MaxSize (maximum pixel allowed) in options.go that is approx 16k, can't we use that somehow?

h2non commented 2 years ago

Thanks for the insights @jcupitt!

I plan to implement safe image validation at bimg level, just for convenience, but in the meantime, you can simply validate the image size before processing it through bimg.

Example:

package main

import (
        "fmt"
        "os"
        "image"
        "log"
        "bufio"
        "gopkg.in/h2non/bimg.v1"
        _ "image/gif"
        _ "image/png"
        _ "image/jpeg"
)

func main() {
        img, err := os.Open("lottapixel.jpg")
        if err != nil {
                log.Fatal(err)
        }
        reader := bufio.NewReader(img)
        config, format, err := image.DecodeConfig(reader)
        if err != nil {
                log.Fatal(err)
        }
        if (config.Width * config.Height) > (bimg.MaxSize * bimg.MaxSize) {
                log.Fatal("Image too large")
        }
        fmt.Println("Valid image of Width", config.Width, "Height:", config.Height, "Format:", format)

        // If the image is safe, call bimg then...
}
jcupitt commented 2 years ago

Oh, nice!

I don't know Go, can this overflow?

        if (config.Width * config.Height) > (bimg.MaxSize * bimg.MaxSize) {

It might be better as a double float multiply..

joeyave commented 1 year ago

Getting this error from scanner when building an image for AWS. Is it related?

Container image with tag c27512a has vulnerabilities findings: 1
IN1-GOLANG-GITHUBCOMH2NONBIMG-2340903 - github.com/h2non/bimg
> ## Overview
[github.com/h2non/bimg](https://pkg.go.dev/github.com/h2non/bimg#section-readme) is a Small Go package for fast high-level image processing using libvips via C bindings, providing a simple programmatic API.
Affected versions of this package are vulnerable to Denial of Service (DoS) by uploading a crafted image with high pixels size value, which will lead to a high allocation of memory.