Skip to content

Commit 86db62b

Browse files
asmeikalfacebook-github-bot
authored andcommitted
fix race condition in iOS websocket implementation (#32847)
Summary: The iOS WebSocket implementation has a race condition that causes WebSocket frame payloads to be processed incorrectly. This can cause errors on RFC6455 compliant WebSocket servers: - the server sends a ping frame with no payload - the server sends a text frame with a payload longer than 125 bytes - the client answers the ping with a pong frame echoing back the contents of the text frame This is caused by concurrent modification of the current frame contents, that is passed by reference to the handlers. The concurrent modification happens [here](https://github.com/facebook/react-native/blob/main/Libraries/WebSocket/RCTSRWebSocket.m#L1162). The bug was detected and fixed in the original SocketRocket repository in [this PR](facebookincubator/SocketRocket#371). The relevant part of the fix is applied in this PR. Resolves #30020. ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - Fix WebSocket control frames having payloads longer than 125 bytes Pull Request resolved: #32847 Test Plan: The bug is not easily and consistently reproduced, being a race condition. We were able to reproduce it by connecting a react native app to a websocket server that sent ~100 pings per second and ~100 text frames per second. After a couple of seconds, the server receives an invalid pong message, with a payload equal to the payload of the text frame. The following is a node server that can replicate the problem on a react-native app running on iOS. <details> ``` const { WebSocketServer } = require('ws'); const wss = new WebSocketServer({ port: 8080 }); wss.on('connection', function connection(ws) { const pingInterval = setInterval(() => { ws.ping(); }, 10); const sendInterval = setInterval(() => { const arr = new Array(100); for (let i = 0; i < arr.length; i++) { arr[i] = Math.random(); } ws.send('message with payload longer than 125 bytes: ' + arr.join(',')); }, 10); ws.on('close', () => { clearInterval(pingInterval); clearInterval(sendInterval); }); ws.on('error', (err) => { console.error(err); process.exit(1); }); }); ``` </details> Reviewed By: hramos Differential Revision: D33486828 Pulled By: sota000 fbshipit-source-id: ba52958a584d633813e0d623d29b19999d0c617b
1 parent 0343e69 commit 86db62b

File tree

1 file changed

+4
-0
lines changed

1 file changed

+4
-0
lines changed

Libraries/WebSocket/RCTSRWebSocket.m

+4
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,10 @@ - (void)_disconnect
782782

783783
- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode
784784
{
785+
// copy frameData before handling,
786+
// to avoid concurrent updates to the value at the pointer
787+
frameData = [frameData copy];
788+
785789
// Check that the current data is valid UTF8
786790

787791
BOOL isControlFrame = (opcode == RCTSROpCodePing || opcode == RCTSROpCodePong || opcode == RCTSROpCodeConnectionClose);

0 commit comments

Comments
 (0)