Skip to content

Image#copy_memory returns an image wrapping NULL when libvips fails, and the next call segfaults #442

Closed

Description

@VitaliiLazebnyi

Summary

When vips_image_copy_memory() fails (for example because the source image cannot be decoded), it returns NULL. Vips::Image#copy_memory wraps that pointer in a Vips::Image without checking it, so instead of raising Vips::Error, the call returns an object whose pointer is NULL. The next operation on that object dereferences it and crashes the whole Ruby process with a segmentation fault, which cannot be rescued.

Versions

  • ruby-vips 2.3.0 (also master as of 2026-10-15)
  • libvips 8.16.1
  • Ruby 4.0.7 (aarch64-linux, Debian, in Docker)
  • also returns NULL with libvips 8.18.7 / Ruby 4.0.6 on macOS arm64

Reproduction

require "vips"

# A valid JPEG cut in half: the header is intact, so the load itself succeeds
# lazily, and decoding fails only when the pixels are computed.
jpeg = Vips::Image.black(256, 256).add(128).cast(:uchar).jpegsave_buffer
broken = jpeg.byteslice(0, jpeg.bytesize / 2)
image = Vips::Image.new_from_buffer(broken, "", fail_on: :error)

copy = image.copy_memory            # libvips fails and returns NULL
p copy.class                        # => Vips::Image
p copy.ptr.null?                    # => true
copy.width                          # segfault

Output:

(process:1): VIPS-WARNING **: error in tile 0 x 40
Vips::Image
true
/usr/local/bundle/ruby/4.0.0/gems/ruby-vips-2.3.0/lib/vips/image.rb:891: [BUG] Segmentation fault at 0x0000000000000050
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +YJIT +PRISM [aarch64-linux]

-- Ruby level backtrace information ----------------------------------------
repro.rb:12:in '<main>'
.../ruby-vips-2.3.0/lib/vips/image.rb:891:in 'width'
.../ruby-vips-2.3.0/lib/vips/image.rb:891:in 'vips_image_get_width'

Expected behaviour

copy_memory raises Vips::Error with the libvips error message (here VipsJpeg: premature end of JPEG image), as other methods do when libvips fails.

Cause

lib/vips/image.rb:

def copy_memory
  new_image = Vips.vips_image_copy_memory self
  Vips::Image.new new_image
end

write_to_memory in the same file already checks for this case:

ptr = Vips.vips_image_write_to_memory self, len
raise Vips::Error if ptr.null?

Suggested fix

def copy_memory
  new_image = Vips.vips_image_copy_memory self
  raise Vips::Error if new_image.null?

  Vips::Image.new new_image
end

With that change, the reproduction above raises Vips::Error: VipsJpeg: premature end of JPEG image and the process keeps running.

Related

Same class of bug as #419 (fixed in #420), which added NULL checks to write_to_memory and others; copy_memory was missed.

Fix: #443

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions