Skip to content

Commit a7425d0

Browse files
authored
Merge pull request #7 from Clean-Dependency-Project/test_pickle
Testing the safe unpickler
2 parents 3246a66 + 6a90e1c commit a7425d0

File tree

1 file changed

+80
-4
lines changed

1 file changed

+80
-4
lines changed

pandas/tests/io/test_pickle.py

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
Day,
5454
MonthEnd,
5555
)
56+
from unittest.mock import patch, Mock
57+
from pandas._config import get_option
5658

5759
pytestmark = pytest.mark.filterwarnings(
5860
"ignore:Timestamp.freq is deprecated:FutureWarning"
@@ -599,19 +601,93 @@ def test_pickle_frame_v124_unpickle_130():
599601
expected = pd.DataFrame()
600602
tm.assert_frame_equal(df, expected)
601603

604+
# Tests related to CVE - https://nvd.nist.gov/vuln/detail/CVE-2020-13091.
605+
# These tests cover all possible code paths introduced by the 3246a66.
606+
# Possibilities include pickle config mode permit/deny/off.
602607

603-
def test_read_pickle_forbidden():
604-
# related to CVE - https://nvd.nist.gov/vuln/detail/CVE-2020-13091
608+
def get_test_option_permit(opt):
609+
if opt == "pickler.unpickle.mode":
610+
return "permit"
611+
if opt == "pickler.safe.tuples":
612+
return [("builtins", "str")]
613+
return get_option(opt)
605614

615+
def get_test_option_deny(opt):
616+
if opt == "pickler.unpickle.mode":
617+
return "deny"
618+
return get_option(opt)
619+
620+
def get_test_option_off(opt):
621+
if opt == "pickler.unpickle.mode":
622+
return "off"
623+
return get_option(opt)
624+
625+
def prepare_unsafe_pickle(path):
606626
class MyEvilPickle:
607627
def __reduce__(self):
608628
return (os.system, ("whoami",))
609629

610630
pickle_data = pickle.dumps(MyEvilPickle())
611631
# storing the serialized output into a file in current directory
612-
path = os.path.join(os.path.dirname(__file__), "data", "pickle", "test_forbidden.pkl")
613632
with open(path, "wb") as file:
614633
file.write(pickle_data)
615634

635+
def prepare_safe_pickle(path):
636+
class MySafePickle:
637+
def __reduce__(self):
638+
return (str, ("dummy",))
639+
640+
pickle_data = pickle.dumps(MySafePickle())
641+
# storing the serialized output into a file in current directory
642+
with open(path, "wb") as file:
643+
file.write(pickle_data)
644+
645+
646+
@patch("pandas.io.pickle.get_option")
647+
@patch("pandas.compat.pickle_compat.get_option")
648+
def test_read_pickle_permit(mock_opt_compat, mock_opt):
649+
mock_opt_compat.side_effect = get_test_option_permit
650+
mock_opt.side_effect = get_test_option_permit
651+
unsafe_path = os.path.join(os.path.dirname(__file__), "data", "pickle", "test_forbidden.pkl")
652+
safe_path = os.path.join(os.path.dirname(__file__), "data", "pickle", "test_safe.pkl")
653+
654+
prepare_unsafe_pickle(unsafe_path)
655+
prepare_safe_pickle(safe_path)
656+
616657
with pytest.raises(pickle.UnpicklingError, match=r".* forbidden"):
617-
pd.read_pickle(path)
658+
pd.read_pickle(unsafe_path)
659+
660+
assert pd.read_pickle(safe_path) == "dummy"
661+
662+
663+
@patch("pandas.io.pickle.get_option")
664+
@patch("pandas.compat.pickle_compat.get_option")
665+
def test_read_pickle_deny(mock_opt_compat, mock_opt):
666+
mock_opt_compat.side_effect = get_test_option_deny
667+
mock_opt.side_effect = get_test_option_deny
668+
unsafe_path = os.path.join(os.path.dirname(__file__), "data", "pickle", "test_forbidden.pkl")
669+
safe_path = os.path.join(os.path.dirname(__file__), "data", "pickle", "test_safe.pkl")
670+
671+
prepare_unsafe_pickle(unsafe_path)
672+
prepare_safe_pickle(safe_path)
673+
674+
with pytest.raises(pickle.UnpicklingError, match=r".* forbidden"):
675+
pd.read_pickle(unsafe_path)
676+
677+
assert pd.read_pickle(safe_path) == "dummy"
678+
679+
680+
@patch("pandas.io.pickle.get_option")
681+
@patch("pandas.compat.pickle_compat.get_option")
682+
def test_read_pickle_off(mock_opt_compat, mock_opt):
683+
mock_opt_compat.side_effect = get_test_option_off
684+
mock_opt.side_effect = get_test_option_off
685+
unsafe_path = os.path.join(os.path.dirname(__file__), "data", "pickle", "test_forbidden.pkl")
686+
safe_path = os.path.join(os.path.dirname(__file__), "data", "pickle", "test_safe.pkl")
687+
688+
prepare_unsafe_pickle(unsafe_path)
689+
prepare_safe_pickle(safe_path)
690+
691+
assert pd.read_pickle(unsafe_path) == 0 #It means the command was executed!
692+
assert pd.read_pickle(safe_path) == "dummy"
693+

0 commit comments

Comments
 (0)