diff options
| author | 2026-01-22 13:12:05 +0100 | |
|---|---|---|
| committer | 2026-01-22 13:12:05 +0100 | |
| commit | ac7b64160f5c204b11451cdda0165e0a1a810188 (patch) | |
| tree | b08f887081b773a297f04b3f61e3277c925195c7 /src | |
| parent | 502eb2a10201e98f247186672add810e54afc15b (diff) | |
| download | libplist-ac7b64160f5c204b11451cdda0165e0a1a810188.tar.gz libplist-ac7b64160f5c204b11451cdda0165e0a1a810188.tar.bz2 | |
xplist: Harden entity unescaping against malformed input
- Fix numeric character reference parsing
- Enforce exact entity name matching
- Guard against size_t underflow and oversized entities
- Reject invalid Unicode code points
Diffstat (limited to 'src')
| -rw-r--r-- | src/xplist.c | 50 |
1 files changed, 32 insertions, 18 deletions
diff --git a/src/xplist.c b/src/xplist.c index 9870104..55b01e9 100644 --- a/src/xplist.c +++ b/src/xplist.c | |||
| @@ -853,42 +853,50 @@ static int unescape_entities(char *str, size_t *length) | |||
| 853 | PLIST_XML_ERR("Invalid entity sequence encountered (missing terminating ';')\n"); | 853 | PLIST_XML_ERR("Invalid entity sequence encountered (missing terminating ';')\n"); |
| 854 | return -1; | 854 | return -1; |
| 855 | } | 855 | } |
| 856 | if (str+i >= entp+1) { | 856 | size_t entlen = (size_t)(str+i - entp); |
| 857 | int entlen = str+i - entp; | 857 | if (entlen > 0) { |
| 858 | int bytelen = 1; | 858 | if (entlen > 256) { |
| 859 | if (!strncmp(entp, "amp", 3)) { | 859 | PLIST_XML_ERR("Rejecting absurdly large entity at &%.*s...\n", 8, entp); |
| 860 | return -1; | ||
| 861 | } | ||
| 862 | size_t bytelen = 1; | ||
| 863 | if (entlen == 3 && memcmp(entp, "amp", 3) == 0) { | ||
| 860 | /* the '&' is already there */ | 864 | /* the '&' is already there */ |
| 861 | } else if (!strncmp(entp, "apos", 4)) { | 865 | } else if (entlen == 4 && memcmp(entp, "apos", 4) == 0) { |
| 862 | *(entp-1) = '\''; | 866 | *(entp-1) = '\''; |
| 863 | } else if (!strncmp(entp, "quot", 4)) { | 867 | } else if (entlen == 4 && memcmp(entp, "quot", 4) == 0) { |
| 864 | *(entp-1) = '"'; | 868 | *(entp-1) = '"'; |
| 865 | } else if (!strncmp(entp, "lt", 2)) { | 869 | } else if (entlen == 2 && memcmp(entp, "lt", 2) == 0) { |
| 866 | *(entp-1) = '<'; | 870 | *(entp-1) = '<'; |
| 867 | } else if (!strncmp(entp, "gt", 2)) { | 871 | } else if (entlen == 2 && memcmp(entp, "gt", 2) == 0) { |
| 868 | *(entp-1) = '>'; | 872 | *(entp-1) = '>'; |
| 869 | } else if (*entp == '#') { | 873 | } else if (*entp == '#') { |
| 870 | /* numerical character reference */ | 874 | /* numerical character reference */ |
| 871 | uint64_t val = 0; | 875 | uint64_t val = 0; |
| 872 | char* ep = NULL; | 876 | char* ep = NULL; |
| 873 | if (entlen > 8) { | 877 | if (entlen > 8) { |
| 874 | PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too long: &%.*s;\n", entlen, entp); | 878 | PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too long: &%.*s;\n", (int)entlen, entp); |
| 875 | return -1; | 879 | return -1; |
| 876 | } | 880 | } |
| 877 | if (*(entp+1) == 'x' || *(entp+1) == 'X') { | 881 | if (entlen >= 2 && (entp[1] == 'x' || entp[1] == 'X')) { |
| 878 | if (entlen < 3) { | 882 | if (entlen < 3) { |
| 879 | PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", entlen, entp); | 883 | PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", (int)entlen, entp); |
| 880 | return -1; | 884 | return -1; |
| 881 | } | 885 | } |
| 882 | val = strtoull(entp+2, &ep, 16); | 886 | val = strtoull(entp+2, &ep, 16); |
| 883 | } else { | 887 | } else { |
| 884 | if (entlen < 2) { | 888 | if (entlen < 2) { |
| 885 | PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", entlen, entp); | 889 | PLIST_XML_ERR("Invalid numerical character reference encountered, sequence too short: &%.*s;\n", (int)entlen, entp); |
| 886 | return -1; | 890 | return -1; |
| 887 | } | 891 | } |
| 888 | val = strtoull(entp+1, &ep, 10); | 892 | val = strtoull(entp+1, &ep, 10); |
| 889 | } | 893 | } |
| 890 | if (val == 0 || val > 0x10FFFF || ep-entp != entlen) { | 894 | if (val == 0 || val > 0x10FFFF || (size_t)(ep-entp) != entlen) { |
| 891 | PLIST_XML_ERR("Invalid numerical character reference found: &%.*s;\n", entlen, entp); | 895 | PLIST_XML_ERR("Invalid numerical character reference found: &%.*s;\n", (int)entlen, entp); |
| 896 | return -1; | ||
| 897 | } | ||
| 898 | if (val >= 0xD800 && val <= 0xDFFF) { | ||
| 899 | PLIST_XML_ERR("Invalid numerical character reference found (surrogate): &%.*s;\n", (int)entlen, entp); | ||
| 892 | return -1; | 900 | return -1; |
| 893 | } | 901 | } |
| 894 | /* convert to UTF8 */ | 902 | /* convert to UTF8 */ |
| @@ -918,12 +926,18 @@ static int unescape_entities(char *str, size_t *length) | |||
| 918 | *(entp-1) = (char)(val & 0x7F); | 926 | *(entp-1) = (char)(val & 0x7F); |
| 919 | } | 927 | } |
| 920 | } else { | 928 | } else { |
| 921 | PLIST_XML_ERR("Invalid entity encountered: &%.*s;\n", entlen, entp); | 929 | PLIST_XML_ERR("Invalid entity encountered: &%.*s;\n", (int)entlen, entp); |
| 930 | return -1; | ||
| 931 | } | ||
| 932 | memmove(entp, str+i+1, len - i); /* include '\0' */ | ||
| 933 | size_t dec = entlen + 1 - bytelen; | ||
| 934 | size_t shrink = entlen + 2 - bytelen; | ||
| 935 | if (i < dec || len < shrink) { | ||
| 936 | PLIST_XML_ERR("Internal error: length underflow?!\n"); | ||
| 922 | return -1; | 937 | return -1; |
| 923 | } | 938 | } |
| 924 | memmove(entp, str+i+1, len - i); | 939 | i -= dec; |
| 925 | i -= entlen+1 - bytelen; | 940 | len -= shrink; |
| 926 | len -= entlen+2 - bytelen; | ||
| 927 | continue; | 941 | continue; |
| 928 | } else { | 942 | } else { |
| 929 | PLIST_XML_ERR("Invalid empty entity sequence &;\n"); | 943 | PLIST_XML_ERR("Invalid empty entity sequence &;\n"); |
