Skip to content

Fix MarkingFunctionsFactory visibility race on static markingFunctions#3625

Open
t-h-i-n-k-er wants to merge 4 commits into
NationalSecurityAgency:integrationfrom
t-h-i-n-k-er:pr1-marking-functions-factory-fix
Open

Fix MarkingFunctionsFactory visibility race on static markingFunctions#3625
t-h-i-n-k-er wants to merge 4 commits into
NationalSecurityAgency:integrationfrom
t-h-i-n-k-er:pr1-marking-functions-factory-fix

Conversation

@t-h-i-n-k-er

Copy link
Copy Markdown

Summary:

MarkingFunctionsFactory.createMarkingFunctions() is synchronized and reads the static markingFunctions field under the class lock, but postContruct() writes that same static field without synchronization and without a volatile declaration on the field. Under the Java Memory Model there is no happens-before edge from the write in postContruct() to the read in createMarkingFunctions(), so a concurrent reader can observe a stale null and fall through into the Spring-context-load branch instead of returning the CDI-provided MarkingFunctions instance.

What does this PR do?

Two commits, in the order requested on #3602 — a test that demonstrates the failure condition, and then the commit to fix it:

1: Add failing test demonstrating MarkingFunctionsFactory visibility race — adds MarkingFunctionsFactoryVisibilityTest which fails on the current code:

  • markingFunctionsFieldIsVolatile | asserts the static markingFunctions field is declared volatile. Fails because the field is currently declared without volatile.

  • postContructIsSynchronized | asserts postContruct() is declared synchronized to match createMarkingFunctions(). Fails because postContruct() is currently declared without synchronized.

  • Both tests are structural reflection-based assertions that fail deterministically on the unfixed code and pass after the fix. Verified locally: 2 tests fail on unfixed code, 2 tests pass on fixed code.

2: Fix MarkingFunctionsFactory visibility race on static markingFunctions — declares the static markingFunctions field as volatile and marks postContruct() as synchronized so its write has a happens-before edge with respect to createMarkingFunctions(), which already synchronizes on the class monitor. All tests now pass.

This PR is intentionally narrow — only MarkingFunctionsFactory.java and one new test file. Split out from #3602 per the maintainer's request to keep changes focused. Refs #3601.

Why not just volatile:

volatile alone would make the write visible, but adding synchronized to postContruct() also matches the locking discipline already used on createMarkingFunctions() and prevents the write from racing with the read-check-write sequence inside createMarkingFunctions() (which could otherwise observe a non-null value, skip the Spring load, and then have postContruct() overwrite it with the CDI-provided instance — or vice versa). Using both is the minimal change that makes the contract internally consistent.

t-h-i-n-k-er and others added 4 commits June 21, 2026 22:56
MarkingFunctionsFactory.createMarkingFunctions() is synchronized and
reads the static markingFunctions field under the class lock, but
postContruct() writes that same static field without synchronization
and without a volatile declaration on the field. Under the Java Memory
Model there is no happens-before edge from the write in postContruct()
to the read in createMarkingFunctions(), so a reader can observe a
stale null and fall through into the Spring-context-load branch
instead of returning the CDI-provided MarkingFunctions instance.

This commit adds MarkingFunctionsFactoryVisibilityTest which fails on
the current code:

  - markingFunctionsFieldIsVolatile: asserts the static
    markingFunctions field is declared volatile. Fails because the
    field is currently declared without volatile.

  - postContructIsSynchronized: asserts postContruct() is declared
    synchronized to match createMarkingFunctions(). Fails because
    postContruct() is currently declared without synchronized.

Both tests are structural reflection-based assertions that fail
deterministically on the unfixed code and pass after the fix.

Refs NationalSecurityAgency#3601
Declare the static markingFunctions field as volatile and mark
postContruct() as synchronized so its write has a happens-before edge
with respect to createMarkingFunctions(), which already synchronizes
on the class monitor.

Without these changes a MarkingFunctions reference published by
postContruct() during CDI initialization is not guaranteed to be
visible to a concurrent caller of createMarkingFunctions(); the reader
can observe a stale null and fall through into the Spring-context-load
branch, overwriting the CDI-provided instance with a freshly loaded
one or returning null when no Spring context is available.

The test added in the previous commit now passes:

  - markingFunctionsFieldIsVolatile: the field is now declared volatile.
  - postContructIsSynchronized: postContruct() is now declared synchronized.

Refs NationalSecurityAgency#3601
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.

1 participant