mudler / mudler/vllm.cpp

port(SERVE-REQUEST-LENGTH-GUARD): four media entry points decode or accept unbounded bytes, with only httplib's 100 MB body cap above them

Open
#2,726 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
423
Forks
53
Avg merge
20h 26m
Merged PRs (30d)
310

Description

Row: SERVE-REQUEST-LENGTH-GUARD (.agents/engine-matrix.md:250, re-resolved by line number before filing)

That row already owns "a REFUSING byte bound at the request boundary, checked BEFORE any tokenization" and already names httplib's 100 MB CPPHTTPLIB_PAYLOAD_MAX_LENGTH as the only limit in the stack. This issue is the media half of the same surface, which that row's derived bound (max_model_len * Tokenizer::MaxTokenBytes()) does not cover.

Found by wave PORTQ-6 (#2718) re-deriving PORT-NOW entry [236], upstream 2ec6f0d71e vllm#51896, of 5559679229..e126687a9a. Nothing was executed.

Read the pre-pin note first

Most of this is a pre-pin hole, and it should not be charged to the pin advance. git show 5559679229:vllm/entrypoints/speech_to_text/base/utils.py has read_upload_with_limit at :14, defaulting to envs.VLLM_MAX_AUDIO_CLIP_FILESIZE_MB. An audio-upload byte cap existed at the pin and was never ported here. This commit relocated that cap into AudioMediaIO and added a pre-decode base64-length estimate on top of it.

What is new in this commit: max_bytes on connections.get_bytes / async_get_bytes (absent at the pin) and VLLM_MAX_MEDIA_DOWNLOAD_SIZE_MB (absent at the pin) — and both of those are on the remote-fetch path, which this tree does not have at all. So the pin advance itself owes nothing here. The hole is real and reachable regardless, which is why it is filed rather than noted.

What upstream does

connections.py (+145) adds HTTPResponseSizeExceededError and MediaDownloadSizeExceededError, a 64 KiB streaming reader that checks Content-Length first and then aborts mid-body once received_bytes > max_bytes, and max_bytes= on both fetch entry points. envs.py adds VLLM_MAX_MEDIA_DOWNLOAD_SIZE_MB (default 256), which floors any caller-supplied cap. media/connector.py threads media_io.get_max_bytes() into both fetch paths and maps the new errors to 422. media/audio.py implements get_max_bytes() as VLLM_MAX_AUDIO_CLIP_FILESIZE_MB * MiB, checks the decoded length in load_bytes, and — the pre-download rejection the title is about — checks len(data) > 4*((max+2)//3) in load_base64 before decoding. run_batch.py does the same base64 arithmetic. speech_to_text/base/serving.py deletes the old post-read check, which AudioMediaIO now replaces.

What is here

The remote-fetch half is surface-absent. There is no HTTP media fetch. The only media entry point, DecodeDataUri (src/vllm/entrypoints/openai/chat_mm.cpp:80-107), refuses a non-data: URI by name at :83-87: "not a data: URI (http(s) media fetch is a named residual)". No connections, no MediaConnector, no MediaIO, no allowed-domain list — nothing to give a max_bytes to.

The decoded-payload half is a real hole, on three reachable paths.

  1. src/vllm/entrypoints/openai/chat_mm.cpp:119 DecodeImageUrlPart reaches DecodeDataUri, which calls DecodeBase64(payload) at :105. No length check before or after.
  2. src/vllm/entrypoints/openai/speech_api.cpp:207 (/v1/audio/speech, reference_audio) and src/vllm/entrypoints/openai/video_api.cpp:84 reach the same unbounded DecodeDataUri.
  3. src/vllm/entrypoints/openai/api_server.cpp:452-470 handle_audio_transcriptions takes the whole multipart file upload as const std::string& file_bytes with only an empty check at :470. Its own comment at :455-457 cites upstream's read_upload_with_limit — the limit it does not implement.

The only bound anywhere is cpp-httplib's default CPPHTTPLIB_PAYLOAD_MAX_LENGTH = 100 MB (third_party/httplib/httplib.h:129-130, used at :1880); set_payload_max_length is never called (0 hits over src/, include/, tests/). So a single request body is capped at 100 MB — about four times looser than upstream's 25 MB audio cap — and the base64 decode then allocates roughly 75 MB of decoded bytes on top of the retained request string.

refuse_oversized_prompt (api_server.cpp:191-208) does not cover this: its three call sites :261, :342 and :894 all pass a text prompt's size, never a media payload.

Consequence: memory amplification, not wrong output

A single accepted anonymous request makes the server allocate the ~100 MB body plus ~75 MB of decoded media before any validation runs, and the transcription upload path refuses only an empty file. Upstream refuses at 25 MB with a named error. There is no authentication in src/vllm/entrypoints/, which is the same premise the SERVE-REQUEST-LENGTH-GUARD row already argues from.

Size

~60-90 product lines: a max_bytes parameter on DecodeDataUri checked against payload.size() before DecodeBase64, using upstream's own 4*((max+2)//3) estimate, threaded from the three call sites; a size refusal in handle_audio_transcriptions mirroring read_upload_with_limit; and one config knob equivalent to VLLM_MAX_AUDIO_CLIP_FILESIZE_MB (25 MB default). Everything gated on an HTTP fetch is out of scope.

~120-160 test lines: a red-first over-limit case per entry point, an at-limit boundary case, and one asserting that the refusal message names the limit.

Zeros, with their controls

Scope src/, include/, tests/, probe form grep -r <needle>, one needle per call.

VLLM_MAX_MEDIA_DOWNLOAD_SIZE_MB 0 · MediaDownloadSizeExceeded 0 · HTTPResponseSizeExceeded 0 · MediaConnector 0 · media_connector 0 · max_audio_filesize 0 · MaxAudioFilesize 0 · filesize_mb 0 · set_payload_max_length 0 · payload_max_length 0 in src include (5 hits in third_party/httplib).

Two false friends: MAX_AUDIO_CLIP_FILESIZE returns 1, a comment at api_server.cpp:187 describing upstream; max_bytes returns 18, all WeightOffloadPolicy / cpu_offload_max_bytes, unrelated.

Positive controls through the identical form: DecodeDataUri 15 · DecodeBase64 3 in chat_mm.cpp · handle_audio_transcriptions present at api_server.cpp:452.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with DecodeDataUri in src/vllm/entrypoints/openai/chat_mm.cpp and trace its callers in speech_api.cpp and video_api.cpp, then inspect handle_audio_transcriptions in api_server.cpp. Add focused tests under tests for oversized and boundary media inputs; done means all four entry points reject inputs above the configured limit before decoding and report the limit clearly.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
api, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.