diff --git a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/SharedServerManager.java b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/SharedServerManager.java index a28148f684..c5b2126ada 100644 --- a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/SharedServerManager.java +++ b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/SharedServerManager.java @@ -21,7 +21,6 @@ import java.io.InputStreamReader; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -242,14 +241,6 @@ private void startServer() throws IOException, InterruptedException, TimeoutExce shutdownUnsafe(); } - // Find a free port for the server to listen on - int port; - try (ServerSocket tempSocket = new ServerSocket()) { - tempSocket.setReuseAddress(true); - tempSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); - port = tempSocket.getLocalPort(); - } - // Generate auth token for this server instance byte[] token = new byte[PipesServer.AUTH_TOKEN_LENGTH_BYTES]; new SecureRandom().nextBytes(token); @@ -287,7 +278,10 @@ private void startServer() throws IOException, InterruptedException, TimeoutExce // Pass port and auth token via environment variables so they are not // visible in /proc//cmdline. The token is only readable via // /proc//environ which requires same-uid access. - pb.environment().put("TIKA_PIPES_PORT", Integer.toString(port)); + // Pass port=0 so the server binds to any available ephemeral port. + // The actual port is read back from the READY:{port} stdout signal, + // eliminating the TOCTOU race between probing a free port and binding it. + pb.environment().put("TIKA_PIPES_PORT", "0"); pb.environment().put("TIKA_PIPES_AUTH_TOKEN", HexFormat.of().formatHex(token)); // Redirect stderr to inherit, capture stdout to read the READY signal pb.redirectErrorStream(false); @@ -306,13 +300,12 @@ private void startServer() throws IOException, InterruptedException, TimeoutExce throw new ServerInitializationException(msg, e); } - // Wait for the server to signal it's ready by printing the port - waitForServerReady(port); - serverPort = port; - LOG.info("Shared server started successfully"); + // Wait for the server to signal it's ready and report the port it actually bound to + serverPort = waitForServerReady(); + LOG.info("Shared server started successfully on port {}", serverPort); } - private void waitForServerReady(int expectedPort) throws IOException, ServerInitializationException { + private int waitForServerReady() throws IOException, ServerInitializationException { long startTime = System.currentTimeMillis(); try (BufferedReader reader = new BufferedReader( @@ -340,13 +333,12 @@ private void waitForServerReady(int expectedPort) throws IOException, ServerInit if (reader.ready()) { String line = reader.readLine(); if (line != null && line.startsWith("READY:")) { - // Server is ready, parse the port String portStr = line.substring("READY:".length()).trim(); - int actualPort = Integer.parseInt(portStr); - if (actualPort != expectedPort) { - LOG.warn("Server reported different port {} than expected {}", actualPort, expectedPort); + int port = Integer.parseInt(portStr); + if (port <= 0 || port > 65535) { + throw new IOException("Server reported invalid port: " + port); } - return; + return port; } } else { // No data available, sleep briefly diff --git a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/server/PipesServer.java b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/server/PipesServer.java index dc214514be..fb7a74551f 100644 --- a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/server/PipesServer.java +++ b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/server/PipesServer.java @@ -255,8 +255,8 @@ private static void runSharedMode(int port, int numConnections, Path tikaConfigP serverSocket.setReuseAddress(true); serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), numConnections); - // Signal readiness to the parent process via stdout - System.out.println("READY:" + port); + // Signal readiness to the parent process via stdout, reporting the actual bound port + System.out.println("READY:" + serverSocket.getLocalPort()); System.out.flush(); LOG.info("Shared server ready, accepting connections");