Possible use-after-free in PDF stream chunks
I found a possible use-after-free in PDF stream chunks returned by streamPDFSync().
Files: src/closure.h, src/Canvas.cc, lib/pdfstream.js
Functions: Closure::writeVec, Canvas::StreamPDFSync, streamPDF, Canvas::destroySurface
Relevant code:
struct Closure {
std::vector<uint8_t> vec;
static cairo_status_t writeVec(void *c, const uint8_t *odata, unsigned len) {
Closure* closure = static_cast<Closure*>(c);
closure->vec.insert(closure->vec.end(), odata, odata + len);
return CAIRO_STATUS_SUCCESS;
}
};
StreamPDFSync() streams pointers into that vector:
PdfSvgClosure *closure = static_cast<PdfSvgClosure *>(_closure);
PdfStreamInfo streaminfo;
streaminfo.fn = fn;
streaminfo.data = &closure->vec[0];
streaminfo.len = closure->vec.size();
cairo_status_t status = canvas_write_to_pdf_stream(ensureSurface(), streamPDF, &streaminfo);
The stream callback creates an external Buffer over the supplied pointer:
Napi::Value buf = Napi::Buffer<uint8_t>::New(env, (uint8_t *)(data), len);
streaminfo->fn.MakeCallback(env.Global(), { env.Null(), buf, Napi::Number::New(env, len) }, async);
The source comment already points out the lifetime issue:
// TODO this is technically wrong, we're returning a pointer to the data in a
// vector in a class with automatic storage duration. If the canvas goes out
// of scope while we're in the handler, a use-after-free could happen.
The closure storage is freed when the canvas surface is destroyed:
if (_closure) {
delete _closure;
_closure = nullptr;
}
JavaScript receives the chunks through the public stream API:
this.canvas.streamPDFSync((err, chunk, len) => {
if (len) {
this.push(chunk)
} else {
this.push(null)
}
}, this.options)
If user code retains a chunk after the canvas has been collected or destroyed,
the Buffer can still point into the freed Closure::vec storage.
Suggested fix: make each PDF chunk own its bytes, for example by using
Napi::Buffer<uint8_t>::Copy(env, data, len), or attach an owner/finalizer that
keeps the backing storage alive until the JS Buffer is finalized.
Possible use-after-free in PDF stream chunks
I found a possible use-after-free in PDF stream chunks returned by
streamPDFSync().Files:
src/closure.h,src/Canvas.cc,lib/pdfstream.jsFunctions:
Closure::writeVec,Canvas::StreamPDFSync,streamPDF,Canvas::destroySurfaceRelevant code:
StreamPDFSync()streams pointers into that vector:The stream callback creates an external
Bufferover the supplied pointer:The source comment already points out the lifetime issue:
The closure storage is freed when the canvas surface is destroyed:
JavaScript receives the chunks through the public stream API:
If user code retains a chunk after the canvas has been collected or destroyed,
the
Buffercan still point into the freedClosure::vecstorage.Suggested fix: make each PDF chunk own its bytes, for example by using
Napi::Buffer<uint8_t>::Copy(env, data, len), or attach an owner/finalizer thatkeeps the backing storage alive until the JS
Bufferis finalized.