Skip to content

Malformed request can cause uncatchable exception - uncaught exception: Part terminated early due to unexpected end of multipart data #594

@itslenny

Description

@itslenny

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the bug has not already been reported

Fastify version

5.6.1

Plugin version

9.2.1

Node.js version

22.17.1

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

15.6.1

Description

When calling req.file() a malformed request can cause data.file.on('error') to throw before the promise resolves. This makes it impossible to listen for the error, and causes an Unhandled 'error' event aka uncaughtException and kills the node process if not handled at the process level.

I've created a minimal repro which is based on the usage in the README.MD

https://github.com/itslenny/fastify-multipart-crash-minimal-repro

Error Output

Image

Additional Details

Adding any await before calling req.file() will cause an error to be emitted on data.file before the promise is resolved. This makes it impossible to listen for an error event causing a uncaughtException crash.

fastify.register(require('@fastify/multipart'))

fastify.post('/', async function (req, reply) {

  // this line causes an uncaught exception, comment it out and it goes away
  // it seems if there is any async operation before req.file() the file.on('error') fires before the promise resolves
  // this causes an uncaught exception and exits node
  await new Promise(r => setImmediate(r))

  const data = await req.file()

  // when the crash happens this line never logs (meaning the above promise never resolves)
  console.log('AFTER req.file()')

})

This is a curl command that triggers the crash

curl -X POST http://localhost:3000 \
  -H "Content-Type: multipart/form-data; boundary=----MyBoundary" \
  --data-binary $'------MyBoundary\r\nContent-Disposition: form-data; name="file"; filename="my-picture.jpg"\r\nContent-Type: image/jpeg\r\n\r\nABC'

Link to code that reproduces the bug

https://github.com/itslenny/fastify-multipart-crash-minimal-repro

Expected Behavior

Prevent uncaught exception crash

Simply adding file.on('error', () => {}) to the top of onFile in index.js prevents the server from crashing, but does result in an exception that cannot be caught.

Allow listening for error before promise resolves

One option would be to pass in an onFile listener which would allow attaching a listener before the promise resolves

const data = await req.file({
  onFile: (file) => {
    file.on('error', err => {
      // handle error at the app level
    }
  }
})

Busboy considerations

The error that causes the crash is emitted here in busboy. If it also emitted on boy it could be listened to from that level instead.

Handling missed error

When this error is missed the stream is closed. So, as long as it's not "uncaught" before the promise resolves it can be handled in that way for this specific error state at least

      if (body.closed || body.destroyed || body.readableEnded) {
        throw ERRORS.NoContentProvided(new Error('Invalid multipart upload: stream closed prematurely'))
      }

Other options

I don't love any of these options. The ultimate goal would to be able to handle this error the request handler and emit an error response (e.g. 400 Bad Request). Currently the only option seems to be handling the uncaughtException at the process level

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