{"record":{"id":"5dce60f4dd4301de","repo":"shadowsocks/shadowsocks-rust","slug":"cipher-is-none-5dce60","errorCode":null,"errorMessage":"cipher is None","messagePattern":"cipher is None","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/shadowsocks/src/relay/tcprelay/stream.rs","lineNumber":118,"sourceCode":"                    let key = unsafe { &*(key.as_ref() as *const _) };\n                    ready!(self.poll_read_iv(cx, context, stream, key))?;\n\n                    self.buffer.clear();\n                    self.buffer.truncate(0);\n                    self.state = DecryptReadState::Read;\n                    self.has_handshaked = true;\n                }\n                DecryptReadState::Read => {\n                    let before_n = buf.filled().len();\n                    ready!(Pin::new(stream).poll_read(cx, buf))?;\n                    let after_n = buf.filled().len();\n                    if before_n == after_n {\n                        return Ok(()).into();\n                    }\n\n                    let m = &mut buf.filled_mut()[before_n..];\n\n                    let cipher = self.cipher.as_mut().expect(\"cipher is None\");\n                    if !cipher.decrypt_packet(m) {\n                        return Err(ProtocolError::DecryptError).into();\n                    }\n\n                    return Ok(()).into();\n                }\n            }\n        }\n    }\n\n    fn poll_read_iv<S>(\n        &mut self,\n        cx: &mut task::Context<'_>,\n        context: &Context,\n        stream: &mut S,\n        key: &[u8],\n    ) -> Poll<ProtocolResult<()>>\n    where","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/shadowsocks/shadowsocks-rust/blob/8eb0f0a65b1d976ab6bed5787327ef86529b0435/crates/shadowsocks/src/relay/tcprelay/stream.rs#L100-L136","documentation":"Panic from `self.cipher.as_mut().expect(\"cipher is None\")` in `poll_read_decrypted` of the AEAD-2022 stream (stream.rs). At this point the code decrypts a buffered packet in place; the `cipher` Option must hold the decryption context installed during the handshake. `None` means the stream was driven into the decryption step without ever having its cipher initialized (or after it was consumed/moved), violating the connection state machine.","triggerScenarios":"Polling reads on an AEAD-2022 stream whose cipher was never set — handshake init not run (no server nonce/subkey setup), reads attempted after a previous `Ok(None)` EOF, or a double poll after the state machine transitioned and cleared the cipher.","commonSituations":"Proxy loops continuing to poll after the peer closed (EOF already seen); reusing a connection instance after a decrypt error; building a stream manually and skipping the `set_server_nonce`/cipher installation; upgrading the crate and relying on old init sequencing.","solutions":["Treat EOF (`Ok(None)`) and any ProtocolError as terminal: stop polling and create a fresh stream per connection.","Ensure stream initialization (deriving the session subkey from the request salt and installing the cipher) completed before reading.","Never reuse or share the stream object across connections or tasks.","Swap the expect for `ok_or_else` returning a ProtocolError to convert misuse into a recoverable error."],"exampleFix":"// before\nlet cipher = self.cipher.as_mut().expect(\"cipher is None\");\n// after\nlet Some(cipher) = self.cipher.as_mut() else {\n    return Err(ProtocolError::DecryptError).into();\n};","handlingStrategy":"type-guard","validationCode":"if stream.cipher.is_none() { return Err(io::Error::new(io::ErrorKind::InvalidInput, \"cipher not initialized\")); }","typeGuard":"fn can_decrypt(s: &Aead2022Stream) -> bool { s.cipher.is_some() }","tryCatchPattern":"loop {\n    match stream.poll_read(cx, &mut buf) {\n        Poll::Ready(Ok(None)) | Poll::Ready(Err(_)) => break, // terminal\n        Poll::Ready(Ok(Some(n))) => forward(&buf[..n]),\n        Poll::Pending => return Poll::Pending,\n    }\n}","preventionTips":["Stop polling after EOF or decrypt errors; rebuild the stream.","Verify initialization (nonce + cipher install) before reading.","Do not share stream instances across tasks."],"tags":["rust","panic","aead-2022","state-machine","stream"],"backgroundTag":"internal-invariant-violation","analyzedSha":"8eb0f0a65b1d976ab6bed5787327ef86529b0435","analyzedAt":"2026-09-09T12:20:43.168Z","contentChangedAt":"2026-09-09T12:20:43.168Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}