Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for libvips #1200

Closed
dwightwatson opened this issue Sep 16, 2021 · 22 comments
Closed

Add support for libvips #1200

dwightwatson opened this issue Sep 16, 2021 · 22 comments

Comments

@dwightwatson
Copy link

dwightwatson commented Sep 16, 2021

Rails 7.0.0 Alpha 1 has just been released, which makes a change to the default variant processor. libvips is now the default instead of ImageMagick.

While ImageMagick is still supported it seems reasonable that Heroku should support the default out of the box so that additional configuration isn't required to get a fresh Rails 7 app up and running. Furthermore libvips is a more performant option.

Added info by maintainer, please don't change:

@schneems
Copy link
Contributor

Thanks for the issue! I was just reviewing the rails changelog and working on getting Rails 7 alpha docs up when I noticed this.

Extra context

We've also have some related internal tickets (reference only, non-herokai won't be able to see them):

Short term workaround

Here's a thread of people trying to get libvips working on heroku janko/image_processing#32 (comment).

As a short term solution: I was able to get it working via the heroku/community/apt buildpack and adding this to the Aptfile.

libglib2.0-0
libglib2.0-dev
libpoppler-glib8
libheif
libvips-dev
libvips

We do have an "active storage previews" buildapck but I'm pretty sure it doesn't install libvips https://devcenter.heroku.com/articles/active-storage-on-heroku

Long term solution

I'm going to bring up adding libvips to our default image. If you're curious, our stack image definitions are also open source https://github.com/heroku/stack-images.

@dwightwatson
Copy link
Author

dwightwatson commented Sep 17, 2021

Great to hear - I'll have a play with the Aptfile solution too.

Would be awesome to see it in the default image (with the HEIF support too).

Thanks!

Edit: doesn't look like that Aptfile configuration works unfortunately - just leads to a build error (using heroku-20):

-----> Building on the Heroku-20 stack
-----> Using buildpacks:
       1. heroku-community/apt
       2. https://github.com/heroku/heroku-buildpack-activestorage-preview
       3. heroku/ruby
-----> Apt app detected
-----> Detected Aptfile or Stack changes, flushing cache
-----> Updating apt caches
       Get:1 http://archive.ubuntu.com/ubuntu focal InRelease [265 kB]
       Get:2 http://apt.postgresql.org/pub/repos/apt focal-pgdg InRelease [86.7 kB]
       Get:3 http://archive.ubuntu.com/ubuntu focal-security InRelease [114 kB]
       Get:4 http://archive.ubuntu.com/ubuntu focal-updates InRelease [114 kB]
       Get:5 http://archive.ubuntu.com/ubuntu focal/universe amd64 Packages [11.3 MB]
       Get:6 http://apt.postgresql.org/pub/repos/apt focal-pgdg/main amd64 Packages [339 kB]
       Get:7 http://archive.ubuntu.com/ubuntu focal/main amd64 Packages [1,275 kB]
       Get:8 http://archive.ubuntu.com/ubuntu focal-security/main amd64 Packages [1,082 kB]
       Get:9 http://archive.ubuntu.com/ubuntu focal-security/universe amd64 Packages [791 kB]
       Get:10 http://archive.ubuntu.com/ubuntu focal-updates/universe amd64 Packages [1,071 kB]
       Get:11 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages [1,528 kB]
       Fetched 18.0 MB in 2s (7,550 kB/s)
       Reading package lists...
-----> Fetching .debs for libglib2.0-0
       Reading package lists...
       Building dependency tree...
       0 upgraded, 0 newly installed, 1 reinstalled, 0 to remove and 10 not upgraded.
       Need to get 1,287 kB of archives.
       After this operation, 0 B of additional disk space will be used.
       Get:1 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 libglib2.0-0 amd64 2.64.6-1~ubuntu20.04.4 [1,287 kB]
       Fetched 1,287 kB in 1s (1,955 kB/s)
       Download complete and in download only mode
-----> Fetching .debs for libglib2.0-dev
       Reading package lists...
       Building dependency tree...
       0 upgraded, 0 newly installed, 1 reinstalled, 0 to remove and 10 not upgraded.
       Need to get 1,506 kB of archives.
       After this operation, 0 B of additional disk space will be used.
       Get:1 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 libglib2.0-dev amd64 2.64.6-1~ubuntu20.04.4 [1,506 kB]
       Fetched 1,506 kB in 1s (2,285 kB/s)
       Download complete and in download only mode
-----> Fetching .debs for libpoppler-glib8
       Reading package lists...
       Building dependency tree...
       The following additional packages will be installed:
         libnspr4 libnss3 libpoppler97
       The following NEW packages will be installed:
         libnspr4 libnss3 libpoppler-glib8 libpoppler97
       0 upgraded, 4 newly installed, 0 to remove and 10 not upgraded.
       Need to get 2,388 kB of archives.
       After this operation, 8,363 kB of additional disk space will be used.
       Get:1 http://archive.ubuntu.com/ubuntu focal/main amd64 libnspr4 amd64 2:4.25-1 [107 kB]
       Get:2 http://archive.ubuntu.com/ubuntu focal-security/main amd64 libnss3 amd64 2:3.49.1-1ubuntu1.5 [1,256 kB]
       Get:3 http://archive.ubuntu.com/ubuntu focal/main amd64 libpoppler97 amd64 0.86.1-0ubuntu1 [915 kB]
       Get:4 http://archive.ubuntu.com/ubuntu focal/main amd64 libpoppler-glib8 amd64 0.86.1-0ubuntu1 [109 kB]
       Fetched 2,388 kB in 1s (2,987 kB/s)
       Download complete and in download only mode
-----> Fetching .debs for libheif
       Reading package lists...
       Building dependency tree...
E: Unable to locate package libheif
 !     Push rejected, failed to compile Apt app.
 !     Push failed

@schneems
Copy link
Contributor

@dwightwatson sorry I missed your response. Can you open a support ticket with access to your app. We can debug the aptfile configuration there with more tools. https://help.heroku.com/ search for "libvips" and open a ticket. Link back to this comment and it will end up in my queue.

For this specific issue it looks like libheif install is working for Ubuntu 18 [heroku-18] (https://packages.ubuntu.com/source/bionic/libheif) but not Ubuntu 20 [heroku-20]. But I'm not totally sure why.

The package you really need though is libheif-dev which has development headers you can link against for other applications. I'm not sure if that's enough for integration or how to test it though. If you can open a support ticket we can work through it together there.

@dwightwatson
Copy link
Author

I opened a ticket (1030923) but I don't think it ended up assigned to you.

We were able to debug that heroku-20/Ubuntu 20.04 installs libvips 8.9.1, when the current version is 8.11.3.

Unfortunately there appears to be an issue prior to version 8.11.1 which prevents libvips from working with HEIF. HEIF support is pretty important as iPhones have used it as the default photo format for a while.

Ideally these newer versions could be baked into the buildpack?

@dwightwatson
Copy link
Author

I know it's hot off the press (Rails 7.0.0 has just been tagged), but I wonder if there's been any progress getting libvips into the buildpack?

@dwightwatson
Copy link
Author

Hey @schneems don't mean to be a bother but wondering if there's any progress internally about this? Would be super helpful to those of us leveraging ActiveStorage to know whether this will actually happen.

@jcupitt
Copy link

jcupitt commented Jan 27, 2022

Hello, I'm one of the libvips maintainers.

Another large plus for building your own libvips binary is security. The libvips that comes with Debian (for example) enables all possible loaders, and some of those load libraries have not been fuzzed. I expect they could be exploited easily, and this would obviously be bad for your customers.

libvips has been in oss-fuss for a couple of years now:

https://github.com/google/oss-fuzz/tree/master/projects/libvips

I suggest you only enable that set of loaders. They should be pretty safe for untrusted input.

In fact I'd go even further and also omit JXL for now. Fuzzing is still finding issues in libjxl relatively frequently.

@dwightwatson
Copy link
Author

@jcupitt that's great to know. I don't suppose you'd have any guidance on building libvips on Heroku?

@jcupitt
Copy link

jcupitt commented Jan 28, 2022

@dwightwatson sorry, I use ruby and heroku a bit, but I'm not an expert on slugs and all that stuff.

I was partly involved in https://elements.heroku.com/buildpacks/brandoncc/heroku-buildpack-vips , I don't know if that's helpful.

@schneems
Copy link
Contributor

To come back to this thread. We looked into adding libvips to heroku-18 or heroku-20 and don't feel comfortable doing that. The plan is to try to roll it into heroku-22. The numbers correspond with Ubuntu LTS releases so heroku-18 is based on Ubuntu 18. Our stack rollout doesn't come out the exact same time as the OS is released, but seeing the release date can let you know that it won't come out before then. Ubuntu is currently targeting April 21 https://linuxconfig.org/ubuntu-22-04-features-and-release-date. Our general policy is "we don't forecast features" so don't take this as a commitment, but read it as my personal intentions and desires. I'll try to keep this thread updated as we work through things.

In the short term, I'm recommending those needing it to use the buildpack linked by @jcupitt. Thanks for the work there and for the recommendation!

@jcupitt
Copy link

jcupitt commented Feb 18, 2022

No problem @schneems, I'd like to see this happen too, so if there's anything I can do to help, please don't hesitate to ping me.

There's a PR for 8.13 which adds a feature which might be relevant: a way to disable less-well tested operations at runtime:

libvips/libvips#2636

This would let rails use the platform libvips safely and not need to worry about building a custom binary.

@wdiechmann
Copy link

hi @jcupitt

TL;DR: will heroku-buildpack-vips support Ruby 3.0.3 any time soon?

LR: I ended up here after a somewhat rather lengthy detour 😓 for just wanting the latest/greatest in Apple Silicon, arm64 and what-not - and obviously libvips - and even though I've battled my way through/around quite a few issues along the road I fear this will be the end of it 😭

The "original" ruby buildpack was not enough so I did dokku buildpacks:add --index 2 greybox https://github.com/brandoncc/heroku-buildpack-vips

which now hands me this:

src % git push staging staging:master
Enumerating objects: 3981, done.
Counting objects: 100% (3981/3981), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3909/3909), done.
Writing objects: 100% (3981/3981), 3.56 MiB | 5.92 MiB/s, done.
Total 3981 (delta 2763), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2763/2763), done.
-----> Cleaning up...
-----> Building greybox from herokuish
-----> Adding BUILD_ENV to build environment...
       BUILD_ENV added successfully
-----> Warning: Multiple default buildpacks reported the ability to handle this app. The first buildpack in the list below will be used.
       Detected buildpacks: multi ruby nodejs
-----> Multipack app detected
=====> Downloading Buildpack: https://github.com/brandoncc/heroku-buildpack-vips
=====> Detected Framework: vips (heroku-20 stack)
-----> Vendoring binaries
       Fetching /tmp/buildpackDneo5/build/heroku-20.tar.gz
-----> Configuring build environment
-----> Building runtime environment
-----> Discovering process types
       Procfile declares types -> web
-----> Releasing greybox...
-----> Checking for predeploy task
       No predeploy task found, skipping
-----> Checking for release task
       No release task found, skipping
-----> App Procfile file found
=====> Processing deployment checks
       No CHECKS file found. Simple container checks will be performed.
       For more efficient zero downtime deployments, create a CHECKS file. See https://dokku.com/docs/deployment/zero-downtime-deploys/ for examples
-----> Deploying greybox via the docker-local scheduler...
-----> Deploying web (count=1)
       Attempting pre-flight checks (web.1)
       Waiting for 10 seconds (web.1)
3a8beed3537b5710a50e0b8077133138cbefdb73b3d8372ea411a5f15e8e5b4b
remote:  !     App container failed to start (web.1)
=====> Start of greybox container output (web.1)
       /usr/lib/ruby/2.7.0/bundler/definition.rb:495:in `validate_ruby!': Your Ruby version is 2.7.0, but your Gemfile specified 3.0.3 (Bundler::RubyVersionMismatch)
        from /usr/lib/ruby/2.7.0/bundler/definition.rb:470:in `validate_runtime!'
        from /usr/lib/ruby/2.7.0/bundler.rb:143:in `setup'
        from /usr/lib/ruby/2.7.0/bundler/setup.rb:20:in `block in <top (required)>'
        from /usr/lib/ruby/2.7.0/bundler/ui/shell.rb:136:in `with_level'
        from /usr/lib/ruby/2.7.0/bundler/ui/shell.rb:88:in `silence'
        from /usr/lib/ruby/2.7.0/bundler/setup.rb:20:in `<top (required)>'
        from /usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
        from /usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
        from /app/config/boot.rb:3:in `<top (required)>'
        from bin/rails:3:in `require_relative'
        from bin/rails:3:in `<main>'
---8<---
       /usr/lib/ruby/2.7.0/bundler/definition.rb:495:in `validate_ruby!': Your Ruby version is 2.7.0, but your Gemfile specified 3.0.3 (Bundler::RubyVersionMismatch)
        from /usr/lib/ruby/2.7.0/bundler/definition.rb:470:in `validate_runtime!'
        from /usr/lib/ruby/2.7.0/bundler.rb:143:in `setup'
        from /usr/lib/ruby/2.7.0/bundler/setup.rb:20:in `block in <top (required)>'
        from /usr/lib/ruby/2.7.0/bundler/ui/shell.rb:136:in `with_level'
        from /usr/lib/ruby/2.7.0/bundler/ui/shell.rb:88:in `silence'
        from /usr/lib/ruby/2.7.0/bundler/setup.rb:20:in `<top (required)>'
        from /usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
        from /usr/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:92:in `require'
        from /app/config/boot.rb:3:in `<top (required)>'
        from bin/rails:3:in `require_relative'
        from bin/rails:3:in `<main>'
=====> End of greybox container output (web.1)
remote: 2022/05/24 17:06:44 exit status 1
remote: parallel: This job failed:
remote: /var/lib/dokku/plugins/available/scheduler-docker-local/bin/scheduler-deploy-process-container greybox herokuish dokku/greybox:latest latest web 1 1
remote: 2022/05/24 17:06:45 exit status 1
remote: 2022/05/24 17:06:45 exit status 1
To greybox:greybox
 ! [remote rejected] staging -> master (pre-receive hook declined)
error: failed to push some refs to 'greybox:greybox'
?1 src % 

@jcupitt
Copy link

jcupitt commented May 24, 2022

TL;DR: will heroku-buildpack-vips support Ruby 3.0.3 any time soon?

Sorry, I don't know, you'd need to ask Brandon.

@dwightwatson
Copy link
Author

Unfortunately it seems that libvips did not make it into heroku-22.

dzuelke added a commit to heroku/base-images that referenced this issue Feb 28, 2023
dzuelke added a commit to heroku/base-images that referenced this issue Feb 28, 2023
* Add libvips to stack images

Also addresses heroku/heroku-buildpack-ruby#1200

GUS-W-9927197

* Add poppler-utils

Since we now have libpoppler already.

Very useful for the ActiveStorage-Preview buildpack.

GUS-W-12613616
@dzuelke
Copy link
Contributor

dzuelke commented Mar 1, 2023

Thank you for your patience, everybody.

libvips is now available on all Heroku stacks, so the ruby-vips Gem will build without third-party buildpacks: https://devcenter.heroku.com/changelog-items/2549

Please bear in mind that rollouts of stack image updates to Private Spaces take additional time and will happen over the next few days, so if your app runs in a Private Space, check with a heroku run 'ls /usr/lib/x86_64-linux-gnu/libvips.so.*' whether the libraries are there yet before building against them.

@dzuelke dzuelke closed this as completed Mar 1, 2023
@jcupitt
Copy link

jcupitt commented Mar 1, 2023

Hi @dzuelke, this is great!

I noticed a possible problem though, looking at the changelog you have:

Added libcfitsio5 version 3.430-2
Added libhdf5-100 version 1.10.0-patch1+docs-4
Added libmatio4 version 1.5.11-1
Added libopenjp2-7 version 2.3.0-2build0.18.04.1
Added libopenslide0 version 3.4.1+dfsg-2

If these libraries were added for libvips' benefit, I'm not sure this is a good idea. They have not been fuzzed (as far as I know) and are probably trivially hackable. I know at least libmatio makes zero attempt to sanitise user input and the maintainer says he has no intention of ever hardening the library against untrusted input files.

With this set of libraries, any rails application which lets activestorage see untrusted data will almost certainly be vulnerable to remote execution attacks (or at the very least denial of service attacks) with crafted files.

I would very strongly urge you to only build libvips with image format libraries that we've fuzzed. You can see our fuzzing set up here:

https://github.com/libvips/libvips/blob/master/fuzz/oss_fuzz_build.sh

I would also pick pdfium over poppler (poppler is GPL), but of course that's your call.

I would also add cgif and remove libgif7, if libgif was put in for libvips to use. libvips has it's own GIF loader, but uses cgif for GIF write. It does not use libgif.

@dzuelke
Copy link
Contributor

dzuelke commented Mar 1, 2023

Hi @jcupitt!

Thanks for the fuzzing reminder. The trouble is that we are limited to libvips builds that Ubuntu offer pre-packaged upstream and offers security backports for (including for dependencies), so https://packages.ubuntu.com/bionic/libvips42 for bionic/18.04/heroku-18, https://packages.ubuntu.com/focal/libvips42 for focal/20.04/heroku-20, and https://packages.ubuntu.com/jammy/libvips42 for jammy/22.04/heroku-22.

I presume it's not possible to limit libvips (or ruby-vips) to a subset of loaders/formats, like e.g. ImageMagick allows using its policies?

We're using Poppler since that, again, is supplied from upstream and receives security backports, unlike PDFium.

The libgif7 dependency is for the two older stacks, where libvips links against it; heroku-22 links against libcgif0 instead.

@jcupitt
Copy link

jcupitt commented Mar 1, 2023

Hey @dzuelke, thank for the quick reply.

Yes, I wondered if you were just using the ubuntu builds, that's a shame.

libvips 8.13 and later has a thing to block untrusted loaders:

https://www.libvips.org/2022/05/28/What's-new-in-8.13.html#blocking-of-unfuzzed-loaders

It's off by default.

I've suggested to image_processing (the package that rails uses to call libvips) that they might set the env var VIPS_BLOCK_UNTRUSTED:

janko/image_processing#105 (comment)

Or perhaps heroku could set this by default somewhere? You'll know far more about this than me!

This won't help older libvips installs, of course.

@jcupitt
Copy link

jcupitt commented Mar 1, 2023

Unfortunately, ubuntu-22 shipped with libvips 8.12, so I think none of these builds will have the thing to block untrusted loaders :(

Perhaps libvips should be removed from heroku until the next LTS, which should support loader blocking?

The alternative would be to build your own libvips binary with only the trusted loaders included (this is what the various buildpacks do), but that would be significantly more work for you, of course.

Perhaps heroku could bless and help maintain an official libvips buildpack? That might be an easy compromise.

@dzuelke
Copy link
Contributor

dzuelke commented Mar 1, 2023

Yeah, 8.12 unfortunately :(

FWIW, having just the presence of VIPS_BLOCK_UNTRUSTED trigger the blocking isn't ideal, as that makes it difficult to "unset" the env var if a user wants to override it (e.g. by setting it to an empty value to override the default blocking), but I guess that ship has sailed now that it's in released code 😬.

We'll discuss a "libvips-fuzzed" buildpack internally.

The most important thing for us is to link against as many libraries from the stack as possible, because our stack images underpin running dynos, meaning an update to packages affects all apps immediately thanks to dynamic linking.

Having libraries supplied by a buildpack, on the other hand, means customers only get their packages updated when they redeploy their apps.

Maybe you folks at libvips.org should provide a PPA with more recent builds for Ubuntu LTS versions? ;)

@jcupitt
Copy link

jcupitt commented Mar 1, 2023

having just the presence of VIPS_BLOCK_UNTRUSTED trigger the blocking isn't ideal, as that makes it difficult to "unset" the env var if a user wants to override it

Oh hmm I thought it was easy to unset env vars. Are there times when this is difficult?

Maybe you folks at libvips.org should provide a PPA with more recent builds for Ubuntu LTS versions? ;)

Ooooof, we're really hoping to not do any more packaging :( It's so much work.

I suppose I'm concerned that by shipping a libvips binary that's not suited for untrusted data, heroku might end up accidentally discouraging community packaging of more secure versions. That would be a very unfortunate outcome for everyone.

@dzuelke
Copy link
Contributor

dzuelke commented Mar 2, 2023

having just the presence of VIPS_BLOCK_UNTRUSTED trigger the blocking isn't ideal, as that makes it difficult to "unset" the env var if a user wants to override it

Oh hmm I thought it was easy to unset env vars. Are there times when this is difficult?

Yup :) Imagine a Dockerfile that sets VIPS_BLOCK_UNTRUSTED=1, now you can not docker run … --env VIPS_BLOCK_UNTRUSTED= … to "remove" that variable - it'll be an empty string, which in the if will evaluate to true.

Same on Heroku and any other platforms... one can set variables to override defaults a buildpack or the platform might set, but to remove that default value as a user, the "best" one can do is to make it an empty string.

Since you y'all have released libvips with this behavior, it would be a bit risky to change it, since there might be people who just set the variable to an empty value (as opposed to "1") to enable the blocking, and suddenly, they'd "lose" the blocking with an update.

Although if you clearly document it, or emit a warning now that it will change in the future if the value is empty... alternatively, have explicit "0", "no", "false" values to disable the blocking maybe?

Maybe you folks at libvips.org should provide a PPA with more recent builds for Ubuntu LTS versions? ;)

Ooooof, we're really hoping to not do any more packaging :( It's so much work.

Yup, we know, that's why we try to avoid it, too ;)

I suppose I'm concerned that by shipping a libvips binary that's not suited for untrusted data, heroku might end up accidentally discouraging community packaging of more secure versions. That would be a very unfortunate outcome for everyone.

Yeah I know what you mean.

I'm also looking through Rails' ActiveStorage and the image_processing Gem to understand how they treat MIME types and whether they rely fully on auto-detecting file types via Vips' loaders.

Because if uploading an image/png forcefully uses Vips' PNG loader, or if Rails uses file contents for MIME detection and not extension or Content-Type, this will be a non issue - uploading a .mat file with a bogus Content-Type and extension image/png would then not invoke the matio loader, and Rails doesn't allow "critical" file formats from your list of unfuzzed libraries for image processing: https://guides.rubyonrails.org/configuring.html#config-active-storage-variable-content-types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants