diff --git a/CHANGELOG.md b/CHANGELOG.md index cf25edad..254739af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,43 @@ from version 5.0.0 onward. Pre-fork releases (`1.x`–`4.2.0`) were authored by ## [Unreleased] +### Changed +- **BREAKING (runtime): the shipped SLF4J binding is now `slf4j-simple`, not `logback-classic`.** + Two independent reasons, and the first is a hard failure rather than a preference: + + 1. **This artifact targets Java 8 and logback no longer does.** Every logback release from 1.4.0 on + is class-file major 55 (Java 11). SLF4J's `ServiceLoader` loads `LogbackServiceProvider` at JVM + startup, so a Java 8 consumer got `UnsupportedClassVersionError` before a single line of library + code ran. Measured: all 181 classes of logback-classic 1.6.3 are major 55. + 2. **The Java 8 logback line is end-of-life with unfixed CVEs.** 1.3.16 (2025-10-29) is its last + release; CVE-2026-1225, CVE-2026-9828 and CVE-2026-10532 were fixed only in 1.5.x, and + CVE-2026-19880 only in 1.6.3 — which is Java 11 bytecode and therefore unreachable from here. + Downgrading would have traded a crash for permanent unpatchability. + + `slf4j-simple` is six classes from the same release train as `slf4j-api`, with no configuration + parser, socket server or deserialization — the subsystems essentially every logback CVE lives in. + + **What changes for you:** `logback.xml` is no longer read. Configure with a classpath + `simplelogger.properties` or `-Dorg.slf4j.simpleLogger.*`. With no configuration at all, output is + quieter than before (logback defaulted the root logger to DEBUG; slf4j-simple defaults to INFO). + To keep logback, exclude `org.slf4j:slf4j-simple` and declare your own binding — which is what the + SLF4J api/binding split is for. + + **The runnable fat jar carries logging defaults; the library jar deliberately does not.** + `simplelogger.properties` (INFO, stdout, timestamps, thread + short logger name — what the previous + logback default emitted) is added by the assembly, from `src/main/assembly-resources/`. It is *not* + under `src/main/resources`, because from there it would be published inside the library jar and land + on every consumer's classpath: slf4j-simple reads whichever file the classloader hands it first, so + a consumer with their own configuration would get a coin flip. A library must not decide that. The + `assembly` profile therefore uses its own descriptor — a verbatim copy of the predefined + `jar-with-dependencies` plus that one file. + +- **`checker-qual` pinned to 3.55.1 and marked optional.** 4.x is major 55 and its annotations are + `@Retention(RUNTIME)`, so a Java 8 JVM throws `UnsupportedClassVersionError` the moment anything + reflects over an annotated element. The optional flag keeps it out of consumers' transitive graph; + the version pin is what protects the fat jar, since `jar-with-dependencies` filters on scope only. + The build-time Checker Framework processor stays on 4.2.2 under its own property. + ### Added - **`ModelParameters.setFlashAttn(FlashAttn)` — the only way to express `--flash-attn` correctly.** llama.cpp turned that option from a bare flag into a value-taking one in **b10273**: the diff --git a/CLAUDE.md b/CLAUDE.md index c11ac495..ed50871c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1620,6 +1620,32 @@ EXPECT_FALSE(j.contains("stop_type")); // filtered out See [`../workspace/policies/javadoc-conventions.md`](../workspace/policies/javadoc-conventions.md). +## Java 8 bytecode floor — what may ship + +This artifact targets **Java 8** (`release 8`), so **every class a consumer's JVM can load must be +class-file major 52 or lower**. Two entries in `llama/pom.xml` exist only for that, and both are +easy to undo by accident: + +- **`slf4j-simple`, not logback, is the shipped SLF4J binding.** Every logback release from 1.4.0 on + is Java 11 bytecode, so `LogbackServiceProvider` cannot load on Java 8 — SLF4J's `ServiceLoader` + finds it at startup and the JVM throws `UnsupportedClassVersionError`. The Java 8 line (1.3.x) is + end-of-life (last release 1.3.16, 2025-10-29) and every logback CVE disclosed since has been fixed + only in 1.5.x/1.6.x with no backport, so it is not an option either. `slf4j-simple` is six classes + from the same release train as `slf4j-api`, with no configuration or socket layer for a CVE to + live in. Configure it with a classpath `simplelogger.properties` or `-Dorg.slf4j.simpleLogger.*`. +- **`checker.qual.version` (3.55.1) is a separate property from `checker.version` (the build-time + processor).** checker-qual 4.x is major 55, its annotations are `@Retention(RUNTIME)`, and anything + reflecting over an annotated element (Jackson does) loads them. `true` keeps + it out of consumers' transitive graph but **not** out of the fat jar — `jar-with-dependencies` + filters on scope only — so the version pin is what protects the shipped artifact. Never collapse + the two properties back into one: the processor runs on the CI JDK and must stay current. + +**Surefire excludes `org.slf4j:slf4j-simple` from the test classpath** (`classpathDependencyExcludes`). +Runtime scope is on the test classpath too, and LogCaptor (test scope) requires logback specifically — +with both providers present it fails with *"SLF4J Logger implementation should be of the type +[ch.qos.logback.classic.Logger]"*. The exclusion leaves logback the sole provider in tests and does +not touch the artifact. + ## SpotBugs Suppressions See [`../workspace/policies/spotbugs-suppressions.md`](../workspace/policies/spotbugs-suppressions.md). @@ -1828,7 +1854,7 @@ the recommended path (README "Importing in Android", Option 1): change was needed. Built by the **standalone plain-Gradle build** in `llama-android/` (see "Repository layout" for why it is not a Maven module); the POM mirrors the core's compile-scope deps (jackson/slf4j-api/jspecify/checker-qual, versions parsed from - `llama/pom.xml` — deliberately NOT logback, which is the JVM-only runtime binding). + `llama/pom.xml` — deliberately NOT the SLF4J binding, which is the JVM-only runtime dependency). - **`net.ladenthin:llama-kotlin`** — Maven reactor module; pure-Kotlin (2.4, jvmTarget 1.8) coroutines façade: `generateFlow`/`generateChatFlow` (cold `Flow`, source closed on completion/error/cancellation) and `completeSuspend`/`chatSuspend`/`chatCompleteTextSuspend`/ diff --git a/llama/pom.xml b/llama/pom.xml index 6dcad70c..6b0a4d8d 100644 --- a/llama/pom.xml +++ b/llama/pom.xml @@ -59,7 +59,14 @@ SPDX-License-Identifier: MIT 1.18.46 2.50.0 0.14.0 + 4.2.2 + + 3.55.1 2.22.2 3.8.7 2.0.18 @@ -183,10 +190,20 @@ SPDX-License-Identifier: MIT jspecify ${jspecify.version} + org.checkerframework checker-qual - ${checker.version} + ${checker.qual.version} + true com.fasterxml.jackson.core @@ -200,11 +217,34 @@ SPDX-License-Identifier: MIT ${slf4j.version} + required on the compile classpath, only loaded at JVM startup. + + slf4j-simple rather than logback, for two independent reasons: + + (1) Java 8. Every logback release from 1.4.0 on is Java 11 bytecode, so + LogbackServiceProvider cannot load on the Java 8 this artifact targets: + SLF4J's ServiceLoader finds it at startup and the JVM throws + UnsupportedClassVersionError. The Java 8 line (1.3.x) would fix that but + is end-of-life: 1.3.16 (2025-10-29) is its last release, and every logback + CVE disclosed since has been fixed only in 1.5.x/1.6.x with no backport + (CVE-2026-1225, CVE-2026-9828, CVE-2026-10532; CVE-2026-19880 is fixed only in 1.6.3, + which is Java 11 bytecode and therefore unreachable from here). + + (2) Attack surface. Essentially every logback CVE lives in its configuration + or socket layers: Janino expression evaluation, HardenedObjectInputStream, + SaxEventRecorder, SocketReceiver. slf4j-simple is six classes with no + config parser, no socket server and no deserialization, so those classes + of defect cannot exist in it. It also ships in the same release train as + slf4j-api above, so the two can never drift apart. + + What consumers lose: no logback.xml. Configure via a classpath + simplelogger.properties or -Dorg.slf4j.simpleLogger.* system properties. + Anyone who wants logback (or any other binding) excludes this one and + declares their own; that is the point of the SLF4J split. --> - ch.qos.logback - logback-classic - ${logback.version} + org.slf4j + slf4j-simple + ${slf4j.version} runtime @{argLine} -Xmx2g -XX:ErrorFile=hs_err_pid%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=. -XX:+EnableDynamicAgentLoading + + + org.slf4j:slf4j-simple + + + src/assembly/fat-jar.xml + net.ladenthin.llama.server.ServerLauncher diff --git a/llama/src/assembly/fat-jar.xml b/llama/src/assembly/fat-jar.xml new file mode 100644 index 00000000..6664c00b --- /dev/null +++ b/llama/src/assembly/fat-jar.xml @@ -0,0 +1,45 @@ + + + + + jar-with-dependencies + + jar + + false + + + / + true + true + runtime + + + + + ${project.basedir}/src/main/assembly-resources + / + + simplelogger.properties + + + + diff --git a/llama/src/main/assembly-resources/simplelogger.properties b/llama/src/main/assembly-resources/simplelogger.properties new file mode 100644 index 00000000..7b84829a --- /dev/null +++ b/llama/src/main/assembly-resources/simplelogger.properties @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2026 Bernard Ladenthin +# +# SPDX-License-Identifier: MIT + +# slf4j-simple defaults for the RUNNABLE FAT JAR only. +# +# This file is deliberately NOT under src/main/resources: that would put it into the +# published library jar, where every consumer of net.ladenthin:llama would find our +# logging configuration on their classpath. slf4j-simple reads whichever +# simplelogger.properties the classloader hands it first, so a consumer with their own +# file would get a coin flip. A library must not decide that; an application may, and +# the fat jar is the application. +# +# Every setting here is also overridable at launch with -Dorg.slf4j.simpleLogger.. + +# INFO keeps startup and per-request lines without the native layer's debug chatter. +org.slf4j.simpleLogger.defaultLogLevel=info + +# stdout, not the slf4j-simple default of stderr: the server's own output belongs on the +# same stream as everything else a user pipes or redirects. +org.slf4j.simpleLogger.logFile=System.out + +# Wall-clock timestamps -- a server log without them is hard to correlate with anything. +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss.SSS + +# Thread and short logger name, mirroring what the previous logback default emitted. +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showShortLogName=true