Skip to content

[vulnops][data] fix: Replace unsafe pickle.loads with restricted unpickler in Qwen VL pipeline - #3139

Merged
yaoyu-33 merged 5 commits into
mainfrom
security/fix-qwenvl-pickle
Apr 16, 2026
Merged

[vulnops][data] fix: Replace unsafe pickle.loads with restricted unpickler in Qwen VL pipeline#3139
yaoyu-33 merged 5 commits into
mainfrom
security/fix-qwenvl-pickle

Conversation

@yaoyu-33

@yaoyu-33 yaoyu-33 commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add safe_pickle.py utility with _RestrictedUnpickler that only allows safe built-in types (list, dict, tuple, str, int, float, bool, bytes, etc.)
  • Replace pickle.loads() in Qwen VL videohandler with safe_pickle_loads() to prevent arbitrary code execution from malicious WebDataset shards
  • The restricted unpickler raises pickle.UnpicklingError if any non-whitelisted type is encountered

Test plan

  • Verify Qwen VL data loading still works with the restricted unpickler
  • Verify malicious pickle payloads are rejected
  • Run Qwen VL data pipeline tests

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Security Improvements
    • Enhanced data deserialization to prevent arbitrary code execution from untrusted sources
    • Added trust_remote_code parameter for model loading with secure default setting
    • Implemented explicit warnings when remote code execution is enabled, alerting users to potential risks

…Qwen VL data pipeline

The videohandler.__call__ method used pickle.loads() directly on data
from WebDataset shards, enabling arbitrary code execution via crafted
pickle payloads. Replace with a RestrictedUnpickler that only allows
safe built-in types (list, dict, tuple, str, int, float, etc.).

The restricted unpickler is placed in a shared utility module
(megatron.bridge.utils.safe_pickle) for reuse across other fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
@copy-pr-bot

copy-pr-bot Bot commented Apr 3, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@yaoyu-33 yaoyu-33 changed the title [data] fix: Replace unsafe pickle.loads with restricted unpickler in Qwen VL pipeline [vulnops][data] fix: Replace unsafe pickle.loads with restricted unpickler in Qwen VL pipeline Apr 3, 2026
@yaoyu-33 yaoyu-33 added area:data Dataset builders, preprocessing, and samplers bug Something isn't working labels Apr 6, 2026
@yaoyu-33
yaoyu-33 marked this pull request as ready for review April 8, 2026 04:06
Resolve conflict in auto_bridge.py: take main's logger.warning() approach
and improved trust_remote_code kwargs handling.

Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
@yaoyu-33

yaoyu-33 commented Apr 8, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 666aa36

@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces security hardening for pickle deserialization by adding a restricted unpickler module that limits deserialization to whitelisted safe types, integrating it into the video data handler, and adding safety warnings and configurable parameters for remote code execution settings in model loading.

Changes

Cohort / File(s) Summary
Safe pickle deserialization module
src/megatron/bridge/utils/safe_pickle.py
New module introducing _RestrictedUnpickler class that restricts pickle deserialization to safe types from builtins and collections modules only. Provides safe_pickle_load() and safe_pickle_loads() functions for secure unpickling with allowlist-based validation.
Video data handler integration
src/megatron/bridge/data/energon/task_encoder_utils.py
Replaced direct pickle.loads() call with safe_pickle_loads() in videohandler's decoding path to use restricted unpickler for deserializing pickled video/frame data.
Model loading safety guardrails
src/megatron/bridge/models/conversion/auto_bridge.py
Added trust_remote_code: bool = False parameter to AutoBridge.from_auto_config() and propagated it to transformers.AutoConfig.from_pretrained(). Introduced explicit UserWarning when trust_remote_code=True in both from_auto_config() and from_hf_pretrained() to alert users of arbitrary code execution risks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Results For Major Changes ⚠️ Warning PR contains security-critical changes but lacks test execution results, CI logs, or evidence of successful testing for new safe_pickle module and modified data loading pipeline. Create test_safe_pickle.py validating restricted unpickler, run existing tests for Qwen VL data loading, and provide CI logs proving all tests pass.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: replacing unsafe pickle.loads with a restricted unpickler for security in the Qwen VL pipeline, which aligns with all three modified files.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/fix-qwenvl-pickle

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/megatron/bridge/data/energon/task_encoder_utils.py`:
- Around line 229-233: The code extracts extension into the local variable
extension, validates using extension.lower() but then looks up
self.extensions_mapping with the original extension which can cause a KeyError
for mixed-case extensions; update the logic in the task_encoder_utils function
(where extension is set and used) to normalize the extension once (e.g.,
extension_lower = extension.lower() or assign back to extension) and use that
normalized value for both the membership check against self.extensions and the
mapping lookup self.extensions_mapping[...], ensuring consistent lowercase keys
are used for validation and retrieval.

In `@src/megatron/bridge/utils/safe_pickle.py`:
- Around line 22-42: The _SAFE_MODULES allowlist is currently a mutable dict of
mutable sets; make it immutable by converting each inner set to frozenset and
wrapping the outer mapping with types.MappingProxyType (import MappingProxyType
from types). Update the _SAFE_MODULES definition so values are frozenset(...)
for each module entry (e.g., "builtins", "collections") and replace the literal
dict with MappingProxyType({...}) to prevent runtime mutation; ensure any
references to _SAFE_MODULES remain the same.
- Around line 23-40: Remove "type" from the builtins whitelist in safe_pickle
(the set literal under "builtins") because it's unnecessary for current payloads
and increases attack surface; edit the whitelist in
src/megatron/bridge/utils/safe_pickle.py (the set containing
"list","dict",...,"NoneType") to delete the "type" entry so unpickling still
permits NoneType but not the builtin type object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 81827af7-b5dd-45d2-a047-52b594681254

📥 Commits

Reviewing files that changed from the base of the PR and between d442550 and 9073c70.

📒 Files selected for processing (3)
  • src/megatron/bridge/data/energon/task_encoder_utils.py
  • src/megatron/bridge/models/conversion/auto_bridge.py
  • src/megatron/bridge/utils/safe_pickle.py

Comment on lines 229 to 233
extension = re.sub(r".*[.]", "", key)
if extension.lower() not in self.extensions:
return None
data = pickle.loads(data)
data = safe_pickle_loads(data)
key = self.extensions_mapping[extension]

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/megatron/bridge/data/energon/task_encoder_utils.py | sed -n '225,240p'

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 787


🏁 Script executed:

grep -n "self.extensions" src/megatron/bridge/data/energon/task_encoder_utils.py | head -20

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 326


Normalize extension before lookup to avoid mixed-case KeyError.

Line 230 validates with extension.lower(), but Line 233 indexes self.extensions_mapping using the original extension. Since both self.extensions and self.extensions_mapping are initialized with lowercase keys only ("jpgs", "mp4s", "videos"), any file with mixed-case extension (e.g., file.JPGS) will pass validation but fail at line 233 with a KeyError.

✅ Proposed fix
         extension = re.sub(r".*[.]", "", key)
-        if extension.lower() not in self.extensions:
+        extension = extension.lower()
+        if extension not in self.extensions:
             return None
         data = safe_pickle_loads(data)
         key = self.extensions_mapping[extension]
📝 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
extension = re.sub(r".*[.]", "", key)
if extension.lower() not in self.extensions:
return None
data = pickle.loads(data)
data = safe_pickle_loads(data)
key = self.extensions_mapping[extension]
extension = re.sub(r".*[.]", "", key)
extension = extension.lower()
if extension not in self.extensions:
return None
data = safe_pickle_loads(data)
key = self.extensions_mapping[extension]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/megatron/bridge/data/energon/task_encoder_utils.py` around lines 229 -
233, The code extracts extension into the local variable extension, validates
using extension.lower() but then looks up self.extensions_mapping with the
original extension which can cause a KeyError for mixed-case extensions; update
the logic in the task_encoder_utils function (where extension is set and used)
to normalize the extension once (e.g., extension_lower = extension.lower() or
assign back to extension) and use that normalized value for both the membership
check against self.extensions and the mapping lookup
self.extensions_mapping[...], ensuring consistent lowercase keys are used for
validation and retrieval.

Comment on lines +22 to +42
_SAFE_MODULES = {
"builtins": {
"list",
"dict",
"tuple",
"set",
"frozenset",
"bytes",
"bytearray",
"str",
"int",
"float",
"bool",
"complex",
"slice",
"range",
"type",
"NoneType",
},
"collections": {"OrderedDict"},
}

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.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify Ruff warning is resolved for this file.
uv run ruff check src/megatron/bridge/utils/safe_pickle.py

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 113


🏁 Script executed:

cat -n src/megatron/bridge/utils/safe_pickle.py | head -50

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 1987


🌐 Web query:

Ruff RUF012 mutable class attribute

💡 Result:

Ruff RUF012 (mutable-class-default) flags mutable default values assigned to class attributes (e.g., [], {}, set() in the class body), because that mutable object is shared by all instances, which can cause surprising cross-instance bugs. [1]

Example that triggers RUF012

class A:
    items: list[int] = []   # shared across all A instances

Common fixes

  1. Make it an instance attribute (usually preferred):
class A:
    def __init__(self) -> None:
        self.items: list[int] = []
  1. If it’s truly intended to be shared, annotate as ClassVar:
from typing import ClassVar

class A:
    items: ClassVar[list[int]] = []
  1. Use an immutable default instead (tuple/frozenset/proxy mapping, etc.). [1]

RUF012 is listed in Ruff’s rules as: “Mutable default value for class attribute.” [2]

Sources: [1] [2]


Make the allowlist immutable (RUF012).

Line 22 defines a mutable class attribute (dict of sets). Ruff's RUF012 rule flags this because the shared mutable object can be accidentally modified at runtime, affecting all instances. Use MappingProxyType and frozenset to make it immutable.

♻️ Proposed fix
+from types import MappingProxyType
+
 class _RestrictedUnpickler(pickle.Unpickler):
@@
-    _SAFE_MODULES = {
-        "builtins": {
+    _SAFE_MODULES = MappingProxyType({
+        "builtins": frozenset({
             "list",
             "dict",
             "tuple",
             "set",
             "frozenset",
             "bytes",
             "bytearray",
             "str",
             "int",
             "float",
             "bool",
             "complex",
             "slice",
             "range",
             "type",
             "NoneType",
-        },
-        "collections": {"OrderedDict"},
-    }
+        }),
+        "collections": frozenset({"OrderedDict"}),
+    })
🧰 Tools
🪛 Ruff (0.15.9)

[warning] 22-42: Mutable default value for class attribute

(RUF012)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/megatron/bridge/utils/safe_pickle.py` around lines 22 - 42, The
_SAFE_MODULES allowlist is currently a mutable dict of mutable sets; make it
immutable by converting each inner set to frozenset and wrapping the outer
mapping with types.MappingProxyType (import MappingProxyType from types). Update
the _SAFE_MODULES definition so values are frozenset(...) for each module entry
(e.g., "builtins", "collections") and replace the literal dict with
MappingProxyType({...}) to prevent runtime mutation; ensure any references to
_SAFE_MODULES remain the same.

Comment on lines +23 to +40
"builtins": {
"list",
"dict",
"tuple",
"set",
"frozenset",
"bytes",
"bytearray",
"str",
"int",
"float",
"bool",
"complex",
"slice",
"range",
"type",
"NoneType",
},

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "safe_pickle.py" | head -5

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 113


🏁 Script executed:

find . -type f -path "*/bridge/data/datasets/utils.py" | head -5

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 115


🏁 Script executed:

cat -n src/megatron/bridge/utils/safe_pickle.py

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 2419


🏁 Script executed:

sed -n '1230,1280p' src/megatron/bridge/data/datasets/utils.py

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 1994


🏁 Script executed:

sed -n '1239,1270p' src/megatron/bridge/data/datasets/utils.py

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 1299


🏁 Script executed:

rg "safe_pickle_load" src/

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 448


🏁 Script executed:

rg -B 5 -A 5 "safe_pickle_loads" src/megatron/bridge/data/energon/task_encoder_utils.py

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 1086


🏁 Script executed:

rg "type" src/megatron/bridge/utils/safe_pickle.py -A 3 -B 3

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 1144


🏁 Script executed:

rg "NoneType\|type\(" src/megatron/bridge/data/datasets/utils.py src/megatron/bridge/data/energon/task_encoder_utils.py

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 53


🏁 Script executed:

rg "type\(" src/megatron/bridge/ -t py | grep -i pickle

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 53


🏁 Script executed:

python3 << 'EOF'
import pickle
import io

# Test what's needed to unpickle a simple dict with int values
data = dict(newline_int=100, version="1.0")
pickled = pickle.dumps(data)

# Check what classes are referenced in the pickle
import pickletools
print("Pickle opcodes for dict(newline_int=100, version='1.0'):")
pickletools.dis(io.BytesIO(pickled))
EOF

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 602


🏁 Script executed:

python3 << 'EOF'
import pickle
import io
import pickletools

# Test what happens when we pickle a list with None values
data = [1, 2, None, "test", 3.14]
pickled = pickle.dumps(data)

print("Pickle opcodes for list with None:")
pickletools.dis(io.BytesIO(pickled))

# Check if type is ever referenced
print("\n\nSearching for 'type' in pickle representation:")
if b'type' in pickled:
    print("Found 'type' in pickle data")
else:
    print("No 'type' found in pickle data")
EOF

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 583


🏁 Script executed:

python3 << 'EOF'
import pickle
import io
import pickletools

# Test if type is ever needed during unpickling
# The whitelist includes both 'type' and 'NoneType'

# Check if we can unpickle None without 'type'
data = None
pickled = pickle.dumps(data)

print("Pickle opcodes for None:")
pickletools.dis(io.BytesIO(pickled))

print("\n\n---")

# Now test a more complex scenario
data = {"version": __import__('sys').version_info.major}
pickled = pickle.dumps(data)

print("Pickle opcodes for dict with sys module reference:")
pickletools.dis(io.BytesIO(pickled))
EOF

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 516


🏁 Script executed:

python3 << 'EOF'
# Check if NoneType is actually referenced during unpickling
import pickle
import io
import pickletools

# NoneType is the type of None, but let's see if it's actually used
data = type(None)
pickled = pickle.dumps(data)

print("Pickle opcodes for NoneType (type(None)):")
pickletools.dis(io.BytesIO(pickled))
EOF

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 521


🏁 Script executed:

python3 << 'EOF'
# Check how to handle NoneType in a restricted unpickler
import pickle
import io

# NoneType is type(None), but how is it represented?
print("NoneType:", type(None))
print("NoneType name:", type(None).__name__)
print("NoneType module:", type(None).__module__)

# Can we access builtins.NoneType?
import builtins
try:
    print("builtins.NoneType:", builtins.NoneType)
except AttributeError as e:
    print(f"Error accessing builtins.NoneType: {e}")

# What about the actual way to get NoneType?
NoneType = type(None)
print(f"\ntype(None).__name__ = {NoneType.__name__}")
print(f"type(None).__module__ = {NoneType.__module__}")
EOF

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 295


🏁 Script executed:

python3 << 'EOF'
import pickle
import io

# Test if 'type' is actually needed for the metadata dict case
class RestrictedUnpickler(pickle.Unpickler):
    """Test without 'type' in whitelist"""
    
    _SAFE_MODULES = {
        "builtins": {
            "list",
            "dict",
            "tuple",
            "set",
            "frozenset",
            "bytes",
            "bytearray",
            "str",
            "int",
            "float",
            "bool",
            "complex",
            "slice",
            "range",
            # "type",  # REMOVED
            "NoneType",
        },
        "collections": {"OrderedDict"},
    }

    def find_class(self, module: str, name: str) -> type:
        if module in self._SAFE_MODULES and name in self._SAFE_MODULES[module]:
            return super().find_class(module, name)
        raise pickle.UnpicklingError(
            f"Restricted unpickler refused to load '{module}.{name}'."
        )

# Test 1: Simple metadata dict (the known payload)
print("Test 1: Unpickling metadata dict (known payload)")
data = dict(newline_int=100, version="1.0")
pickled = pickle.dumps(data)
try:
    result = RestrictedUnpickler(io.BytesIO(pickled)).load()
    print(f"✓ SUCCESS: {result}")
except pickle.UnpicklingError as e:
    print(f"✗ FAILED: {e}")

# Test 2: Unpickling NoneType
print("\nTest 2: Unpickling type(None)")
data = type(None)
pickled = pickle.dumps(data)
try:
    result = RestrictedUnpickler(io.BytesIO(pickled)).load()
    print(f"✓ SUCCESS: {result}")
except pickle.UnpicklingError as e:
    print(f"✗ FAILED: {e}")

# Test 3: Unpickling dict with None values
print("\nTest 3: Unpickling dict with None values")
data = dict(value=None, count=10)
pickled = pickle.dumps(data)
try:
    result = RestrictedUnpickler(io.BytesIO(pickled)).load()
    print(f"✓ SUCCESS: {result}")
except pickle.UnpicklingError as e:
    print(f"✗ FAILED: {e}")
EOF

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 346


🏁 Script executed:

rg "type\(None\)" src/ -t py

Repository: NVIDIA-NeMo/Megatron-Bridge

Length of output: 53


Remove builtins.type from the whitelist: it is not required for the known payloads and weakens the security boundary.

The metadata dict pickled at lines 1239-1270 (dict(newline_int=newline_int, version=__idx_version__)) unpickles successfully without type. While NoneType is listed in the whitelist, unpickling None values does not require the type builtin; type is only needed if someone explicitly pickles type(None) itself, which does not occur in this codebase. Including type expands the callable construction surface during unpickling unnecessarily.

🔒 Suggested hardening
-            "type",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/megatron/bridge/utils/safe_pickle.py` around lines 23 - 40, Remove "type"
from the builtins whitelist in safe_pickle (the set literal under "builtins")
because it's unnecessary for current payloads and increases attack surface; edit
the whitelist in src/megatron/bridge/utils/safe_pickle.py (the set containing
"list","dict",...,"NoneType") to delete the "type" entry so unpickling still
permits NoneType but not the builtin type object.

@yaoyu-33

Copy link
Copy Markdown
Contributor Author

/claude review

"complex",
"slice",
"range",
"type",

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.

Bug: builtins.type should not be in the allowlist. Pickle can resolve builtins.type via find_class and then invoke it via REDUCE as type(name, bases, namespace_dict), which constructs an arbitrary new class. This undermines the purpose of the restricted unpickler.

Unless there's a known data format that pickles type objects in the Qwen VL shards, this should be removed:

Suggested change
"type",
"type",

→ delete this line.

If you do need it, please add a comment explaining why and what types are expected.

Comment thread src/megatron/bridge/utils/safe_pickle.py

import dataclasses
import logging
import warnings

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.

Nit: import warnings is added here but never used — only logger.warning() (from logging) is called in this file. Looks like a leftover from the merge conflict resolution.

Suggested change
import warnings

- Make _SAFE_MODULES immutable with MappingProxyType/frozenset (RUF012)
- Remove builtins.type from allowlist to reduce attack surface
- Normalize extension to lowercase before mapping lookup to prevent KeyError
- Add unit tests for safe_pickle (round-trip, rejection, immutability)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yaoyu-33

Copy link
Copy Markdown
Contributor Author

/ok to test 858ed24

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yaoyu-33

Copy link
Copy Markdown
Contributor Author

/ok to test ef47a9d

…rtability

The hardcoded byte-string payloads used protocol 5 framing that caused
"pickle data was truncated" on Python 3.12 instead of hitting find_class.
Use pickle.dumps() to generate valid payloads at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yaoyu-33

Copy link
Copy Markdown
Contributor Author

/ok to test f795f1a

yaoyu-33 added a commit that referenced this pull request Apr 15, 2026
Manual bump since PR #3139 passed on mcore-dev.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@cuichenx cuichenx 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.

tested on toy video dataset and ran without issues

@yaoyu-33
yaoyu-33 merged commit b8b13d3 into main Apr 16, 2026
84 checks passed
@yaoyu-33
yaoyu-33 deleted the security/fix-qwenvl-pickle branch April 16, 2026 03:53
@chtruong814 chtruong814 added the r0.4.0 Auto-cherrypick to release branch. Apply before merge; cherrypick happens after merge. label Apr 27, 2026
@pruprakash

Copy link
Copy Markdown
Contributor

QA RCCA Analysis

1. Fix Reference

2. Root Cause

SECURITY VULNERABILITY: pickle.loads() in Qwen VL videohandler allowed arbitrary code execution from malicious WebDataset shards.

3. Trigger Configuration

  • Loading Qwen VL data from WebDataset shards
  • Using videohandler for video data

4. Nature of the Bug

Classification: CODE BUG (SECURITY) - Arbitrary code execution via malicious pickle in WebDataset

5. Existing Test Coverage

In Fix PR: YES - 1 test file:

  • tests/unit_tests/utils/test_safe_pickle.py

In NMFW Tests: N/A for security fixes

6. Coverage Assessment

Test Type Exists Covers Bug
Fix PR unit tests YES YES
Security regression tests YES YES

7. New Regression Test

NOT NEEDED - Fix PR includes unit tests for safe_pickle restricted unpickler.

8. Conclusion

Verdict: ADEQUATE COVERAGE - Security fix with unit tests for restricted unpickler.

vasunvidia pushed a commit to vasunvidia/Megatron-Bridge that referenced this pull request Jun 10, 2026
…ckler in Qwen VL pipeline (NVIDIA-NeMo#3139)

Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Vasudevan Rengasamy <vrengasamy@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:data Dataset builders, preprocessing, and samplers bug Something isn't working full-test-suite qa_rcca_done r0.4.0 Auto-cherrypick to release branch. Apply before merge; cherrypick happens after merge.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants