|
53 | 53 | Day, |
54 | 54 | MonthEnd, |
55 | 55 | ) |
| 56 | +from unittest.mock import patch, Mock |
| 57 | +from pandas._config import get_option |
56 | 58 |
|
57 | 59 | pytestmark = pytest.mark.filterwarnings( |
58 | 60 | "ignore:Timestamp.freq is deprecated:FutureWarning" |
@@ -599,19 +601,93 @@ def test_pickle_frame_v124_unpickle_130(): |
599 | 601 | expected = pd.DataFrame() |
600 | 602 | tm.assert_frame_equal(df, expected) |
601 | 603 |
|
| 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. |
602 | 607 |
|
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) |
605 | 614 |
|
| 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): |
606 | 626 | class MyEvilPickle: |
607 | 627 | def __reduce__(self): |
608 | 628 | return (os.system, ("whoami",)) |
609 | 629 |
|
610 | 630 | pickle_data = pickle.dumps(MyEvilPickle()) |
611 | 631 | # storing the serialized output into a file in current directory |
612 | | - path = os.path.join(os.path.dirname(__file__), "data", "pickle", "test_forbidden.pkl") |
613 | 632 | with open(path, "wb") as file: |
614 | 633 | file.write(pickle_data) |
615 | 634 |
|
| 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 | + |
616 | 657 | 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