Skip to content

fix(ocr): preserve nested coordinate structure in extract_text_blocks - #5349

Open
Ricardo-M-L wants to merge 3 commits into
xorbitsai:mainfrom
Ricardo-M-L:fix/deepseek-ocr-eval-rce
Open

fix(ocr): preserve nested coordinate structure in extract_text_blocks#5349
Ricardo-M-L wants to merge 3 commits into
xorbitsai:mainfrom
Ricardo-M-L:fix/deepseek-ocr-eval-rce

Conversation

@Ricardo-M-L

Copy link
Copy Markdown
Contributor

This PR addresses: fix(ocr): preserve nested coordinate structure in extract_text_blocks

Ricardo-M-L and others added 3 commits May 5, 2026 00:20
…nate parsing

`xinference/model/image/ocr/deepseek_ocr.py` had two `eval()` calls in
the coordinate-extraction helpers — `extract_coordinates_and_label`
(line 231) and `extract_text_blocks` (line 399). Both `eval()` their
input directly:

```python
cor_list = eval(ref_text[2])                  # ref_text from OCR output
coords   = eval(f"[{coords_str}]")            # coords_str from OCR output
```

The argument to each `eval()` is a substring extracted from the OCR
model's text output. Because OCR output is LLM-generated and
attacker-influenceable through the source document or prompt, an OCR
response of the form

```
<|ref|>x<|/ref|><|det|>[[__import__('os').system('id')]]<|/det|>
```

would have its payload **executed by the host process** when the result
is parsed for downstream rendering / block extraction.

Replace both calls with `ast.literal_eval`, which only accepts Python
literal structures (lists, tuples, numbers, strings, …) and refuses
calls, attribute access, and imports. The expected coordinate strings
are list literals like `[[10, 20, 30, 40]]`, which `literal_eval`
parses identically to `eval`, so behavior on legitimate input is
unchanged. Exception handling is tightened to the specific
`ValueError` / `SyntaxError` that `literal_eval` raises (plus
`IndexError`/`TypeError` in the first helper because it indexes
`ref_text[1]`/`ref_text[2]`).

This is the same fix shape as the merged tool-parser PR xorbitsai#4786 — that
one covered `xinference/model/llm/`; this PR covers the image OCR
path that xorbitsai#4786 didn't reach.

Adds `xinference/model/image/ocr/tests/test_deepseek_ocr_safe_eval.py`
with cases that pin the security guarantee:

- Legitimate single and multi coordinate lists still parse.
- A payload of `__import__('pathlib').Path(...).write_text(...)` is
  rejected with no side effect (verified by checking a tmp_path
  sentinel file is never created).
- Attribute-walking payloads (`().__class__.__bases__[0]...`) are
  rejected.
- Malformed truncated input does not crash callers.
- A malicious block followed by a valid block in the same OCR
  response still allows the valid block to surface.
The pre-existing regex `\[\[(.*?)\]\]` stripped the outer `[[...]]`,
leaving `coords_str` as e.g. `1, 2, 3, 4`. After ast.literal_eval the
result was a *flat* list `[1, 2, 3, 4]`, which:

  1. Disagrees with the sibling helper extract_coordinates_and_label,
     which returns a nested list `[[1, 2, 3, 4]]`.
  2. Breaks the immediately-following `bbox = coords[0] if
     len(coords) == 1 else coords` line — that pattern only makes
     sense when `coords` is a list-of-lists (length-1 means one bbox,
     length>1 means multiple). With a flat list, `coords[0]` returns
     a single integer.
  3. Causes the new safe_eval tests in this file to fail.

Fix the regex to capture the outer brackets and use literal_eval
directly on `coords_str`. Verified locally that:

  - Single block: coords == [[1, 2, 3, 4]], bbox == [1, 2, 3, 4]
  - Multiple blocks: each produces its own [[...]]
  - Code-injection payload still rejected by literal_eval (ValueError)
@XprobeBot XprobeBot added the bug Something isn't working label Aug 17, 2026
@XprobeBot XprobeBot added this to the v3.x milestone Aug 17, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces unsafe eval calls with ast.literal_eval in deepseek_ocr.py to mitigate remote code execution (RCE) vulnerabilities from LLM-generated OCR outputs, and introduces comprehensive unit tests to verify this behavior. The reviewer recommends also catching MemoryError and RecursionError during parsing to protect against potential denial-of-service (DoS) attacks from maliciously crafted inputs.

# Python literal structures (lists/tuples/numbers) and refuses calls,
# imports, and attribute access.
cor_list = ast.literal_eval(ref_text[2])
except (ValueError, SyntaxError, IndexError, TypeError) as e:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

ast.literal_eval can also raise MemoryError and RecursionError on maliciously crafted input, which could lead to a denial of service. It would be more robust to catch these exceptions as well to prevent the process from crashing.

Suggested change
except (ValueError, SyntaxError, IndexError, TypeError) as e:
except (ValueError, SyntaxError, IndexError, TypeError, MemoryError, RecursionError) as e:

}
)
except Exception:
except (ValueError, SyntaxError):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Similar to the other ast.literal_eval call, this one could also raise MemoryError or RecursionError on maliciously crafted input, leading to a denial of service. Since this is inside a loop, an unhandled exception would stop the processing of subsequent text blocks. Catching these exceptions would make the parsing more robust.

Suggested change
except (ValueError, SyntaxError):
except (ValueError, SyntaxError, MemoryError, RecursionError):

@Ricardo-M-L Ricardo-M-L changed the title fix: fix(ocr): preserve nested coordinate structure in extract_text_blocks fix(ocr): preserve nested coordinate structure in extract_text_blocks Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants