@@ -182,6 +182,7 @@ struct Connection {
182182 stats : Arc < Stats > ,
183183 txs_limit : usize ,
184184 subscription_limit : usize ,
185+ max_request_bytes : usize ,
185186 #[ cfg( feature = "electrum-discovery" ) ]
186187 discovery : Option < Arc < DiscoveryManager > > ,
187188 rpc_logging : RpcLogging ,
@@ -204,6 +205,7 @@ impl Connection {
204205 stats : Arc < Stats > ,
205206 txs_limit : usize ,
206207 subscription_limit : usize ,
208+ max_request_bytes : usize ,
207209 #[ cfg( feature = "electrum-discovery" ) ] discovery : Option < Arc < DiscoveryManager > > ,
208210 rpc_logging : RpcLogging ,
209211 salt : String ,
@@ -218,6 +220,7 @@ impl Connection {
218220 stats,
219221 txs_limit,
220222 subscription_limit,
223+ max_request_bytes,
221224 #[ cfg( feature = "electrum-discovery" ) ]
222225 discovery,
223226 rpc_logging,
@@ -784,13 +787,51 @@ impl Connection {
784787 }
785788 }
786789
790+ fn read_bounded_line ( reader : & mut BufReader < TcpStream > , max_len : usize ) -> Result < Vec < u8 > > {
791+ let mut line = Vec :: < u8 > :: new ( ) ;
792+ loop {
793+ let ( done, consumed) = {
794+ let available = reader. fill_buf ( ) . chain_err ( || "failed to read a request" ) ?;
795+ if available. is_empty ( ) {
796+ ( true , 0 ) // EOF
797+ } else if let Some ( pos) = available. iter ( ) . position ( |& b| b == b'\n' ) {
798+ let new_len = line
799+ . len ( )
800+ . checked_add ( pos + 1 )
801+ . ok_or_else ( || "request line length overflow" ) ?;
802+ if new_len > max_len {
803+ bail ! ( "request line exceeds maximum size of {} bytes" , max_len) ;
804+ }
805+ line. extend_from_slice ( & available[ ..=pos] ) ;
806+ ( true , pos + 1 )
807+ } else {
808+ let new_len = line
809+ . len ( )
810+ . checked_add ( available. len ( ) )
811+ . ok_or_else ( || "request line length overflow" ) ?;
812+ if new_len > max_len {
813+ bail ! ( "request line exceeds maximum size of {} bytes" , max_len) ;
814+ }
815+ let take = available. len ( ) ;
816+ line. extend_from_slice ( & available[ ..take] ) ;
817+ ( false , take)
818+ }
819+ } ;
820+ reader. consume ( consumed) ;
821+ if done {
822+ return Ok ( line) ;
823+ }
824+ }
825+ }
826+
787827 #[ trace]
788- fn parse_requests ( mut reader : BufReader < TcpStream > , tx : & SyncSender < Message > ) -> Result < ( ) > {
828+ fn parse_requests (
829+ mut reader : BufReader < TcpStream > ,
830+ tx : & SyncSender < Message > ,
831+ max_request_bytes : usize ,
832+ ) -> Result < ( ) > {
789833 loop {
790- let mut line = Vec :: < u8 > :: new ( ) ;
791- reader
792- . read_until ( b'\n' , & mut line)
793- . chain_err ( || "failed to read a request" ) ?;
834+ let line = Connection :: read_bounded_line ( & mut reader, max_request_bytes) ?;
794835 if line. is_empty ( ) {
795836 return Ok ( ( ) ) ;
796837 } else {
@@ -810,8 +851,12 @@ impl Connection {
810851 }
811852 }
812853
813- fn reader_thread ( reader : BufReader < TcpStream > , tx : SyncSender < Message > ) -> Result < ( ) > {
814- let result = Connection :: parse_requests ( reader, & tx) ;
854+ fn reader_thread (
855+ reader : BufReader < TcpStream > ,
856+ tx : SyncSender < Message > ,
857+ max_request_bytes : usize ,
858+ ) -> Result < ( ) > {
859+ let result = Connection :: parse_requests ( reader, & tx, max_request_bytes) ;
815860 if let Err ( e) = tx. send ( Message :: Done ) {
816861 // The writer already tore the channel down (e.g. after a write
817862 // error or connection expiry) — expected during teardown races.
@@ -826,7 +871,10 @@ impl Connection {
826871
827872 let reader = BufReader :: new ( self . stream . try_clone ( ) . expect ( "failed to clone TcpStream" ) ) ;
828873 let sender = self . sender . clone ( ) ;
829- let child = spawn_thread ( "reader" , || Connection :: reader_thread ( reader, sender) ) ;
874+ let max_request_bytes = self . max_request_bytes ;
875+ let child = spawn_thread ( "reader" , move || {
876+ Connection :: reader_thread ( reader, sender, max_request_bytes)
877+ } ) ;
830878 if let Err ( e) = self . handle_replies ( receiver) {
831879 if is_disconnect ( & e) {
832880 // client went away mid-exchange (broken pipe / reset) — not actionable
@@ -1189,6 +1237,7 @@ impl RPC {
11891237 let rpc_addr = config. electrum_rpc_addr ;
11901238 let txs_limit = config. electrum_txs_limit ;
11911239 let subscription_limit = config. electrum_subscription_limit ;
1240+ let max_request_bytes = config. electrum_rpc_max_request_num_bytes ;
11921241 let conn_max_age = config. electrum_rpc_conn_max_age ;
11931242
11941243 RPC {
@@ -1240,6 +1289,7 @@ impl RPC {
12401289 stats,
12411290 txs_limit,
12421291 subscription_limit,
1292+ max_request_bytes,
12431293 #[ cfg( feature = "electrum-discovery" ) ]
12441294 discovery,
12451295 rpc_logging,
@@ -1512,4 +1562,127 @@ mod tests {
15121562 drop ( stream) ;
15131563 assert ! ( weak. upgrade( ) . is_none( ) ) ;
15141564 }
1565+
1566+ #[ test]
1567+ fn read_bounded_line_reads_a_normal_line ( ) {
1568+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1569+ let mut client = TcpStream :: connect ( listener. local_addr ( ) . unwrap ( ) ) . unwrap ( ) ;
1570+ let mut reader = BufReader :: new ( listener. accept ( ) . unwrap ( ) . 0 ) ;
1571+
1572+ client. write_all ( b"hello world\n " ) . unwrap ( ) ;
1573+ let line = Connection :: read_bounded_line ( & mut reader, 1024 ) . unwrap ( ) ;
1574+ assert_eq ! ( line, b"hello world\n " ) ;
1575+ }
1576+
1577+ #[ test]
1578+ fn read_bounded_line_rejects_a_line_without_a_newline_past_the_limit ( ) {
1579+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1580+ let mut client = TcpStream :: connect ( listener. local_addr ( ) . unwrap ( ) ) . unwrap ( ) ;
1581+ let mut reader = BufReader :: new ( listener. accept ( ) . unwrap ( ) . 0 ) ;
1582+
1583+ // Stream well past the limit with no '\n': the never-terminated-line
1584+ // OOM this guards against.
1585+ let chunk = [ b'A' ; 4096 ] ;
1586+ let writer = thread:: spawn ( move || {
1587+ for _ in 0 ..64 {
1588+ if client. write_all ( & chunk) . is_err ( ) {
1589+ break ;
1590+ }
1591+ }
1592+ } ) ;
1593+
1594+ let err = Connection :: read_bounded_line ( & mut reader, 1024 ) . unwrap_err ( ) ;
1595+ assert ! ( err. to_string( ) . contains( "exceeds maximum size" ) ) ;
1596+ let _ = writer. join ( ) ;
1597+ }
1598+
1599+ #[ test]
1600+ fn read_bounded_line_rejects_a_line_whose_terminating_newline_arrives_over_the_limit ( ) {
1601+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1602+ let mut client = TcpStream :: connect ( listener. local_addr ( ) . unwrap ( ) ) . unwrap ( ) ;
1603+ let mut reader = BufReader :: new ( listener. accept ( ) . unwrap ( ) . 0 ) ;
1604+
1605+ // The '\n' lands in the same fill_buf() chunk that pushes the
1606+ // accumulated line past the limit, so `done` is true on the very
1607+ // iteration where the size check must fire.
1608+ let mut payload = vec ! [ b'A' ; 2048 ] ;
1609+ payload. push ( b'\n' ) ;
1610+ client. write_all ( & payload) . unwrap ( ) ;
1611+
1612+ let err = Connection :: read_bounded_line ( & mut reader, 1024 ) . unwrap_err ( ) ;
1613+ assert ! ( err. to_string( ) . contains( "exceeds maximum size" ) ) ;
1614+ }
1615+
1616+ #[ test]
1617+ fn read_bounded_line_returns_empty_on_immediate_eof ( ) {
1618+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1619+ let client = TcpStream :: connect ( listener. local_addr ( ) . unwrap ( ) ) . unwrap ( ) ;
1620+ let mut reader = BufReader :: new ( listener. accept ( ) . unwrap ( ) . 0 ) ;
1621+
1622+ drop ( client) ;
1623+ let line = Connection :: read_bounded_line ( & mut reader, 1024 ) . unwrap ( ) ;
1624+ assert ! ( line. is_empty( ) ) ;
1625+ }
1626+
1627+ #[ test]
1628+ fn read_bounded_line_returns_partial_line_on_eof_without_newline ( ) {
1629+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1630+ let mut client = TcpStream :: connect ( listener. local_addr ( ) . unwrap ( ) ) . unwrap ( ) ;
1631+ let mut reader = BufReader :: new ( listener. accept ( ) . unwrap ( ) . 0 ) ;
1632+
1633+ client. write_all ( b"no newline here" ) . unwrap ( ) ;
1634+ drop ( client) ;
1635+
1636+ let line = Connection :: read_bounded_line ( & mut reader, 1024 ) . unwrap ( ) ;
1637+ assert_eq ! ( line, b"no newline here" ) ;
1638+ }
1639+
1640+ #[ test]
1641+ fn read_bounded_line_accepts_a_line_exactly_at_the_limit ( ) {
1642+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1643+ let mut client = TcpStream :: connect ( listener. local_addr ( ) . unwrap ( ) ) . unwrap ( ) ;
1644+ let mut reader = BufReader :: new ( listener. accept ( ) . unwrap ( ) . 0 ) ;
1645+
1646+ let payload = vec ! [ b'A' ; 1024 ] ;
1647+ client. write_all ( & payload) . unwrap ( ) ;
1648+ drop ( client) ;
1649+
1650+ let line = Connection :: read_bounded_line ( & mut reader, 1024 ) . unwrap ( ) ;
1651+ assert_eq ! ( line, payload) ;
1652+ }
1653+
1654+ #[ test]
1655+ fn read_bounded_line_rejects_a_single_chunk_exceeding_a_small_limit ( ) {
1656+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1657+ let mut client = TcpStream :: connect ( listener. local_addr ( ) . unwrap ( ) ) . unwrap ( ) ;
1658+ let mut reader = BufReader :: new ( listener. accept ( ) . unwrap ( ) . 0 ) ;
1659+
1660+ // A single write, well within the reader's default 8KiB buffer, that
1661+ // still lands entirely in one fill_buf() call and exceeds a limit
1662+ // much smaller than that buffer.
1663+ let payload = vec ! [ b'A' ; 500 ] ;
1664+ client. write_all ( & payload) . unwrap ( ) ;
1665+
1666+ let err = Connection :: read_bounded_line ( & mut reader, 100 ) . unwrap_err ( ) ;
1667+ assert ! ( err. to_string( ) . contains( "exceeds maximum size" ) ) ;
1668+ }
1669+
1670+ #[ test]
1671+ fn read_bounded_line_handles_unlimited_max_len_split_across_writes ( ) {
1672+ // max_len == usize::MAX is what a configured value of 0 ("unlimited")
1673+ // maps to. Splitting the request and its terminating newline across
1674+ // separate writes forces at least one fill_buf() call to return a
1675+ // newline-less chunk, which used to compute `max_len + 1` and
1676+ // overflow.
1677+ let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
1678+ let mut client = TcpStream :: connect ( listener. local_addr ( ) . unwrap ( ) ) . unwrap ( ) ;
1679+ let mut reader = BufReader :: new ( listener. accept ( ) . unwrap ( ) . 0 ) ;
1680+
1681+ client. write_all ( b"server.ping" ) . unwrap ( ) ;
1682+ thread:: sleep ( Duration :: from_millis ( 50 ) ) ;
1683+ client. write_all ( b"\n " ) . unwrap ( ) ;
1684+
1685+ let line = Connection :: read_bounded_line ( & mut reader, usize:: MAX ) . unwrap ( ) ;
1686+ assert_eq ! ( line, b"server.ping\n " ) ;
1687+ }
15151688}
0 commit comments