Skip to content

Commit 0ce5a6c

Browse files
authored
Merge pull request #40 from sysprog21/fix-buffer-overflow
Fix buffer overflow in stdio formatting functions
2 parents 25c6e3c + 2137698 commit 0ce5a6c

File tree

7 files changed

+434
-50
lines changed

7 files changed

+434
-50
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ deps += $(LIB_OBJS:%.o=%.o.d)
2929
APPS := coop echo hello mqueues semaphore mutex cond \
3030
pipes pipes_small pipes_struct prodcons progress \
3131
rtsched suspend test64 timer timer_kill \
32-
cpubench
32+
cpubench test_libc
3333

3434
# Output files for __link target
3535
IMAGE_BASE := $(BUILD_DIR)/image

app/mqueues.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ void task1(void)
3636
/* Prepare and enqueue second message (string payload) for task 3. */
3737
pmsg = &msg2;
3838

39-
sprintf(str, "hello %d from t1...", val++);
39+
snprintf(str, sizeof(str), "hello %d from t1...", val++);
4040
/* Enqueue the string. The payload points to the local 'str' buffer. */
4141
pmsg->payload = str; /* Point payload to the string buffer. */
4242
pmsg->size = strlen(str) + 1; /* Store string size. */

app/pipes_small.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ void task2(void)
77
char data2[64];
88

99
while (1) {
10-
sprintf(data2, "Hello from task 2!");
10+
snprintf(data2, sizeof(data2), "Hello from task 2!");
1111
/* write pipe - write size must be less than buffer size */
1212
mo_pipe_write(pipe2, data2, strlen((char *) data2));
1313
}
@@ -18,7 +18,7 @@ void task1(void)
1818
char data1[64];
1919

2020
while (1) {
21-
sprintf(data1, "Hello from task 1!");
21+
snprintf(data1, sizeof(data1), "Hello from task 1!");
2222
/* write pipe - write size must be less than buffer size */
2323
mo_pipe_write(pipe1, data1, strlen((char *) data1));
2424
}

app/pipes_struct.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void task1(void)
1818
ptr->b = -555;
1919

2020
while (1) {
21-
sprintf(ptr->v, "hello %ld", i++);
21+
snprintf(ptr->v, sizeof(ptr->v), "hello %ld", i++);
2222
ptr->a++;
2323
ptr->b++;
2424

app/test_libc.c

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
/* LibC Test Suite - Comprehensive tests for standard library functions.
2+
*
3+
* Current Coverage:
4+
* - vsnprintf/snprintf: Buffer overflow protection
5+
* * C99 semantics, truncation behavior, ISR safety
6+
* * Format specifiers: %s, %d, %u, %x, %p, %c, %%
7+
* * Edge cases: size=0, size=1, truncation, null termination
8+
*
9+
* Future Tests (Planned):
10+
* - String functions: strlen, strcmp, strcpy, strncpy, memcpy, memset
11+
* - Memory allocation: malloc, free, realloc
12+
* - Character classification: isdigit, isalpha, isspace, etc.
13+
*/
14+
15+
#include <linmo.h>
16+
17+
#define TEST_PASS 1
18+
#define TEST_FAIL 0
19+
20+
/* Test result tracking */
21+
static int tests_run = 0;
22+
static int tests_passed = 0;
23+
static int tests_failed = 0;
24+
25+
/* Simple string comparison for tests */
26+
static int test_strcmp(const char *s1, const char *s2)
27+
{
28+
while (*s1 && (*s1 == *s2))
29+
s1++, s2++;
30+
return *s1 - *s2;
31+
}
32+
33+
/* Simple string length for tests */
34+
static size_t test_strlen(const char *s)
35+
{
36+
const char *p = s;
37+
while (*p)
38+
p++;
39+
return p - s;
40+
}
41+
42+
/* Test assertion macro */
43+
#define ASSERT_TEST(condition, test_name) \
44+
do { \
45+
tests_run++; \
46+
if (condition) { \
47+
tests_passed++; \
48+
printf("[PASS] %s\n", test_name); \
49+
} else { \
50+
tests_failed++; \
51+
printf("[FAIL] %s\n", test_name); \
52+
} \
53+
} while (0)
54+
55+
/* Test 1: Basic functionality with sufficient buffer */
56+
void test_basic_functionality(void)
57+
{
58+
char buf[64];
59+
int ret;
60+
61+
/* Test simple string */
62+
ret = snprintf(buf, sizeof(buf), "Hello World");
63+
ASSERT_TEST(ret == 11 && test_strcmp(buf, "Hello World") == 0,
64+
"Basic string formatting");
65+
66+
/* Test integer formatting */
67+
ret = snprintf(buf, sizeof(buf), "Number: %d", 42);
68+
ASSERT_TEST(ret == 10 && test_strcmp(buf, "Number: 42") == 0,
69+
"Integer formatting");
70+
71+
/* Test unsigned formatting */
72+
ret = snprintf(buf, sizeof(buf), "Unsigned: %u", 123);
73+
ASSERT_TEST(ret == 13 && test_strcmp(buf, "Unsigned: 123") == 0,
74+
"Unsigned formatting");
75+
76+
/* Test hex formatting */
77+
ret = snprintf(buf, sizeof(buf), "Hex: %x", 0xDEAD);
78+
ASSERT_TEST(ret == 9 && test_strcmp(buf, "Hex: dead") == 0,
79+
"Hex formatting");
80+
81+
/* Test pointer formatting */
82+
void *ptr = (void *) 0x12345678;
83+
ret = snprintf(buf, sizeof(buf), "Ptr: %p", ptr);
84+
ASSERT_TEST(ret == 13 && test_strcmp(buf, "Ptr: 12345678") == 0,
85+
"Pointer formatting");
86+
87+
/* Test character formatting */
88+
ret = snprintf(buf, sizeof(buf), "Char: %c", 'A');
89+
ASSERT_TEST(ret == 7 && test_strcmp(buf, "Char: A") == 0,
90+
"Character formatting");
91+
92+
/* Test multiple format specifiers */
93+
ret = snprintf(buf, sizeof(buf), "%d %s %x", 42, "test", 0xFF);
94+
ASSERT_TEST(ret == 10 && test_strcmp(buf, "42 test ff") == 0,
95+
"Multiple format specifiers");
96+
}
97+
98+
/* Test 2: Edge case - size = 0 (C99 semantics) */
99+
void test_size_zero(void)
100+
{
101+
char buf[10] = "unchanged";
102+
int ret;
103+
104+
/* C99: should return length that would be written, no buffer modification
105+
*/
106+
ret = snprintf(buf, 0, "Hello World");
107+
ASSERT_TEST(ret == 11 && test_strcmp(buf, "unchanged") == 0,
108+
"Size=0 preserves buffer");
109+
110+
/* NULL buffer with size=0 is valid (C99) */
111+
ret = snprintf(NULL, 0, "Test %d", 123);
112+
ASSERT_TEST(ret == 8, "NULL buffer with size=0");
113+
}
114+
115+
/* Test 3: Edge case - size = 1 (only null terminator) */
116+
void test_size_one(void)
117+
{
118+
char buf[10];
119+
int ret;
120+
121+
buf[0] = 'X'; /* Sentinel value */
122+
ret = snprintf(buf, 1, "Hello");
123+
ASSERT_TEST(ret == 5 && buf[0] == '\0',
124+
"Size=1 writes only null terminator");
125+
}
126+
127+
/* Test 4: Truncation scenarios */
128+
void test_truncation(void)
129+
{
130+
char buf[10];
131+
int ret;
132+
133+
/* String longer than buffer */
134+
ret = snprintf(buf, sizeof(buf), "This is a very long string");
135+
ASSERT_TEST(ret == 26 && test_strlen(buf) == 9 && buf[9] == '\0',
136+
"Truncation with long string");
137+
138+
/* Exact fit (9 chars + null in 10-byte buffer) */
139+
ret = snprintf(buf, sizeof(buf), "123456789");
140+
ASSERT_TEST(
141+
ret == 9 && test_strcmp(buf, "123456789") == 0 && buf[9] == '\0',
142+
"Exact fit");
143+
144+
/* One char too long */
145+
ret = snprintf(buf, sizeof(buf), "1234567890");
146+
ASSERT_TEST(
147+
ret == 10 && test_strcmp(buf, "123456789") == 0 && buf[9] == '\0',
148+
"One char truncation");
149+
150+
/* Format string producing truncated output */
151+
ret = snprintf(buf, 8, "Value: %d", 12345);
152+
ASSERT_TEST(ret == 12 && test_strcmp(buf, "Value: ") == 0 && buf[7] == '\0',
153+
"Format truncation");
154+
}
155+
156+
/* Test 5: Null-termination guarantee */
157+
void test_null_termination(void)
158+
{
159+
char buf[5];
160+
int ret;
161+
162+
/* Fill buffer to verify null-termination */
163+
for (int i = 0; i < 5; i++)
164+
buf[i] = 'X';
165+
166+
ret = snprintf(buf, 5, "1234567890");
167+
ASSERT_TEST(buf[4] == '\0', "Null termination guaranteed");
168+
169+
/* Verify buffer was limited */
170+
ASSERT_TEST(test_strcmp(buf, "1234") == 0, "Truncated content correct");
171+
172+
/* C99 return value: chars that would be written */
173+
ASSERT_TEST(ret == 10, "C99 return value for truncation");
174+
}
175+
176+
/* Test 6: Format specifier edge cases */
177+
void test_format_specifiers(void)
178+
{
179+
char buf[32];
180+
int ret;
181+
182+
/* Null string pointer */
183+
ret = snprintf(buf, sizeof(buf), "String: %s", (char *) NULL);
184+
ASSERT_TEST(test_strcmp(buf, "String: <NULL>") == 0,
185+
"NULL string handling");
186+
187+
/* Negative numbers */
188+
ret = snprintf(buf, sizeof(buf), "%d", -42);
189+
ASSERT_TEST(test_strcmp(buf, "-42") == 0, "Negative number formatting");
190+
191+
/* Zero */
192+
ret = snprintf(buf, sizeof(buf), "%d %u %x", 0, 0, 0);
193+
ASSERT_TEST(test_strcmp(buf, "0 0 0") == 0, "Zero formatting");
194+
195+
/* Width padding */
196+
ret = snprintf(buf, sizeof(buf), "%5d", 42);
197+
ASSERT_TEST(test_strcmp(buf, " 42") == 0, "Width padding");
198+
199+
/* Zero padding */
200+
ret = snprintf(buf, sizeof(buf), "%05d", 42);
201+
ASSERT_TEST(test_strcmp(buf, "00042") == 0, "Zero padding");
202+
203+
/* Literal percent */
204+
ret = snprintf(buf, sizeof(buf), "100%% complete");
205+
ASSERT_TEST(test_strcmp(buf, "100% complete") == 0, "Literal percent sign");
206+
207+
(void) ret; /* Return values tested in test_return_values() */
208+
}
209+
210+
/* Test 7: C99 return value semantics */
211+
void test_return_values(void)
212+
{
213+
char buf[10];
214+
int ret;
215+
216+
/* Return value = chars that would be written (excluding null) */
217+
ret = snprintf(buf, sizeof(buf), "12345");
218+
ASSERT_TEST(ret == 5, "Return value for normal case");
219+
220+
/* Return value for truncated string */
221+
ret = snprintf(buf, 5, "1234567890");
222+
ASSERT_TEST(ret == 10, "Return value for truncated case");
223+
224+
/* Empty string */
225+
ret = snprintf(buf, sizeof(buf), "");
226+
ASSERT_TEST(ret == 0 && buf[0] == '\0', "Empty string return value");
227+
}
228+
229+
/* Test 8: Buffer boundary verification */
230+
void test_buffer_boundaries(void)
231+
{
232+
char buf[16];
233+
char guard_before = 0xAA;
234+
char guard_after = 0xBB;
235+
int ret;
236+
237+
/* Place guards around buffer */
238+
char *test_buf = &buf[1];
239+
buf[0] = guard_before;
240+
buf[15] = guard_after;
241+
242+
/* Write to middle buffer, verify guards intact */
243+
ret = snprintf(test_buf, 14, "Test boundary");
244+
ASSERT_TEST(buf[0] == guard_before && buf[15] == guard_after,
245+
"No buffer overrun");
246+
247+
ASSERT_TEST(test_strcmp(test_buf, "Test boundary") == 0,
248+
"Content correct within boundaries");
249+
250+
(void) ret; /* Return value not critical for boundary test */
251+
}
252+
253+
/* Test 9: ISR safety verification */
254+
void test_isr_safety(void)
255+
{
256+
char buf[32];
257+
int ret;
258+
259+
/* Verify no dynamic allocation (manual inspection of code required)
260+
* Verify bounded execution time (all format handlers O(1) or O(n) small n)
261+
* Verify reentrancy (no global state modified)
262+
*/
263+
264+
/* Test can be called multiple times without side effects */
265+
ret = snprintf(buf, sizeof(buf), "ISR Test %d", 123);
266+
char saved[32];
267+
for (int i = 0; i < 32; i++)
268+
saved[i] = buf[i];
269+
270+
ret = snprintf(buf, sizeof(buf), "ISR Test %d", 123);
271+
int match = 1;
272+
for (int i = 0; i < 32; i++) {
273+
if (buf[i] != saved[i]) {
274+
match = 0;
275+
break;
276+
}
277+
}
278+
ASSERT_TEST(match == 1, "Reentrant behavior (no global state)");
279+
280+
(void) ret; /* Return value not critical for ISR safety test */
281+
}
282+
283+
/* Test 10: Mixed format stress test */
284+
void test_mixed_formats(void)
285+
{
286+
char buf[128];
287+
int ret;
288+
289+
/* Complex format string with multiple types */
290+
ret = snprintf(buf, sizeof(buf),
291+
"Task %d: ptr=%p, count=%u, hex=%x, char=%c, str=%s", 5,
292+
(void *) 0xABCD, 100, 0xFF, 'X', "test");
293+
294+
/* Verify no crash and reasonable return value */
295+
ASSERT_TEST(ret > 0 && ret < 128, "Mixed format stress test");
296+
297+
/* Verify null termination */
298+
ASSERT_TEST(buf[test_strlen(buf)] == '\0', "Mixed format null termination");
299+
}
300+
301+
void test_runner(void)
302+
{
303+
printf("\n=== LibC Test Suite ===\n");
304+
printf("Testing: vsnprintf/snprintf\n\n");
305+
306+
test_basic_functionality();
307+
test_size_zero();
308+
test_size_one();
309+
test_truncation();
310+
test_null_termination();
311+
test_format_specifiers();
312+
test_return_values();
313+
test_buffer_boundaries();
314+
test_isr_safety();
315+
test_mixed_formats();
316+
317+
printf("\n=== Test Summary ===\n");
318+
printf("Tests run: %d\n", tests_run);
319+
printf("Tests passed: %d\n", tests_passed);
320+
printf("Tests failed: %d\n", tests_failed);
321+
322+
if (tests_failed == 0) {
323+
printf("\n[SUCCESS] All tests passed!\n");
324+
} else {
325+
printf("\n[FAILURE] %d test(s) failed!\n", tests_failed);
326+
}
327+
}
328+
329+
int32_t app_main(void)
330+
{
331+
test_runner();
332+
333+
/* Shutdown QEMU cleanly after tests complete */
334+
*(volatile uint32_t *) 0x100000U = 0x5555U;
335+
336+
/* Fallback: keep task alive if shutdown fails (non-QEMU environments) */
337+
while (1)
338+
; /* Idle loop */
339+
340+
return 0;
341+
}

0 commit comments

Comments
 (0)