Skip to content

patch _imagingcms.c: use strncpy#153

Open
vrunoa wants to merge 2 commits intouploadcare:simd/masterfrom
vrunoa:patch/cve-2024-28219
Open

patch _imagingcms.c: use strncpy#153
vrunoa wants to merge 2 commits intouploadcare:simd/masterfrom
vrunoa:patch/cve-2024-28219

Conversation

@vrunoa
Copy link
Copy Markdown

@vrunoa vrunoa commented Aug 8, 2025

Fixes # CVE-2024-28219.

Changes proposed in this pull request:

Copying upstream changes to fix CVE-2024-28219. https://github.com/python-pillow/Pillow/pull/7928/files

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of mode strings to prevent potential buffer overflows when creating new transform objects.
  • Tests
    • Added a test to verify handling of long mode strings in color profile transformations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 8, 2025

Walkthrough

The change updates the copying of mode strings within the CmsTransformObject structure in src/_imagingcms.c. It replaces the use of strcpy with strncpy to copy up to 8 characters, preventing buffer overflows in the fixed-size arrays. No public interfaces or function signatures are affected. Additionally, a new test test_long_modes was added in Tests/test_imagecms.py to exercise building a transform with long mode strings.

Changes

Cohort / File(s) Change Summary
Safe string copy in transform object
src/_imagingcms.c
Replaced strcpy with strncpy for copying mode strings to fixed-size arrays, limiting copy to 8 characters.
Test for long mode strings in ImageCms
Tests/test_imagecms.py
Added test_long_modes to build a transform using mode strings longer than 8 characters, without assertions.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • JIRA integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c0ee09 and a8efbdd.

📒 Files selected for processing (1)
  • src/_imagingcms.c (1 hunks)

Comment on lines +204 to +205
strncpy(self->mode_in, mode_in, 8);
strncpy(self->mode_out, mode_out, 8);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

strncpy here can leave mode strings unterminated; ensure explicit NUL-termination

strncpy does not NUL-terminate when the source length ≥ n. For 8-char modes like "RGBA;16B", this yields an unterminated buffer and PyUnicode_FromString(self->mode_in/out) may read past the array, causing OOB reads/UB. Copy up to sizeof(buf)-1 and force-terminate.

Apply this minimal, safe fix:

-    strncpy(self->mode_in, mode_in, 8);
-    strncpy(self->mode_out, mode_out, 8);
+    strncpy(self->mode_in, mode_in, sizeof(self->mode_in) - 1);
+    self->mode_in[sizeof(self->mode_in) - 1] = '\0';
+    strncpy(self->mode_out, mode_out, sizeof(self->mode_out) - 1);
+    self->mode_out[sizeof(self->mode_out) - 1] = '\0';

Optional follow-ups (pick one, if you want to avoid truncating 8-char modes):

  • Keep arrays as-is and change getters to not rely on a trailing NUL:
    • inputMode getter: return PyUnicode_FromStringAndSize(self->mode_in, strnlen(self->mode_in, sizeof(self->mode_in)));
    • outputMode getter: same pattern.
  • Or increase buffers to 9 and keep the above termination pattern to preserve all 8 chars plus '\0' (verify ABI considerations; these structs are internal, but confirm downstream assumptions).

🏁 Script executed:

#!/bin/bash
set -e
echo "---- struct definition around mode_in/mode_out ----"
rg -n "mode_in" -A3 -B3 src/_imagingcms.c || true
rg -n "mode_out" -A3 -B3 src/_imagingcms.c || true

echo "---- strncpy usages ----"
rg -n -C2 "strncpy" src/_imagingcms.c || true

echo "---- Getter functions using mode_in/mode_out ----"
rg -n "PyUnicode_FromString" -C3 src/_imagingcms.c || true

Length of output: 3227


Ensure mode_in/mode_out are always NUL-terminated

strncpy(self->mode_in, mode_in, 8) and likewise for mode_out won’t append '\0' if the source is ≥ 8 bytes (e.g. "RGBA;16B"). Since the getters call PyUnicode_FromString(), a missing terminator can lead to OOB reads and undefined behavior.

