NivaarExam PrepOfficial exam papers ↗

19-Soft-B3 Security · May 2014

Question 6 of 7: Stack Buffer Overflow in a C Program

Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)

Notes on this paper

National Exams, May 2014 — 04-Soft-B3, Security/Safety (closed book, 3 hours, no calculator). FIVE of the seven questions constitute a complete paper (the first five as answered in the answer book are marked, each of equal value); this solution answers all seven as a full study resource. Most questions call for essay-format answers; clarity and organisation of the answer are important. Question 6 asks for a security analysis of a short C program.

Reference texts. Stallings & Brown, Computer Security: Principles and Practice, 4th ed., Ch. 2–3 (Cryptographic Tools, One-Time Pad, Block Ciphers), Ch. 21 (Public-Key Infrastructure, Certificate Authorities), Ch. 3 (Hash Functions, MAC), Ch. 23 (Digital Signatures), Ch. 3 & 24 (User Authentication, Two-Factor, SSO), Ch. 9 (Firewalls, DMZ), Ch. 8 (Intrusion Detection, Honeypots), Ch. 10 (Buffer Overflow); Anderson, Security Engineering, 3rd ed., Ch. 4 (Access Control, Least Privilege), Ch. 1 & 9 (Defense in Depth, Separation of Duty).

Question 6: Stack Buffer Overflow in a C Program (20 marks)

Question text not reproduced: the examination questions are © Engineers and Geoscientists BC. Open the official past paper (linked at the top of this page) to read the question, then follow the worked solution below.

Given. The listed C program, with the line numbering from the exam:

 1: int foo(char *arg)
 2: {
 3:   int i, len;
 4:   char buf[16];
 5:
 6:   len = strlen(arg);
 7:
 8:   if (len > 24)
 9:     len = 24;
10:   for (i = 0; i <= len; i++)
11:     buf[i] = arg[i];
12:   return 0;
13: }
14:
15: int main(int argc, char *argv[])
16: {
17:   char string[12];
18:
19:   strncpy(string, argv[1], 12);
20:   foo(argv[2]);
21:   return 0;
22: }

buf is a fixed 16-byte stack array (valid indices 0–15); the length check on line 8–9 clamps len to at most 24; the copy loop on line 10–11 runs while i <= len; argv[2], an attacker-controlled command-line argument, is passed into foo with no length check of its own.

Find. (a) Whether the program is vulnerable, and if so how many bytes past the end of buf an attacker can write; (b) a corrected version that removes the vulnerability.

Approach. Trace the bound-check and loop arithmetic against buf's actual declared capacity (not the unrelated constant 24) to find the worst-case number of out-of-bounds bytes written, then re-derive the same bound from sizeof(buf) to fix it.

  1. Identify buf's true capacity. char buf[16] has exactly 16 valid byte positions, indices 0 through 15. Any write to index 16 or higher is outside the array.
  2. Evaluate the length clamp. If the attacker supplies argv[2] with strlen(arg) ≥ 24, line 8–9 clamps len to exactly 24. This bound of 24 is unrelated to buf's real size of 16 — it is already 8 bytes too permissive before the loop is even considered.
  3. Evaluate the loop bound. for (i = 0; i <= len; i++) executes for $i = 0, 1, \dots, len$, i.e. $len+1$ iterations — an off-by-one, since copying "len characters" should use i < len. With the clamped worst case $len = 24$, the loop writes indices $i = 0 \ldots 24$: $$\text{bytes\_written} = len + 1 = 24 + 1 = 25$$
  4. Compute the overflow. $$\text{overflow} = \text{bytes\_written} - \text{sizeof(buf)} = 25 - 16 = \boxed{9 \text{ bytes}}$$ Indices 16–24 of the write lie past the end of buf on the stack, fully populated from attacker-controlled bytes of argv[2] — a classic stack buffer overflow (CWE-121/CWE-193, off-by-one combined with a bound check against the wrong constant). Depending on the compiler's stack layout, those 9 bytes can corrupt adjacent local variables, a saved frame pointer, or (with a slightly larger overflow) the function's return address, which is the basis of classic stack-smashing exploits.
  5. Fix the bound check. Clamp against the buffer's own declared size, not an unrelated literal: if (len > sizeof(buf) - 1) len = sizeof(buf) - 1; — giving $len_{max} = 16 - 1 = 15$, reserving one byte for a NUL terminator.
  6. Fix the loop and terminate the string. Copy exactly len bytes (drop the ≤ off-by-one) and null-terminate explicitly: for (i = 0; i < len; i++) buf[i] = arg[i]; buf[len] = '\0';. The corrected worst case writes indices $0 \ldots 14$ plus a terminator at index 15: $$\text{bytes\_written}_{fixed} = len_{max} + 1 = 15 + 1 = 16 = \text{sizeof(buf)}$$ exactly filling buf and never exceeding it, for zero bytes of overflow regardless of how long arg is.
QuantityVulnerable versionCorrected version
buf capacity16 bytes16 bytes
Length clamplen ≤ 24 (wrong constant)len ≤ sizeof(buf)-1 = 15
Loop boundi ≤ len (off-by-one)i < len, plus explicit buf[len]='\0'
Worst-case bytes written2516
Overflow past buf9 bytes0 bytes

Two secondary observations worth noting, though they are not the primary vulnerability: strncpy(string, argv[1], 12) in main is itself bound-safe (it will never write past string's 12 bytes), but if argv[1] is 12 characters or longer, strncpy does not append a NUL terminator, leaving string non-terminated for any later use; and neither argv[1] nor argv[2] is checked against argc before use, so a caller supplying too few arguments causes main to read/pass a NULL or out-of-bounds pointer. Both are latent defects in the same defensive-programming spirit as the main fix, even though the exam's Part a)/b) is centred on foo's overflow.