Hello core-libs-dev,

While packaging an app with jpackage on Linux, I encountered a
reproducible SIGSEGV when the input directory contains a large number of
JAR files (e.g. 1000 or more). A reproducer Makefile is included below.
If the Makefile does not reproduce the failure on a particular system,
increasing `JAR_COUNT` may trigger it.

After investigation, it appears the launcher assumes that `read()` and
`write()` on the pipe between parent and child transfer the full
requested size in a single call. However, when the serialized
`JvmlLauncherData` buffer becomes large (about 100KB in my test), the
parent may receive only a partial read.

For example (strace excerpt):
- Child write: returns 16384 when 102028 was requested.
- Parent read: returns 8192 when 102028 was requested.
```
06:42:45.145883 write(4,
"h\212\352\252#V\0\0\6\0\0\0\2\0\0\0\310\212\352\252#V\0\08\30\354\252#V\0\0"...,
102028) = 16384
06:42:45.146017 read(3,
"h\212\352\252#V\0\0\6\0\0\0\2\0\0\0\310\212\352\252#V\0\08\30\354\252#V\0\0"...,
102028) = 8192
```

Neither the parent nor the child loops to transfer the remaining bytes.
This results in a truncated configuration buffer, and the launcher
subsequently crashes in `jvmLauncherStartJvm()` (observed in
`__setenv()`). As POSIX permits partial transfers on pipes, especially
for buffers larger than the pipe capacity, the current implementation
appears to assume that transfers complete the full transfer in a single
call, which may not always hold.

I have prepared a patch that introduces static `readFully()` and
`writeFully()` functions to ensure complete buffer transfer, and I've
verified it resolves the crash. While I also noticed a similar pair of
functions exists in `src/java.base/unix/native/libjava/childproc.c`, I
opted for a local static function to avoid unnecessary linking across
modules. The proposed change is available here:

https://github.com/sseu-buhzzi/jdk/commit/e78c0ec0a02eec92cad7f9f34a1f6c82970522cc

I do not currently have JBS access. If appropriate, could someone please
create a JBS issue for this (or advise on the correct process)? I can
then submit the patch for formal review.

Best regards,
Xu Jiawei

---- Reproducer Makefile ----

JAVA_HOME ?= $(shell which javac | xargs realpath | sed 's:/bin/javac::')
JAVAC = $(JAVA_HOME)/bin/javac
JAR = $(JAVA_HOME)/bin/jar
JPACKAGE = $(JAVA_HOME)/bin/jpackage

APP_NAME = LongCp
JAR_COUNT ?= 1024
LIBS_DIR = libs
OUTPUT_DIR = output
LAUNCHER = $(OUTPUT_DIR)/$(APP_NAME)/bin/$(APP_NAME)

define MAIN_JAVA_CONTENT
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}
endef
export MAIN_JAVA_CONTENT

all: run

clean:
    rm -rf Main.java Main.class $(LIBS_DIR) $(OUTPUT_DIR)

run: $(LAUNCHER)
    $(LAUNCHER) || echo 'Bug.'

$(LAUNCHER): $(LIBS_DIR)/main.jar
    rm -rf $(OUTPUT_DIR)
    $(JPACKAGE) --type app-image \
        --name $(APP_NAME) \
        --input $(LIBS_DIR) \
        --main-jar main.jar \
        --main-class Main \
        --dest $(OUTPUT_DIR)

$(LIBS_DIR)/main.jar: Main.class $(LIBS_DIR)
    $(JAR) cf $(LIBS_DIR)/main.jar Main.class

Main.class: Main.java
    $(JAVAC) Main.java

Main.java:
    printf '%s' "$$MAIN_JAVA_CONTENT" > Main.java

$(LIBS_DIR):
    mkdir -p $(LIBS_DIR)
    seq -f '$(LIBS_DIR)/dummy-%010g.jar' 1 $(JAR_COUNT) | xargs touch


Reply via email to