From 4b5b2f122bf5df628d4e07631b599e8ef3144f70 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Fri, 10 Jun 2022 13:58:56 +0200 Subject: Fix #831: gdImageAvif memory leak First, we must not forget to call `avifImageDestroy()` when we're finished with the image. Then we also need to cater to the allocated `dataBuf`. To keep track of that, we "extend" `avifIO` as `avifIOCtxReader`. To simplify, and to avoid unnecessary allocations, we use `realloc()`. To better fit with GD, we also use the GD memory allocation functions instead of the ones provided by libavif. --- src/gd_avif.c | 52 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/gd_avif.c b/src/gd_avif.c index 5935623..48369fe 100644 --- a/src/gd_avif.c +++ b/src/gd_avif.c @@ -159,6 +159,11 @@ static avifBool isAvifError(avifResult result, const char *msg) { } +typedef struct avifIOCtxReader { + avifIO io; // this must be the first member for easy casting to avifIO* + avifROData rodata; +} avifIOCtxReader; + /* implements the avifIOReadFunc interface by calling the relevant functions in the gdIOCtx. Our logic is inspired by avifIOMemoryReaderRead() and avifIOFileReaderRead(). @@ -174,8 +179,8 @@ static avifBool isAvifError(avifResult result, const char *msg) { */ static avifResult readFromCtx(avifIO *io, uint32_t readFlags, uint64_t offset, size_t size, avifROData *out) { - void *dataBuf = NULL; gdIOCtx *ctx = (gdIOCtx *) io->data; + avifIOCtxReader *reader = (avifIOCtxReader *) io; // readFlags is unsupported if (readFlags != 0) { @@ -191,28 +196,34 @@ static avifResult readFromCtx(avifIO *io, uint32_t readFlags, uint64_t offset, s if (!ctx->seek(ctx, (int) offset)) return AVIF_RESULT_IO_ERROR; - dataBuf = avifAlloc(size); - if (!dataBuf) { + if (size > reader->rodata.size) { + reader->rodata.data = gdRealloc((void *) reader->rodata.data, size); + reader->rodata.size = size; + } + if (!reader->rodata.data) { gd_error("avif error - couldn't allocate memory"); return AVIF_RESULT_UNKNOWN_ERROR; } // Read the number of bytes requested. // If getBuf() returns a negative value, that means there was an error. - int charsRead = ctx->getBuf(ctx, dataBuf, (int) size); + int charsRead = ctx->getBuf(ctx, (void *) reader->rodata.data, (int) size); if (charsRead < 0) { - avifFree(dataBuf); return AVIF_RESULT_IO_ERROR; } - out->data = dataBuf; + out->data = reader->rodata.data; out->size = charsRead; return AVIF_RESULT_OK; } // avif.h says this is optional, but it seemed easy to implement. static void destroyAvifIO(struct avifIO *io) { - avifFree(io); + avifIOCtxReader *reader = (avifIOCtxReader *) io; + if (reader->rodata.data != NULL) { + gdFree((void *) reader->rodata.data); + } + gdFree(reader); } /* Set up an avifIO object. @@ -226,21 +237,23 @@ static void destroyAvifIO(struct avifIO *io) { // TODO: can we get sizeHint somehow? static avifIO *createAvifIOFromCtx(gdIOCtx *ctx) { - avifIO *io; + struct avifIOCtxReader *reader; - io = gdMalloc(sizeof(*io)); - if (io == NULL) + reader = gdMalloc(sizeof(*reader)); + if (reader == NULL) return NULL; // TODO: setting persistent=FALSE is safe, but it's less efficient. Is it necessary? - io->persistent = AVIF_FALSE; - io->read = readFromCtx; - io->write = NULL; // this function is currently unused; see avif.h - io->destroy = destroyAvifIO; - io->sizeHint = 0; // sadly, we don't get this information from the gdIOCtx. - io->data = ctx; - - return io; + reader->io.persistent = AVIF_FALSE; + reader->io.read = readFromCtx; + reader->io.write = NULL; // this function is currently unused; see avif.h + reader->io.destroy = destroyAvifIO; + reader->io.sizeHint = 0; // sadly, we don't get this information from the gdIOCtx. + reader->io.data = ctx; + reader->rodata.data = NULL; + reader->rodata.size = 0; + + return (avifIO *) reader; } @@ -599,6 +612,9 @@ cleanup: if (avifOutput.data) avifRWDataFree(&avifOutput); + if (avifIm) + avifImageDestroy(avifIm); + return failed; } -- cgit v1.2.1