Skip to content

Commit 9bfed2c

Browse files
committed
1 parent 641bdaa commit 9bfed2c

File tree

17 files changed

+684
-6
lines changed

17 files changed

+684
-6
lines changed

BackendAst/DAstConstruction.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,7 @@ let calculateFunctionToBeGenerated (r:Asn1AcnAst.AstRoot) (us:State) =
11271127
let functionCalls = us.functionCalls
11281128
let requiresUPER = r.args.encodings |> Seq.exists ( (=) Asn1Encoding.UPER)
11291129
let requiresAcn = r.args.encodings |> Seq.exists ( (=) Asn1Encoding.ACN)
1130+
let requiresXer = r.args.encodings |> Seq.exists ( (=) Asn1Encoding.XER)
11301131

11311132
let functionTypes =
11321133
seq {
@@ -1135,6 +1136,7 @@ let calculateFunctionToBeGenerated (r:Asn1AcnAst.AstRoot) (us:State) =
11351136
if r.args.GenerateEqualFunctions then yield EqualFunctionType;
11361137
if requiresUPER then yield UperEncDecFunctionType;
11371138
if requiresAcn then yield AcnEncDecFunctionType;
1139+
if requiresXer then yield XerEncDecFunctionType;
11381140
} |> Seq.toList
11391141

11401142
(*

FrontEndAst/AcnCreateFromAntlr.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@ let private getIntSizeProperty errLoc (props:GenericAcnProperty list) =
4343
| Some (GP_NullTerminated ) ->
4444
match tryGetProp props (fun x -> match x with TERMINATION_PATTERN e -> Some e | _ -> None) with
4545
| Some bitPattern ->
46+
printfn "bitPattern: %A" bitPattern
47+
printfn "bitPattern.Value.Length: %A" bitPattern.Value.Length
4648
match bitPattern.Value.Length % 8 <> 0 with
4749
| true -> raise(SemanticError(bitPattern.Location, sprintf "termination-pattern value must be a sequence of bytes" ))
4850
| false ->
4951
let ba = bitStringValueToByteArray bitPattern |> Seq.toList
52+
if ba.Length > 10 then
53+
raise(SemanticError(bitPattern.Location, "termination-pattern cannot exceed 10 bytes"))
5054
Some(AcnGenericTypes.IntNullTerminated ba)
5155
| None -> Some(AcnGenericTypes.IntNullTerminated ([byte 0]))
5256
| Some (GP_SizeDeterminant _) -> raise(SemanticError(errLoc ,"Expecting an Integer value or an ACN constant as value for the size property"))
@@ -70,6 +74,8 @@ let private getStringSizeProperty (minSize:BigInteger) (maxSize:BigInteger) errL
7074
| true -> raise(SemanticError(bitPattern.Location, sprintf "termination-pattern value must be a sequence of bytes" ))
7175
| false ->
7276
let ba = bitStringValueToByteArray bitPattern |> Seq.toList
77+
if ba.Length > 10 then
78+
raise(SemanticError(bitPattern.Location, "termination-pattern cannot exceed 10 bytes"))
7379
Some(AcnGenericTypes.StrNullTerminated ba)
7480
| None -> Some(AcnGenericTypes.StrNullTerminated ([byte 0]))
7581
| Some (GP_SizeDeterminant fld) -> (Some (AcnGenericTypes.StrExternalField fld))

StgAda/xer_a.stg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ adaasn1rtl.encoding.xer.Xer_DecodeComplexElementStart(bs, <sTag>, ret);
282282
if ret.Success then
283283
<sI> := 1;
284284
while ret.Success and not adaasn1rtl.encoding.xer.Xer_NextEndElementIs(bs, <sTag>) loop
285+
if <sI> > <nSizeMax> then
286+
ret.Success := False;
287+
ret.ErrorCode := <sErrCode>;
288+
exit;
289+
end if;
285290
<sChildBody>
286291
<sI> := <sI> + 1;
287292
end loop;

StgC/xer_c.stg

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ if (ret) {
263263
<sI> = 0;
264264
while(ret && !Xer_NextEndElementIs(pByteStrm, <sTag>))
265265
{
266+
if (<sI> >= <nSizeMax>) {
267+
ret = FALSE;
268+
*pErrCode = <sErrCode>;
269+
break;
270+
}
266271
<sChildBody>
267272
<sI>++;
268273
<if(!bFixedSize)><p><sAcc>nCount++;<endif>

asn1crt/asn1crt_encoding_acn.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -855,16 +855,16 @@ flag Acn_Dec_UInt_ASCII_VarSize_NullTerminated(BitStream* pBitStrm, asn1SccUint*
855855
memset(tmp, 0x0, 10);
856856

857857
//read null_character_size characters into the tmp buffer
858-
for (int j = 0; j < (int)null_characters_size; j++) {
858+
for (int j = 0; j < (int)sz; j++) {
859859
if (!BitStream_ReadByte(pBitStrm, &(tmp[j])))
860860
return FALSE;
861861
}
862862

863863
while (memcmp(null_characters, tmp, sz) != 0) {
864864
digit = tmp[0];
865-
for (int j = 0; j < (int)null_characters_size - 1; j++)
865+
for (int j = 0; j < (int)sz - 1; j++)
866866
tmp[j] = tmp[j + 1];
867-
if (!BitStream_ReadByte(pBitStrm, &(tmp[null_characters_size - 1])))
867+
if (!BitStream_ReadByte(pBitStrm, &(tmp[sz - 1])))
868868
return FALSE;
869869

870870
digit = (byte)((int)digit - '0');
@@ -1324,7 +1324,7 @@ flag Acn_Dec_String_Ascii_Null_Terminated_mult(BitStream* pBitStrm, asn1SccSint
13241324
memset(tmp, 0x0, 10);
13251325
memset(strVal, 0x0, (size_t)max + 1);
13261326
//read null_character_size characters into the tmp buffer
1327-
for (int j = 0; j < (int)null_character_size; j++) {
1327+
for (int j = 0; j < (int)sz; j++) {
13281328
if (!BitStream_ReadByte(pBitStrm, &(tmp[j])))
13291329
return FALSE;
13301330
}
@@ -1333,9 +1333,9 @@ flag Acn_Dec_String_Ascii_Null_Terminated_mult(BitStream* pBitStrm, asn1SccSint
13331333
while (i <= max && (memcmp(null_character, tmp, sz) != 0)) {
13341334
strVal[i] = tmp[0];
13351335
i++;
1336-
for (int j = 0; j < (int)null_character_size - 1; j++)
1336+
for (int j = 0; j < (int)sz - 1; j++)
13371337
tmp[j] = tmp[j + 1];
1338-
if (!BitStream_ReadByte(pBitStrm, &(tmp[null_character_size - 1])))
1338+
if (!BitStream_ReadByte(pBitStrm, &(tmp[sz - 1])))
13391339
return FALSE;
13401340
}
13411341

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Case 1 – XER Primitive Decoder Buffer Overflow (Issue #367)
2+
3+
This directory contains a security regression test for a buffer overflow that previously existed in the XER primitive decoder.
4+
5+
The issue affected `Xer_DecodePrimitiveElement()`, which copied decoded XML content into a fixed-size buffer without bounds checking. A malicious XER/XML input with oversized element content could trigger a buffer overflow and crash the decoder.
6+
7+
This test verifies that the decoder now **fails safely** (returns an error) instead of overflowing memory.
8+
9+
## Contents
10+
11+
- `a.asn`
12+
Minimal ASN.1 grammar used to generate a XER decoder.
13+
14+
- `malicious.xml`
15+
Crafted XER input with oversized element content intended to trigger the overflow.
16+
17+
- `reproduce_issue.sh`
18+
Script that:
19+
1. Runs `asn1scc` with XER support
20+
2. Builds the generated code
21+
3. Invokes the decoder on `malicious.xml`
22+
23+
- `SECURITY_ISSUE_1_XER_BUFFER_OVERFLOW.md`
24+
Original security report and proposed fix.
25+
26+
## How to run
27+
28+
From this directory:
29+
30+
```bash
31+
./reproduce_issue.sh
32+
```
33+
This will execute the test and demonstrate that the decoder now handles the malicious input without crashing, confirming that the buffer overflow has been mitigated.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Security Issue: XER Primitive Decoder Buffer Overflow
2+
3+
## Summary
4+
5+
`Xer_DecodePrimitiveElement` copies XML element content into a caller-supplied buffer without bounds checking. Malformed XER input with oversized element content can overflow fixed-size stack buffers.
6+
7+
## Location
8+
9+
- **File:** `asn1crt/asn1crt_encoding_xer.c`
10+
- **Function:** `Xer_DecodePrimitiveElement` (lines 248-330)
11+
- **Exposed via:** `Xer_DecodeString`, `Xer_DecodeOctetString`, `Xer_DecodeObjectIdentifier`, etc.
12+
13+
## Affected Code
14+
15+
```c
16+
while (c != '<')
17+
{
18+
if (!GetNextChar(pByteStrm, &c))
19+
return FALSE;
20+
if (c == '<') {
21+
*pDecodedValue = 0x0;
22+
pDecodedValue++;
23+
break;
24+
}
25+
26+
*pDecodedValue = c; // No bounds check
27+
pDecodedValue++;
28+
}
29+
```
30+
31+
## Impact
32+
33+
- Callers use fixed buffers: 256 bytes (integers), 1024 bytes (octet strings, OIDs), 2048 bytes (bit strings)
34+
- `Xer_DecodeString` passes through user buffer with unknown size
35+
- Overflow corrupts stack, may enable code execution or crash
36+
37+
## Prerequisites
38+
39+
1. Application uses `-XER` flag during code generation
40+
2. Application decodes XER/XML data from untrusted source
41+
3. Attacker provides element content exceeding buffer size
42+
43+
## CVSS v3.1 Estimate
44+
45+
**Vector:** `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`
46+
**Score:** 7.5 (High) - assuming XER decoder processes network input
47+
48+
If code execution is achievable: `AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H` = 8.1
49+
50+
*Note: Real-world impact depends on whether XER is used with untrusted input.*
51+
52+
## Suggested Fix
53+
54+
```diff
55+
--- a/asn1crt/asn1crt_encoding_xer.c
56+
+++ b/asn1crt/asn1crt_encoding_xer.c
57+
@@ -245,8 +245,9 @@ flag Xer_EncodePrimitiveElement(ByteStream* pByteStrm, const char* elementTag, c
58+
}
59+
60+
61+
-flag Xer_DecodePrimitiveElement(ByteStream* pByteStrm, const char* elementTag, char* pDecodedValue, int *pErrCode)
62+
+flag Xer_DecodePrimitiveElement(ByteStream* pByteStrm, const char* elementTag, char* pDecodedValue, size_t maxLen, int *pErrCode)
63+
{
64+
+ size_t written = 0;
65+
Token t;
66+
char c = 0x0;
67+
68+
@@ -288,12 +289,17 @@ flag Xer_DecodePrimitiveElement(ByteStream* pByteStrm, const char* elementTag, c
69+
while (c != '<')
70+
{
71+
if (!GetNextChar(pByteStrm, &c))
72+
return FALSE;
73+
if (c == '<') {
74+
*pDecodedValue = 0x0;
75+
break;
76+
}
77+
+ if (written >= maxLen - 1) {
78+
+ *pErrCode = ERR_INVALID_XML_FILE;
79+
+ return FALSE;
80+
+ }
81+
*pDecodedValue = c;
82+
pDecodedValue++;
83+
+ written++;
84+
}
85+
86+
PushBackChar(pByteStrm);
87+
```
88+
89+
Update all callers to pass buffer size:
90+
91+
```diff
92+
--- a/asn1crt/asn1crt_encoding_xer.c
93+
+++ b/asn1crt/asn1crt_encoding_xer.c
94+
@@ -707,7 +707,7 @@ flag Xer_DecodeInteger(ByteStream* pByteStrm, const char* elementTag, asn1SccSin
95+
{
96+
char tmp[256];
97+
memset(tmp, 0x0, sizeof(tmp));
98+
- if (!Xer_DecodePrimitiveElement(pByteStrm, elementTag, tmp, pErrCode))
99+
+ if (!Xer_DecodePrimitiveElement(pByteStrm, elementTag, tmp, sizeof(tmp), pErrCode))
100+
return FALSE;
101+
*value = atoll(tmp);
102+
return TRUE;
103+
```
104+
105+
## Testing
106+
107+
1. Create XER input with element content > 2048 bytes
108+
2. Decode using generated XER decoder
109+
3. Verify decoder returns error instead of crashing
110+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
MOD-A DEFINITIONS ::=
2+
BEGIN
3+
4+
MyInt ::= INTEGER (0..40)
5+
BYTE ::= INTEGER (0..255)
6+
RGB ::= SEQUENCE {
7+
r BYTE,
8+
g BYTE,
9+
c BYTE
10+
}
11+
12+
PDU ::= SEQUENCE {
13+
a INTEGER (0..10),
14+
b INTEGER (0..10),
15+
c MyInt,
16+
d BYTE
17+
18+
}
19+
20+
DUMMY ::= SEQUENCE {
21+
c INTEGER (0..10),
22+
d INTEGER (0..10)
23+
}
24+
25+
END
26+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<PDU><a>111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111</a><b>1</b><c>1</c><d>1</d></PDU>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/bin/bash
2+
3+
# Configuration
4+
ASN1SCC="asn1scc.exe"
5+
OUT_DIR="c_out"
6+
RUNNER="runner.c"
7+
RUNNER_EXE="runner.exe"
8+
XML_FILE="malicious.xml"
9+
10+
# Cleanup previous run
11+
rm -rf $OUT_DIR $RUNNER $RUNNER_EXE $XML_FILE
12+
13+
# 1. Generate Code using asn1scc
14+
echo "Step 1: Generating C code..."
15+
mkdir -p $OUT_DIR
16+
$ASN1SCC -XER -c -o $OUT_DIR -atc a.asn
17+
if [ $? -ne 0 ]; then
18+
echo "Error: ASN1SCC compilation failed."
19+
exit 1
20+
fi
21+
22+
# 2. Prepare Input (Malicious XML)
23+
echo "Step 2: Creating malicious input ($XML_FILE)..."
24+
# Create a string of 300 '1's for the integer 'a' to overflow the 256 byte buffer
25+
# We construct the payload manually to ensure portability
26+
PAYLOAD=""
27+
for i in {1..300}; do PAYLOAD="${PAYLOAD}1"; done
28+
29+
# Note: The structure is PDU ::= SEQUENCE { a INTEGER, b INTEGER, c MyInt, d BYTE }
30+
# XML format for XER usually uses the field names as tags.
31+
echo "<PDU><a>$PAYLOAD</a><b>1</b><c>1</c><d>1</d></PDU>" > $XML_FILE
32+
33+
# 3. Create Test Runner dynamically
34+
echo "Step 3: Creating runner source code..."
35+
cat << 'EOF' > $RUNNER
36+
#include <stdio.h>
37+
#include <stdlib.h>
38+
#include <string.h>
39+
40+
/* Include generated headers */
41+
#include "a.h"
42+
43+
int main() {
44+
const char *filename = "malicious.xml";
45+
FILE *f = fopen(filename, "rb");
46+
if (!f) {
47+
fprintf(stderr, "Error: Could not open %s\n", filename);
48+
return 1;
49+
}
50+
51+
fseek(f, 0, SEEK_END);
52+
long fsize = ftell(f);
53+
fseek(f, 0, SEEK_SET);
54+
55+
char *buffer = (char *)malloc(fsize + 1);
56+
if (!buffer) {
57+
fprintf(stderr, "Error: Memory allocation failed\n");
58+
fclose(f);
59+
return 1;
60+
}
61+
62+
size_t read_size = fread(buffer, 1, fsize, f);
63+
buffer[read_size] = 0;
64+
fclose(f);
65+
66+
printf("Read %ld bytes from %s\n", (long)read_size, filename);
67+
68+
/* Initialize decoding structures */
69+
PDU pdu;
70+
PDU_Initialize(&pdu);
71+
72+
ByteStream strm;
73+
ByteStream_Init(&strm, (byte*)buffer, read_size);
74+
75+
int errorCode = 0;
76+
77+
printf("Attempting to decode malicious input...\n");
78+
flag result = PDU_XER_Decode(&pdu, &strm, &errorCode);
79+
80+
free(buffer);
81+
82+
/* Validation Logic */
83+
/* If the fix works, it should detect the overflow/length and return FALSE */
84+
if (result == 0) { /* FALSE is 0 */
85+
printf("PASS: Decode failed as expected. Error Code: %d\n", errorCode);
86+
return 0;
87+
} else {
88+
printf("FAIL: Decode succeeded unexpectedly! Buffer overflow check missing.\n");
89+
return 1;
90+
}
91+
}
92+
EOF
93+
94+
# 4. Compile
95+
echo "Step 4: Compiling test runner..."
96+
gcc -o $RUNNER_EXE $RUNNER \
97+
$OUT_DIR/a.c \
98+
$OUT_DIR/asn1crt.c \
99+
$OUT_DIR/asn1crt_encoding.c \
100+
$OUT_DIR/asn1crt_encoding_xer.c \
101+
-I$OUT_DIR
102+
103+
if [ $? -ne 0 ]; then
104+
echo "Error: Compilation failed."
105+
exit 1
106+
fi
107+
108+
# 5. Run Test
109+
echo "Step 5: Running test..."
110+
./$RUNNER_EXE
111+
RET_CODE=$?
112+
113+
if [ $RET_CODE -eq 0 ]; then
114+
echo "Test Result: PASS"
115+
else
116+
echo "Test Result: FAIL"
117+
fi
118+
119+
exit $RET_CODE

0 commit comments

Comments
 (0)