Java vulnerabilities of the week: 22 to 28 October 2026

Most security news reaches me as a CVE number with a score next to it, and from that I learn nothing I can use on Monday. What I can use is the mechanism: the value that was trusted, the layer that read the bytes differently, the default that switched a check off. That is what I go and look for in my own code, so once a week I take the Java side of the news apart. The longer argument is in the CVE stigma post.

This is the seven days to 28 October 2026. The window overlaps last Sunday’s post by four days, so I leave out what that one already covered (the HAPI FHIR, Abdera, Camel and Vysper items from 22 and 23 October), and the two items that start before the window are dated where they come up.

The theme picked itself: who the JVM thinks it is talking to. The JDK trust store lost four root certificates because eight eMudhra intermediate keys could be computed by anyone. netty-tcnative handed Java an SNI hostname with a NUL character inside that the wire never carried. Micronaut’s HTTP/3 server could attach another client’s certificate to a request, and a Kafka broker could accept a JWT without checking its signature. The two I take apart are the trust store, a process story, and the netty-tcnative encoding bug, a code one.

The week in five

A few smaller ones. jackson-databind has a critical PolymorphicTypeValidator bypass, CVE-2026-110879 (9.8): the validator checks the outer class of a class-name type id and lets attacker-chosen classes through as generic type arguments or array element types. The advisory went up on GitHub on 20 October, two days before this window, and the CVE record on 28 October; fixed in 2.18.12, 2.21.8, 2.22.4, 3.1.8 and 3.2.4. Apache SIS 1.7 fixes two Low issues published on 26 October, path traversal through links between data files and an unquoted ORDER BY clause in the SQL feature store (CVE-2026-110748, CVE-2026-110733). And com.vaadin.external.google:android-json overflows the stack on deeply nested input and will not be fixed (CVE-2026-110974, 22 October), so the advice is to replace it.

The JDK trust store: a key is only as strong as the range it was drawn from

When a Java TLS client connects, the default trust manager builds a path from the server’s certificate to one of the roots in the JVM’s trust store, lib/security/cacerts unless something says otherwise, and if a path exists the server is trusted. Revocation is not part of that by default: PKIXValidator calls setRevocationEnabled(checkTLSRevocation) for TLS, and that flag is the system property com.sun.net.ssl.checkRevocation, false unless you set it.

On 17 October a community member, credited as Wayne, reported to the root programs that the private keys of eight eMudhra intermediate CAs had been recovered, six for TLS and two for code signing. Mozilla’s analysis in the incident bug explains the mechanism. These were P-384 keys, and a P-384 private key is supposed to be an integer chosen at random between 1 and the group order, a number of about 384 bits. All eight were integers below 256, every byte zero except the last. The public key is the private key times the curve’s generator point, so you compute G, 2G, 3G and so on, and among at most 255 candidates one matches the public key in the certificate. Mozilla recovered the keys independently within two hours of the report and noted the flaw was in the key generation, not in the signatures. eMudhra’s preliminary explanation is a script flaw in its April 2025 key ceremony that created a second, weaker key pair, used by mistake for the certificate requests; the full root cause report is pending. eMudhra revoked the intermediates on 18 October and Sectigo, which had cross-signed them under AAA Certificate Services, on 19 October.

Browsers were covered within hours by pushed blocklists, OneCRL and CRLSets. A JVM has no pushed list and does not check revocation by default, so the revocations did not reach a default Java client. Mozilla’s notice to trust store maintainers set out the options: block the intermediates if you have a mechanism for it, otherwise evaluate removing the roots, at the cost of some servers becoming unreachable. The JDK did both, in that order: change 8395142 put the 16 certificates, each intermediate as issued by eMudhra and as cross-signed by Sectigo, on blocked.certs, which UntrustedChecker consults during path validation whatever the revocation setting, and 8395690 removed the four roots three days later. Oracle published each step as a Security Alert with its own CVE, and thats how a trust store edit shows up in a scanner or an SBOM report.

Why I find it interesting

The class is weak key generation, insufficient entropy in the CWE-331 family, and what makes it a teaching case is how little there is to see. Every signature verified, the curve and the algorithm were the standard ones, the certificates were well formed. A P-384 key whose private scalar is below 256 has the same type, curve and encoding as a strong one, so no structural check catches it; you find it only by trying to recover the key, which is what the reporter’s scan of the CA database did. When you create keys in Java, let KeyPairGenerator draw them from a SecureRandom, and be wary of any path that builds a private key from a number it was handed, an ECPrivateKeySpec or an unwrap, because on that path the range is whatever the caller supplied.

The second lesson is where Java’s trust lives. It is a file, and it changes only when you change it: a JDK update, the OS package that generates the store for distribution JDKs (Debian’s /etc/ssl/certs/java/cacerts, Fedora and RHEL’s /etc/pki/java/cacerts), or an edit to a custom store passed with javax.net.ssl.trustStore. Container images freeze whichever one they were built with. The check takes a minute, keytool -list -cacerts | grep -iE 'emsign|aaa' on the JVM that actually runs your service, and an empty result is what you want. Expect PKIX path building failed from a few hosts afterwards: on oss-security Stuart Henderson pointed out that the InCommon intermediates also chain to emSign ECC Root CA - G3.

One detail from the blocklist commit I liked. blocked.certs matches by fingerprint, and an ECDSA signature (r, s) has a twin (r, n - s) that also verifies, so the build tool fingerprints the twin too. The commit makes it decide that from the signature algorithm, which belongs to the issuer, rather then from the certificate’s own key, which matters here because eight new entries are P-384 certificates signed by an RSA root.

netty-tcnative: a check on bytes does not survive a change of encoding

One of the values netty-tcnative passes up from OpenSSL or BoringSSL to Java is the SNI hostname, the server_name a client puts in its ClientHello so a server with several names can pick a certificate or a virtual host. On the native side SSL_get_servername() returns that name as a C string, and the one guarantee it carries, as the advisory puts it, is that the library rejects a raw 0x00 byte inside it. The other bytes are not validated, RFC 6066 says ASCII but the library does not enforce it.

netty-tcnative turned that C string into a Java String with JNI’s NewStringUTF() in two places, the servername callback that asks the application’s SniHostNameMatcher.match(long, String) to accept or reject the name, and the public SSL.getSniHostname(). NewStringUTF() does not decode UTF-8. It decodes modified UTF-8, the variant Java uses in JNI and in class files, where the character U+0000 is written as the two bytes 0xC0 0x80 precisely so that an encoded string never contains a zero byte. In standard UTF-8 those two bytes are an overlong encoding and invalid. So a ClientHello with 0xC0 0x80 in the hostname passes OpenSSL’s check, because there is no zero byte, and arrives in Java as a String with a real NUL inside. Call getBytes(StandardCharsets.UTF_8) on it later, for a log line, a file name or another native call, and the zero byte is back, the classic setup for truncation in anything that reads bytes as a C string. That is CVE-2026-116542, Medium, 6.5 in the project’s advisory, which notes that the real impact depends on what the application does with the name.

The fix, commit a879c85, replaces both calls with tcn_new_stringn(e, servername, strlen(servername)), which copies the bytes into a byte[] and calls new String(byte[]). A standard decoder produces U+0000 only from a zero byte, and OpenSSL already excluded those, so the guarantee now holds on both sides of the boundary.

Why I find it interesting

The family is encoding confusion across a boundary, CWE-172 and CWE-838 in the advisory’s terms, and its the same shape as a path normalization differential or HTTP request smuggling: two layers read the same bytes differently, and a check made by one of them does not hold for the other. Here the check in C was correct, and the property it established was lost one function call later, in the conversion to Java.

The Java specific part is that modified UTF-8 is all over the platform. JNI’s NewStringUTF and GetStringUTFChars use it, and so do DataOutputStream.writeUTF and DataInputStream.readUTF, which also decode 0xC0 0x80 to U+0000. Those are fine for strings your own code wrote. For bytes from a socket, a file or a peer, build the string from a byte[] with an explicit charset and validate after decoding, not before, because the validation has to look at the value your code will actually use. If you maintain JNI code, a grep for NewStringUTF with an argument that came from outside the process is a ten minute review.

What I take away from this week

First, where the fixes reach people, which differs item by item. The trust store changes ship with the JDK: a JDK update from your vendor (Oracle through its two alerts, other vendors from the OpenJDK update repositories), the OS CA package for distribution JDKs, or a manual edit for a custom trust store, and a container image rebuild in every case. Kafka is mostly a broker upgrade. The BOM bumps in Camel, Spring Boot and Quarkus move the client libraries, which covers the deserializer issue, but most of the rest is on the broker, and on 4.4.0 the broker will not start until issuer and audience are configured. For netty-tcnative the lever is the Netty BOM, so check which one your framework imports. Micronaut users bump the framework, and Jenkins is a controller upgrade. The fixed Kafka, netty-tcnative, Micronaut, Jackson and SIS versions are all on Maven Central and the Jenkins ones in the Jenkins repository; I checked.

Second, the theme from the top. In each of those four cases the identity decision rested on something that did not hold: a key that was not secret, a hostname that changed between C and Java, a TLS session from another connection, a token accepted without a signature check. When you review code that decides who is on the other end, ask where the value came from and what ties it to this connection, this key and this request.

Third, a quieter cluster: a size the sender declared, used as an allocation size. Kafka’s protocol readers, record decoder and Streams deserializers, and Micronaut’s SSE client, all trusted a count or a length that cost the sender a few bytes. The fixes look the same everywhere: cap the preallocation and grow on demand, check the declared size against the bytes actually present, and keep a cumulative budget for anything that accumulates.

One last observation, as last week. The Jenkins advisory credits the Anthropic OSS program on 13 of its 15 issues, three of them alongside researchers who found them independently, and Kafka’s CVE-2026-112375 credits Ada Logics with Anthropic Research and, separately, OpenAI Codex Security. I note it as the factual texture of the week, as in is there really a human in the loop; whoever finds the bug, the mechanism is still the part you act on.

References