• File: src/_imagingcms.c
Lines 204–205 (inside cms_transform_new): replace the two strncpy calls with:

-    strncpy(self->mode_in, mode_in, 8);
-    strncpy(self->mode_out, mode_out, 8);
+    strncpy(self->mode_in, mode_in, sizeof(self->mode_in) - 1);
+    self->mode_in[sizeof(self->mode_in) - 1] = '\0';
+    strncpy(self->mode_out, mode_out, sizeof(self->mode_out) - 1);
+    self->mode_out[sizeof(self->mode_out) - 1] = '\0';

Optional follow-ups (choose one):

  • Change the getters to use PyUnicode_FromStringAndSize() with strnlen() to avoid relying on a terminator.
  • Increase the buffer sizes to 9 to allow full 8-char modes plus '\0' (verify ABI impact).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
strncpy(self->mode_in, mode_in, 8);
strncpy(self->mode_out, mode_out, 8);
/* ensure mode_in/mode_out are always NUL-terminated */
strncpy(self->mode_in, mode_in, sizeof(self->mode_in) - 1);
self->mode_in[sizeof(self->mode_in) - 1] = '\0';
strncpy(self->mode_out, mode_out, sizeof(self->mode_out) - 1);
self->mode_out[sizeof(self->mode_out) - 1] = '\0';
🤖 Prompt for AI Agents
In src/_imagingcms.c at lines 204-205 inside cms_transform_new, the strncpy
calls copying mode_in and mode_out do not guarantee NUL-termination if the
source strings are 8 or more characters, risking out-of-bounds reads. Replace
these strncpy calls by copying up to 7 characters and explicitly setting the 8th
byte to '\0' to ensure proper NUL-termination of the mode_in and mode_out
buffers.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
Tests/test_imagecms.py (2)

629-631: Strengthen the test to assert truncation behavior and prevent silent regressions

Right now this is a pure smoke test. Let’s minimally assert that a transform is created and the mode metadata is bounded to 8 chars, which is the intent of the strncpy fix for CVE-2024-28219.

 def test_long_modes() -> None:
-    p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
-    ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
+    """Regression for CVE-2024-28219: long mode strings should be safely handled."""
+    p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
+    t = ImageCms.buildTransform(p, p, "ABCDEFGHI", "ABCDEFGHI")
+    # Smoke check: transform is created and mode metadata is bounded
+    assert t is not None
+    assert hasattr(t, "inputMode") and hasattr(t, "outputMode")
+    assert len(t.inputMode) <= 8 and len(t.outputMode) <= 8
+    # Optionally, if we want to pin truncation semantics exactly:
+    # assert t.inputMode == "ABCDEFGH" and t.outputMode == "ABCDEFGH"

629-631: Optional: Add boundary-case coverage (7, 8, 9, very long, non-ASCII)

To harden against future regressions, consider a parametrized test to cover lengths around the boundary and non-ASCII input. This complements the smoke test without changing its intent.

Additional test to append elsewhere in this file:

import pytest

@pytest.mark.parametrize("mode", [
    "ABCDEFG",          # 7
    "ABCDEFGH",         # 8 (exact fit)
    "ABCDEFGHI",        # 9 (truncation)
    "A" * 1024,         # very long
    "ĀBĆĐĖFGHI",        # non-ASCII (ensure UTF-8 handling; will be truncated)
])
def test_long_modes_boundary(mode):
    from PIL import ImageCms
    p = ImageCms.getOpenProfile("Tests/icc/sGrey-v2-nano.icc")
    t = ImageCms.buildTransform(p, p, mode, mode)
    assert t is not None
    # Validate metadata is bounded to 8 characters regardless of input length
    assert hasattr(t, "inputMode") and hasattr(t, "outputMode")
    assert len(t.inputMode) <= 8 and len(t.outputMode) <= 8
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8efbdd and c8a209a.

📒 Files selected for processing (1)
  • Tests/test_imagecms.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
Tests/test_imagecms.py (1)
src/PIL/ImageCms.py (1)
  • getOpenProfile (411-430)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants