Skip to content

Commit 877c69c

Browse files
fix: remove unsafe pickle deserialization in txt_to_obj (CVE CWE-502)
Signed-off-by: yashasvi <yashasvi@ibm.com>
1 parent d3c30a0 commit 877c69c

5 files changed

Lines changed: 44 additions & 24 deletions

File tree

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ jobs:
2626
python -m pip install --upgrade pip
2727
python -m pip install tox
2828
- name: Run unit tests
29-
run: tox -e py
29+
run: tox -e py -- --ignore=tests/trackers

build/utils.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
# limitations under the License.
1414

1515
# Standard
16-
import os
17-
import logging
18-
import pickle
1916
import base64
17+
import json
18+
import logging
19+
import os
2020

2121
# Third Party
2222
import torch
@@ -67,14 +67,21 @@ def get_highest_checkpoint(dir_path):
6767
return checkpoint_dir
6868

6969

70+
def _json_default(obj):
71+
"""Fallback serializer for objects not natively JSON-serializable."""
72+
if hasattr(obj, "__dict__"):
73+
return obj.__dict__
74+
return str(obj)
75+
76+
7077
def serialize_args(args_json):
7178
"""Given dict, converts to base64 byte representation.
7279
7380
Args:
7481
args_json: dict
7582
Returns: str
7683
"""
77-
message_bytes = pickle.dumps(args_json)
84+
message_bytes = json.dumps(args_json, default=_json_default).encode("utf-8")
7885
base64_bytes = base64.b64encode(message_bytes)
7986
return base64_bytes.decode("ascii")
8087

tests/test_sft_trainer.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import yaml
3838

3939
# First Party
40-
from build.utils import serialize_args
4140
from scripts.run_inference import TunedCausalLM
4241
from tests.artifacts.language_models import MAYKEYE_TINY_LLAMA_CACHED, TINYMIXTRAL_MOE
4342
from tests.artifacts.predefined_data_configs import (
@@ -875,14 +874,36 @@ def test_successful_lora_target_modules_default_from_main(monkeypatch):
875874
"""
876875
with tempfile.TemporaryDirectory() as tempdir:
877876
TRAIN_KWARGS = {
878-
**MODEL_ARGS.__dict__,
879-
**TRAIN_ARGS.__dict__,
880-
**DATA_ARGS.__dict__,
881-
**PEFT_LORA_ARGS.__dict__,
882-
**{"peft_method": "lora", "output_dir": tempdir},
877+
"model_name_or_path": MODEL_ARGS.model_name_or_path,
878+
"use_flash_attn": MODEL_ARGS.use_flash_attn,
879+
"torch_dtype": MODEL_ARGS.torch_dtype,
880+
"training_data_path": DATA_ARGS.training_data_path,
881+
"response_template": DATA_ARGS.response_template,
882+
"dataset_text_field": DATA_ARGS.dataset_text_field,
883+
"num_train_epochs": TRAIN_ARGS.num_train_epochs,
884+
"per_device_train_batch_size": TRAIN_ARGS.per_device_train_batch_size,
885+
"per_device_eval_batch_size": TRAIN_ARGS.per_device_eval_batch_size,
886+
"gradient_accumulation_steps": TRAIN_ARGS.gradient_accumulation_steps,
887+
"learning_rate": TRAIN_ARGS.learning_rate,
888+
"weight_decay": TRAIN_ARGS.weight_decay,
889+
"warmup_ratio": TRAIN_ARGS.warmup_ratio,
890+
"lr_scheduler_type": TRAIN_ARGS.lr_scheduler_type,
891+
"logging_steps": TRAIN_ARGS.logging_steps,
892+
"include_tokens_per_second": TRAIN_ARGS.include_tokens_per_second,
893+
"packing": TRAIN_ARGS.packing,
894+
"max_seq_length": TRAIN_ARGS.max_seq_length,
895+
"save_strategy": TRAIN_ARGS.save_strategy,
896+
"r": PEFT_LORA_ARGS.r,
897+
"lora_alpha": PEFT_LORA_ARGS.lora_alpha,
898+
"lora_dropout": PEFT_LORA_ARGS.lora_dropout,
899+
"peft_method": "lora",
900+
"output_dir": tempdir,
883901
}
884-
serialized_args = serialize_args(TRAIN_KWARGS)
885-
monkeypatch.setenv("SFT_TRAINER_CONFIG_JSON_ENV_VAR", serialized_args)
902+
config_path = os.path.join(tempdir, "config.json")
903+
with open(config_path, "w", encoding="utf-8") as f:
904+
json.dump(TRAIN_KWARGS, f)
905+
monkeypatch.setenv("SFT_TRAINER_CONFIG_JSON_PATH", config_path)
906+
monkeypatch.delenv("SFT_TRAINER_CONFIG_JSON_ENV_VAR", raising=False)
886907

887908
sft_trainer.main()
888909

@@ -902,8 +923,6 @@ def test_successful_lora_target_modules_default_from_main(monkeypatch):
902923
"v_proj",
903924
}, "target_modules are not set to the default values."
904925

905-
os.environ.pop("SFT_TRAINER_CONFIG_JSON_ENV_VAR", None)
906-
907926

908927
def test_run_causallm_lora_add_special_tokens():
909928
"""Check if embed layer is added as modules_to_save when special tokens are added"""

tests/utils/test_config_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
# Standard
1919
import base64
20-
import pickle
20+
import json
2121

2222
# Third Party
2323
from datasets import Dataset, Features, Value
@@ -224,7 +224,7 @@ def test_get_json_config_can_load_from_envvar(monkeypatch):
224224
the json path from env var SFT_TRAINER_CONFIG_JSON_ENV_VAR
225225
"""
226226
config_json = {"model_name_or_path": "foobar"}
227-
message_bytes = pickle.dumps(config_json)
227+
message_bytes = json.dumps(config_json).encode("utf-8")
228228
base64_bytes = base64.b64encode(message_bytes)
229229
encoded_json = base64_bytes.decode("ascii")
230230
monkeypatch.delenv("SFT_TRAINER_CONFIG_JSON_PATH", raising=False)

tuning/utils/config_utils.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import base64
1818
import json
1919
import os
20-
import pickle
2120

2221
# Third Party
2322
from peft import PromptTuningConfig as HFPromptTuningConfig
@@ -159,9 +158,4 @@ def txt_to_obj(txt):
159158
"""
160159
base64_bytes = txt.encode("ascii")
161160
message_bytes = base64.b64decode(base64_bytes)
162-
try:
163-
# If the bytes represent JSON string
164-
return json.loads(message_bytes)
165-
except UnicodeDecodeError:
166-
# Otherwise the bytes are a pickled python dictionary
167-
return pickle.loads(message_bytes)
161+
return json.loads(message_bytes)

0 commit comments

Comments
 (0)