a2ps 4.15.7: Stack-based buffer overflow (CWE-121) in list_options() via --list-options and oversized -E style name

wheat MAX <[email protected]> Wed, 3 Dec 2025 21:37:15 +0800
Newsgroups gmane.comp.printing.a2ps.bugs
Message-ID <CAKugowCK+URpv1X1gCh5jhZ7j-wpv56qoHL4jfNsy6YSpg3A4A@mail.gmail.com>
--0000000000002bcc2706450c50b8
Content-Type: multipart/alternative; boundary="0000000000002bcc2506450c50b6"

--0000000000002bcc2506450c50b6
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

Hello Maintainer, I would like to submit a new vulnerability report for GNU
a2ps that contains a stack-based buffer overflow (CWE-121) in the
--list-options reporting path of the main a2ps binary.
This email is a correction and supplement to the report I sent yesterday,
available at:
https://lists.gnu.org/archive/html/bug-a2ps/2025-12/msg00000.html
Following the instructions in the a2ps manual quoted below, I have adjusted
the subject line of my report to match the recommended format and resent
the email.

#Summary#
In GNU a2ps 4.15.7, the function list_options() in src/main.c (or the
equivalent source file) contains a stack-based buffer overflow when the
program is invoked with --list-options and a very long argument to -E
(style selection).
The option handler stores the user-controlled style string into a global
pointer style_request. Later, list_options() unconditionally copies
style_request into a fixed-size stack buffer.
Because style_request is directly derived from the -E command-line argument
and its length is never checked, a sufficiently long -E value causes
strcpy(buf, style_request) to write past the end of the 256-byte stack
buffer buf, corrupting the stack frame. With typical builds (stack
protector enabled), this results in an immediate crash due to stack
smashing detection; in builds without stack protection it yields classic
stack corruption and potential control-flow hijacking.
This corresponds to CWE-121: Stack-Based Buffer Overflow.

#Affected Versions#
Confirmed in GNU a2ps 4.15.7 released and Git master branch.

#Root Cause#
The -E option handler stores an unbounded, user-controlled string from the
command line (optional_arg of -E) into style_request via xstrdup(), without
any length check. When --list-options is used, main() calls list_options(),
whose =E2=80=9CPretty printing=E2=80=9D section copies style_request into a=
 fixed-size
stack buffer char buf[256] using strcpy(). IS_EMPTY(style_request) only
checks for NULL or empty strings and does not constrain length. Thus, a
very long -E<arg> value leads to strcpy(buf, style_request) overflowing
buf[256], causing a stack-based buffer overflow.

#Reproduction & PoC#
First, construct a very long style name (e.g., 4,000 'A' characters) in a
shell variable:
-----
hakujyo@hakujyo-virtual-machine:~/tp/bin$ LONG_STYLE=3D$(python3 -c
'print("A"*4000)')
-----
Then run a2ps with --list-options and pass this long value to -E:
-----
hakujyo@hakujyo-virtual-machine:~/tp/bin$ ./a2ps --list-options
-E"$LONG_STYLE"
-----
The program terminates with a stack-smashing error caused by list_options()=
.
'Segmentation fault (core dumped)' caused by the stack corruption in
list_options().

#Fix#
In list_options(), avoid copying the user-controlled style_request into the
fixed-size stack buffer buf using strcpy(). Instead, select the string to
print via a pointer and pass it directly to fprintf().
This ensures that the style name-regardless of length-is no longer copied
into a 256-byte stack buffer, preventing the stack-based buffer overflow
while preserving the existing behavior of --list-options.

#Attachments#
PoC command: a2ps_stack_overflow_poc.txt
Suggested patch: a2ps_stack_overflow_fix.patch (derived from the change
above).

The issue was reproduced on an x86_64 machine running Ubuntu 22.04.5 LTS.
The package was built from source with the following configuration:
CFLAGS=3D"-O0 -g" \
CXXFLAGS=3D"-O0 -g" \
./configure \
  --prefix=3D/home/hakujyo/tp \
  --sysconfdir=3D/home/hakujyo/tp/etc/a2ps \
  --disable-shared \
  --enable-static

Thanks.

Best regards,
[email protected]

--0000000000002bcc2506450c50b6
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr">Hello Maintainer, I would like to submit a new vulnerabili=
ty report for GNU a2ps that contains a stack-based buffer overflow (CWE-121=
) in the --list-options reporting path of the main a2ps binary.<div>This em=
ail is a correction and supplement to the report I sent yesterday, availabl=
e at: <a href=3D"https://lists.gnu.org/archive/html/bug-a2ps/2025-12/msg000=
00.html">https://lists.gnu.org/archive/html/bug-a2ps/2025-12/msg00000.html<=
/a><br><div>Following the instructions in the a2ps manual quoted below, I h=
ave adjusted the subject line of my report to match the recommended format =
and resent the email.<br><br>#Summary#<br>In GNU a2ps 4.15.7, the function =
list_options() in src/main.c (or the equivalent source file) contains a sta=
ck-based buffer overflow when the program is invoked with --list-options an=
d a very long argument to -E (style selection).<br>The option handler store=
s the user-controlled style string into a global pointer style_request. Lat=
er, list_options() unconditionally copies style_request into a fixed-size s=
tack buffer.<br>Because style_request is directly derived from the -E comma=
nd-line argument and its length is never checked, a sufficiently long -E va=
lue causes strcpy(buf, style_request) to write past the end of the 256-byte=
 stack buffer buf, corrupting the stack frame. With typical builds (stack p=
rotector enabled), this results in an immediate crash due to stack smashing=
 detection; in builds without stack protection it yields classic stack corr=
uption and potential control-flow hijacking.<br>This corresponds to CWE-121=
: Stack-Based Buffer Overflow.<br><br>#Affected Versions#<br>Confirmed in G=
NU a2ps 4.15.7 released and Git master branch.<br><br>#Root Cause#<br>The -=
E option handler stores an unbounded, user-controlled string from the comma=
nd line (optional_arg of -E) into style_request via xstrdup(), without any =
length check. When --list-options is used, main() calls list_options(), who=
se =E2=80=9CPretty printing=E2=80=9D section copies style_request into a fi=
xed-size stack buffer char buf[256] using strcpy(). IS_EMPTY(style_request)=
 only checks for NULL or empty strings and does not constrain length. Thus,=
 a very long -E&lt;arg&gt; value leads to strcpy(buf, style_request) overfl=
owing buf[256], causing a stack-based buffer overflow.<br><br>#Reproduction=
 &amp; PoC#<br>First, construct a very long style name (e.g., 4,000 &#39;A&=
#39; characters) in a shell variable:<br>-----<br>hakujyo@hakujyo-virtual-m=
achine:~/tp/bin$ LONG_STYLE=3D$(python3 -c &#39;print(&quot;A&quot;*4000)&#=
39;)<br>-----<br>Then run a2ps with --list-options and pass this long value=
 to -E:<br>-----<br>hakujyo@hakujyo-virtual-machine:~/tp/bin$ ./a2ps --list=
-options -E&quot;$LONG_STYLE&quot;<br>-----<br>The program terminates with =
a stack-smashing error caused by list_options().<br>&#39;Segmentation fault=
 (core dumped)&#39; caused by the stack corruption in list_options().<br><b=
r>#Fix#<br>In list_options(), avoid copying the user-controlled style_reque=
st into the fixed-size stack buffer buf using strcpy(). Instead, select the=
 string to print via a pointer and pass it directly to fprintf().<br>This e=
nsures that the style name-regardless of length-is no longer copied into a =
256-byte stack buffer, preventing the stack-based buffer overflow while pre=
serving the existing behavior of --list-options.<br><br>#Attachments#<br>Po=
C command: a2ps_stack_overflow_poc.txt<br>Suggested patch: a2ps_stack_overf=
low_fix.patch (derived from the change above).<br><br><div>The issue was re=
produced on an x86_64 machine running Ubuntu 22.04.5 LTS.=C2=A0</div><div>T=
he package was built from source with the following configuration:=C2=A0</d=
iv><div>CFLAGS=3D&quot;-O0 -g&quot; \<br>CXXFLAGS=3D&quot;-O0 -g&quot; \<br=
>./configure \<br>=C2=A0 --prefix=3D/home/hakujyo/tp \<br>=C2=A0 --sysconfd=
ir=3D/home/hakujyo/tp/etc/a2ps \<br>=C2=A0 --disable-shared \<br>=C2=A0 --e=
nable-static<br><br>Thanks.<br><br>Best regards,<br><a href=3D"mailto:maxto=
[email protected]" target=3D"_blank">[email protected]</a></div></div></di=
v></div>

--0000000000002bcc2506450c50b6--

--0000000000002bcc2706450c50b8
Content-Type: text/plain; charset="US-ASCII"; name="a2ps_stack_overflow_poc.txt"
Content-Disposition: attachment; filename="a2ps_stack_overflow_poc.txt"
Content-Transfer-Encoding: base64
Content-ID: <f_miq1wxsb0>
X-Attachment-Id: f_miq1wxsb0

aGFrdWp5b0BoYWt1anlvLXZpcnR1YWwtbWFjaGluZTp+L3RwL2JpbiQgTE9OR19TVFlMRT0kKHB5
dGhvbjMgLWMgJ3ByaW50KCJBIio0MDAwKScpDQpoYWt1anlvQGhha3VqeW8tdmlydHVhbC1tYWNo
aW5lOn4vdHAvYmluJCAuL2EycHMgLS1saXN0LW9wdGlvbnMgLUUiJExPTkdfU1RZTEUiDQogICAg
ICAgICAgICAgICAgICAgICAgQ29uZmlndXJhdGlvbiBzdGF0dXMgb2YgYTJwcyA0LjE1LjcNCiAg
ICAgICAgICAgICAgICAgICAgICA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0K
DQpTaGVldHM6DQotLS0tLS0tDQogIG1lZGl1bSAgICAgICAgICA9IGE0LCBsYW5kc2NhcGUNCiAg
cGFnZSBsYXlvdXQgICAgID0gMiB4IDEsIHJvd3MgZmlyc3QNCiAgYm9yZGVycyAgICAgICAgID0g
eWVzDQogIGZpbGUgYWxpZ25tZW50ICA9IHBhZ2UNCiAgaW50ZXJpb3IgbWFyZ2luID0gMA0KDQpW
aXJ0dWFsIHBhZ2VzOg0KLS0tLS0tLS0tLS0tLS0NCiAgbnVtYmVyIGxpbmVzICAgICAgICAgPSBu
bw0KICBmb3JtYXQgICAgICAgICAgICAgICA9IDgwIGNoYXJhY3RlcnMgcGVyIGxpbmUNCiAgdGFi
dWxhdGlvbiBzaXplICAgICAgPSA4DQogIG5vbiBwcmludGFibGUgZm9ybWF0ID0gY2FyZXQgKGku
ZS4sIGBeQycsIGBNLV5DJyBldGMuKQ0KDQpIZWFkZXJzOg0KLS0tLS0tLS0NCiAgaGVhZGVyICAg
ICAgID0gJWENCiAgbGVmdCBmb290ZXIgID0gIz9sISVFISM/dnwlRXwlcy4vJXMjfCENCiAgZm9v
dGVyICAgICAgID0gIz9sfCMhcy0kZi0sIC18fA0KICByaWdodCBmb290ZXIgPSAjP2whJXMuLyVz
IyEjP3Z8JXMuLyVzI3wlRXwhDQogIGxlZnQgdGl0bGUgICA9ICM/Mnx8JGUgJFR8DQogIGNlbnRl
ciB0aXRsZSA9ICM/MXwkdDF8JG58DQogIHJpZ2h0IHRpdGxlICA9ICM/MnwkdDJ8JFF8DQogIHVu
ZGVyIGxheSAgICA9IA0KDQpJbnB1dDoNCi0tLS0tLQ0KICB0cnVuY2F0ZSBsaW5lcyA9IG5vDQog
IGludGVycHJldCAgICAgID0geWVzDQogIGVuZCBvZiBsaW5lICAgID0gYW55IHR5cGUNCiAgZW5j
b2RpbmcgICAgICAgPSBJU08tODg1OS0xDQogIGRvY3VtZW50IHRpdGxlID0gIzEwIWZ8JG58LCB8
DQogIHByb2xvZ3VlICAgICAgID0gYncNCiAgcHJpbnQgYW55d2F5ICAgPSBubw0KICBkZWxlZ2F0
aW5nICAgICA9IHllcw0KDQpTZWdtZW50YXRpb24gZmF1bHQgKGNvcmUgZHVtcGVkKQ0K
--0000000000002bcc2706450c50b8
Content-Type: application/octet-stream; name="a2ps_stack_overflow_fix.patch"
Content-Disposition: attachment; filename="a2ps_stack_overflow_fix.patch"
Content-Transfer-Encoding: base64
Content-ID: <f_miq1wzvm1>
X-Attachment-Id: f_miq1wzvm1

ZGlmZiAtLWdpdCBhLy4vc3JjL21haW4uYyBiLy4vc3JjL21haW5fcGF0Y2guYwppbmRleCBlY2Nk
MmZkLi4yYjVjZWVjIDEwMDY0NAotLS0gYS8uL3NyYy9tYWluLmMKKysrIGIvLi9zcmMvbWFpbl9w
YXRjaC5jCkBAIC0zMjEsNiArMzIxLDcgQEAgbGlzdF9vcHRpb25zIChzdHJ1Y3QgYTJwc19qb2Ig
KmFfam9iLCBGSUxFICpzdHJlYW0pCiAjZGVmaW5lIGJvb2xfdG9fc3RyaW5nKGJvb2wpICgoYm9v
bCkgPyBfKCJ5ZXMiKSA6IF8oIm5vIikpCiAgIGNoYXIgYnVmWzI1Nl0sIGJ1ZjJbMjU2XTsKICAg
Y29uc3QgY2hhciAqY3AgPSBOVUxMLCAqY3AyID0gTlVMTDsKKyAgY29uc3QgY2hhciAqc3R5bGVf
bmFtZSA9IE5VTEw7CiAKICAgLyogVGl0bGUgb2YgLS1saXN0LW9wdGlvbnMgKCVzJXMgaXMgYGEy
cHMnIGB2ZXJzaW9uJyAqLwogICB0aXRsZSAoc3RyZWFtLCAnPScsIHRydWUsCkBAIC00MjEsMTkg
KzQyMiwyMyBAQCBsaXN0X29wdGlvbnMgKHN0cnVjdCBhMnBzX2pvYiAqYV9qb2IsIEZJTEUgKnN0
cmVhbSkKIAogICAvKgogICAgKiBQcmV0dHkgcHJpbnRpbmcKKyAgICoKKyAgICogTm8gc3RyY3B5
CisgICAqCiAgICAqLwogICBpZiAoSVNfRU1QVFkgKHN0eWxlX3JlcXVlc3QpKQogICAgIC8qIFRS
QU5TOiBhMnBzIC1FIC0tbGlzdD1vcHRpb25zLiAgV2FybmluZywgdGhpcyBhbnN3ZXIgaXMgYWxz
bwogICAgICAgIHVzZWQgZm9yIHRoZSBQUEQgZmlsZS4gIE1ha2UgaXQgY29tcGF0aWJsZSB3aXRo
IGJvdGguICAqLwotICAgIHN0cmNweSAoYnVmLCBfKCJzZWxlY3RlZCBhdXRvbWF0aWNhbGx5Iikp
OworICAgIHN0eWxlX25hbWUgPSBfKCJzZWxlY3RlZCBhdXRvbWF0aWNhbGx5Iik7CiAgIGVsc2UK
LSAgICBzdHJjcHkgKGJ1Ziwgc3R5bGVfcmVxdWVzdCk7CisgICAgc3R5bGVfbmFtZSA9IHN0eWxl
X3JlcXVlc3Q7CisKICAgdGl0bGUgKHN0cmVhbSwgJy0nLCBmYWxzZSwgXygiUHJldHR5LXByaW50
aW5nOlxuIikpOwogICBmcHJpbnRmIChzdHJlYW0sIF8oIlwKICAgc3R5bGUgc2hlZXQgICAgID0g
JXNcblwKICAgaGlnaGxpZ2h0IGxldmVsID0gJXNcblwKICAgc3RyaXAgbGV2ZWwgICAgID0gJWRc
biIpLAotCSAgIGJ1ZiwKKwkgICBzdHlsZV9uYW1lLAogCSAgIGhpZ2hsaWdodF9sZXZlbF90b19z
dHJpbmcgKGhpZ2hsaWdodF9sZXZlbCksCiAJICAgc3RyaXBfbGV2ZWwpOwogICBwdXRjICgnXG4n
LCBzdHJlYW0pOwo=
--0000000000002bcc2706450c50b8--