Skip to content

Security: fix Javadoc injection in li-avro-codegen and class instantiation bypass in fast-serde - #604

Merged
leiykpt merged 3 commits into
linkedin:masterfrom
SrilakshmiBharadwaj:fix/cve-security-patches
Jul 14, 2026
Merged

Security: fix Javadoc injection in li-avro-codegen and class instantiation bypass in fast-serde#604
leiykpt merged 3 commits into
linkedin:masterfrom
SrilakshmiBharadwaj:fix/cve-security-patches

Conversation

@SrilakshmiBharadwaj

@SrilakshmiBharadwaj SrilakshmiBharadwaj commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR is a prerequisite for LinkedIn's Avro 1.10.2 CVE fork patch work. Before rolling out the patched Avro fork (org.apache.avro:avro:1.10.2-li-N) to ~600 internal multiproducts, these two gaps in avro-util must be closed — otherwise the core CVE fixes in Avro are incomplete: fast-serde and li-avro-codegen carry their own vulnerable logic that bypasses the protections added to Avro core.

Closes two security gaps in avro-util that bypass CVE fixes shipped in Apache Avro core:

1. li-avro-codegen — Javadoc injection (CVE-2024-47561 analog)

File: avro-codegen/src/main/java/com/linkedin/avroutil1/codegen/SpecificRecordClassGenerator.java

Avro's SpecificCompiler (Velocity-based) escapes schema doc strings before embedding them in generated code to fix CVE-2024-47561. li-avro-codegen uses JavaPoet instead of Velocity but had the same gap: schema doc strings were added to Javadoc blocks without sanitization.

Attack vector: A malicious schema with "doc": "something */ public class Evil { static { Runtime.exec(...); } } //" would close the Javadoc block comment early and inject arbitrary Java code into the generated source.

Fix: Add sanitizeDocForJavadoc() that applies two escaping steps:

  • $$$ — prevents JavaPoet from treating the string as a format specifier
  • */* / — prevents premature Javadoc block comment termination

Applied uniformly to enum, fixed, record, and field doc strings. Previously enum and fixed had zero escaping; record and field only escaped $.


2. fast-serde — Class instantiation bypass (CVE-2023-39410 analog)

Files: fastserde/avro-fastserde/src/main/java/com/linkedin/avro/fastserde/SchemaAssistant.java, FastSerdeBase.java

Avro core fixed CVE-2023-39410 by adding a SERIALIZABLE_CLASSES allowlist to SpecificData.newInstance(). fast-serde bypasses this entirely: it generates Janino/javac source code that directly instantiates classes derived from schema fullName fields, never going through the allowlist check.

Attack vector: An attacker-controlled schema with "name": "com.malicious.RCEClass" causes fast-serde to generate code like new com.malicious.RCEClass(). If that class is on the classpath, it gets instantiated at deserialization time — no allowlist check applied.

Fix: Add validatedSpecificClassRef() in SchemaAssistant that loads the class referenced by getSchemaFullName(schema) and asserts it implements the expected Avro interface (SpecificRecord for RECORD, Enum for ENUM, SpecificFixed for FIXED) before emitting a codeModel.ref(). If the class is not found on the classloader (schema-evolution scenario), the check is skipped to preserve existing behaviour.

Thread classLoader from FastSerdeBase into SchemaAssistant; backward-compat no-arg constructor preserved.


Testing

  • ./gradlew :fastserde:avro-fastserde-tests111:test :avro-codegen:test — all tests pass
  • Both modules compile cleanly with no new warnings

Related

  • CVE-2023-39410 (Avro core fix: SERIALIZABLE_CLASSES in SpecificData)
  • CVE-2024-47561 (Avro core fix: escapeForJavadoc in SpecificCompiler)
  • LinkedIn Avro fork security project (Avro 1.10.2 CVE backports)

SrilakshmiBharadwaj and others added 3 commits February 28, 2025 14:14
…9410 bypass in fast-serde

li-avro-codegen (SpecificRecordClassGenerator):
- Add sanitizeDocForJavadoc() that escapes both JavaPoet format specifiers ($->$$)
  and Javadoc block-comment terminators (*/->'* /') before embedding schema doc strings
  in generated Javadoc. The '*/'-escaping closes the same injection vector that
  CVE-2024-47561 exploited in Avro's Velocity-based SpecificCompiler.
- Apply the new sanitizer to enum, fixed, record, and field doc strings uniformly.
  Previously enum and fixed docs had no escaping at all; record and field docs only
  escaped '$' but not '*/'.

fast-serde (SchemaAssistant):
- Add validatedSpecificClassRef() which, before emitting a codeModel.ref() from a
  schema full name, loads the class and asserts it implements the expected Avro
  interface (SpecificRecord for RECORD, Enum for ENUM, SpecificFixed for FIXED).
  This prevents an attacker-controlled schema from causing fast-serde to generate
  code that instantiates an arbitrary class on the classpath -- the path that
  bypassed the SERIALIZABLE_CLASSES allowlist added to Avro core for CVE-2023-39410.
- If the class is not found on the classloader (schema-evolution / compile-time
  absence), the check is skipped to preserve existing behaviour.
- Thread classLoader from FastSerdeBase into SchemaAssistant; add backward-compat
  no-classLoader constructor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SrilakshmiBharadwaj
SrilakshmiBharadwaj marked this pull request as ready for review July 14, 2026 17:43

@leiykpt leiykpt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@leiykpt
leiykpt merged commit 3ed1779 into linkedin:master Jul 14, 2026
2 checks passed
SrilakshmiBharadwaj added a commit to SrilakshmiBharadwaj/avro-util that referenced this pull request Aug 26, 2026
… value-generation helpers

validatedSpecificClassRef() (added in linkedin#604) is correctly used when
declaring a field's *type* (schemaToJavaType() for RECORD/ENUM/FIXED),
but three sibling methods that generate the actual instantiation/
invocation code independently re-derived the class name via
codeModel.ref(AvroCompatibilityHelper.getSchemaFullName(...)),
bypassing the validation entirely:

- getEnumValueByName(): generated '<SchemaClass>.valueOf(name)'
- getEnumValueByIndex(): generated 'Enums.getConstant(<SchemaClass>.class, index)'
- getFixedValue(): generated 'new <SchemaClass>(bytes)' -- a direct,
  unvalidated constructor invocation

getEnumValueByIndex/getEnumValueByName are on the live enum-field
deserialization path in FastDeserializerGenerator. While today's call
sites happen to validate the same schema earlier in the same
generation pass via schemaToJavaType(), these three methods are public
API on SchemaAssistant and had no independent guard, making the
bypass trivially reachable by any future caller (including outside
FastDeserializerGenerator) that doesn't validate first.

Fixed by routing all three through validatedSpecificClassRef(), same
as the type-declaration call sites. Verified with a standalone runtime
harness: hostile enum/fixed schemas resolving to real classes that do
NOT implement Enum/SpecificFixed are now blocked with
SchemaAssistantException, while legitimate schemas (e.g.
java.time.DayOfWeek as an Enum) are still allowed through unchanged.
SrilakshmiBharadwaj added a commit that referenced this pull request Aug 26, 2026
…or methods (#606)

* security: fix incomplete Javadoc injection escaping in Builder accessor methods

PR #604 (commit 3ed1779) introduced sanitizeDocForJavadoc() to escape
'$' and '*/' in doc strings before embedding them in generated Javadoc,
and applied it to class/enum/fixed-level doc and field declaration doc.

However getFieldJavaDoc() - used by the generated Builder inner class's
get/set/has/clear accessor method Javadoc - still called the older
replaceSingleDollarSignWithDouble(), which does not escape '*/'. A
schema with a hostile doc string containing '*/' can therefore still
break out of the generated Builder accessor Javadoc comment blocks,
reproducing the original CVE-2024-47561-style injection in 4 additional
locations per field.

Verified against a hostile-doc .avsc fixture: pre-fix, javac fails to
compile the generated class (broken comment blocks); post-fix, the
Builder accessor Javadoc is properly escaped and the generated class
compiles cleanly.

* security: close remaining CVE-2023-39410-style bypasses in fast-serde value-generation helpers

validatedSpecificClassRef() (added in #604) is correctly used when
declaring a field's *type* (schemaToJavaType() for RECORD/ENUM/FIXED),
but three sibling methods that generate the actual instantiation/
invocation code independently re-derived the class name via
codeModel.ref(AvroCompatibilityHelper.getSchemaFullName(...)),
bypassing the validation entirely:

- getEnumValueByName(): generated '<SchemaClass>.valueOf(name)'
- getEnumValueByIndex(): generated 'Enums.getConstant(<SchemaClass>.class, index)'
- getFixedValue(): generated 'new <SchemaClass>(bytes)' -- a direct,
  unvalidated constructor invocation

getEnumValueByIndex/getEnumValueByName are on the live enum-field
deserialization path in FastDeserializerGenerator. While today's call
sites happen to validate the same schema earlier in the same
generation pass via schemaToJavaType(), these three methods are public
API on SchemaAssistant and had no independent guard, making the
bypass trivially reachable by any future caller (including outside
FastDeserializerGenerator) that doesn't validate first.

Fixed by routing all three through validatedSpecificClassRef(), same
as the type-declaration call sites. Verified with a standalone runtime
harness: hostile enum/fixed schemas resolving to real classes that do
NOT implement Enum/SpecificFixed are now blocked with
SchemaAssistantException, while legitimate schemas (e.g.
java.time.DayOfWeek as an Enum) are still allowed through unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants