The Calysteon Corner

A Guide to the Cyber Galaxy

Home

CVE-2025-43375: A Stack Overflow in rpcgen’s get_prog_declaration()

Apple Security Advisory: Xcode 26


Introduction

This is a stack buffer overflow I found in rpcgen’s rpc_parse.c, part of Apple’s open-source developer_cmds, which ships inside Xcode’s developer tooling. The buffer at fault is char name[10], and the input that overruns it is a long identifier inside a .x file.

rpcgen ships inside Xcode’s command-line tooling through the open-source developer_cmds repository, which makes its parser easy to read. Having already looked at how it handles its command line options, I turned to the part that does the real work: the code that consumes a .x file.

rpcgen is the classic RPC protocol compiler. You hand it a .x file describing programs, versions, and procedures, and it emits the C stubs needed to marshal remote procedure calls. The problem starts when rpcgen begins parsing the procedure arguments in that file, because it trusts the identifiers you write more than it should.

The Vulnerability

The vulnerability lives in get_prog_declaration(), which is responsible for parsing a single declaration inside a program definition. Below is the relevant code from lines 484 to 507 of rpc_parse.c:

get_prog_declaration(dec, dkind, num)
	declaration *dec;
	defkind dkind;
	int     num;		/* arg number */
{
	token   tok;
	char    name[10];	/* argument name */

	if (dkind == DEF_PROGRAM) {
		peek(&tok);
		if (tok.kind == TOK_RPAREN) {	/* no arguments */
			dec->rel = REL_ALIAS;
			dec->type = "void";
			dec->prefix = NULL;
			dec->name = NULL;
			return;
		}
	}
	get_type(&dec->prefix, &dec->type, dkind);
	dec->rel = REL_ALIAS;
	if (peekscan(TOK_IDENT, &tok))	/* optional name of argument */
		strcpy(name, tok.str);
	else
		sprintf(name, "%s%d", ARGNAME, num);	/* default name of
							 * argument */

The buffer is declared at the top of the function:

	char    name[10];	/* argument name */

Ten bytes, on the stack. Further down, once the parser has scanned an optional argument identifier out of the .x file, it copies that identifier into the buffer without any bounds check:

	if (peekscan(TOK_IDENT, &tok))	/* optional name of argument */
		strcpy(name, tok.str);

tok.str is the raw identifier the parser lifted straight out of the input file. It is user-controlled and arbitrarily long. strcpy copies that string, byte by byte, into the ten-byte name array until it reaches the string’s null terminator. There is no length check, no bound, and no truncation. Any identifier longer than nine characters plus its null terminator writes past the end of name and overwrites the surrounding stack frame.

Root Cause

The path an oversized identifier takes to that strcpy is short. get_prog_declaration() is invoked while parsing the arguments of a procedure declared inside a program block. When the parser encounters an argument of the form int <identifier>, it calls get_type() to consume the int, then peekscan(TOK_IDENT, &tok) to grab the optional argument name. That optional name is <identifier>, and it lands in tok.str.

At that point, nothing stands between the identifier in the .x file and the ten-byte stack buffer except the strcpy. A short name copies fine. A name with fifty-two As in it writes well past the end of the buffer.

Reproduction

Conceptually, the trigger is a .x file that declares a program with one procedure whose argument name runs well past the nine-character limit. The int <identifier> form inside a procedure declaration is what steers the parser into get_prog_declaration(). When peekscan matches an identifier longer than nine characters as a TOK_IDENT, strcpy(name, tok.str) copies all of it into the ten-byte name array.

Because the identifier is dozens of characters long, the copy runs far past the end of name[10] and corrupts the adjacent stack memory in the get_prog_declaration() frame. This clobbers whatever the compiler laid out next to name, up to and including saved registers and the return address, which is why an overflow like this can escalate from a simple crash into a corruption primitive.