@@ -10,6 +10,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
1010#[ cfg( target_os = "linux" ) ]
1111static ACTIVATION_CONSUMED : AtomicBool = AtomicBool :: new ( false ) ;
1212
13+ #[ cfg( target_os = "linux" ) ]
14+ const MAX_ACTIVATION_LISTENERS : usize = 128 ;
15+
1316#[ cfg( target_os = "linux" ) ]
1417pub fn receive_tcp_listeners ( expected : usize ) -> io:: Result < Vec < TcpListener > > {
1518 use std:: os:: fd:: { FromRawFd as _, IntoRawFd as _, OwnedFd } ;
@@ -25,6 +28,8 @@ pub fn receive_tcp_listeners(expected: usize) -> io::Result<Vec<TcpListener>> {
2528 )
2629 } ) ?;
2730
31+ validate_declared_count ( expected, activation_listener_declaration ( ) ?. as_deref ( ) ) ?;
32+
2833 // `false` is security-critical: environment mutation is unsound once other
2934 // process threads can concurrently read libc's environment storage.
3035 let descriptors =
@@ -71,19 +76,83 @@ pub fn receive_tcp_listeners(expected: usize) -> io::Result<Vec<TcpListener>> {
7176
7277#[ cfg( target_os = "linux" ) ]
7378fn validated_tcp_listener ( index : usize , owned : std:: os:: fd:: OwnedFd ) -> io:: Result < TcpListener > {
74- if rustix:: net:: sockopt:: socket_type ( & owned) ? != rustix:: net:: SocketType :: STREAM {
79+ validate_tcp_listener_properties (
80+ index,
81+ rustix:: net:: sockopt:: socket_type ( & owned) ?,
82+ rustix:: net:: sockopt:: socket_protocol ( & owned) ?,
83+ rustix:: net:: sockopt:: socket_acceptconn ( & owned) ?,
84+ ) ?;
85+ Ok ( TcpListener :: from ( owned) )
86+ }
87+
88+ #[ cfg( target_os = "linux" ) ]
89+ fn validate_tcp_listener_properties (
90+ index : usize ,
91+ socket_type : rustix:: net:: SocketType ,
92+ protocol : Option < rustix:: net:: Protocol > ,
93+ listening : bool ,
94+ ) -> io:: Result < ( ) > {
95+ if socket_type != rustix:: net:: SocketType :: STREAM {
96+ return Err ( io:: Error :: new (
97+ io:: ErrorKind :: InvalidInput ,
98+ format ! ( "descriptor index {index} is not a stream socket" ) ,
99+ ) ) ;
100+ }
101+ if protocol != Some ( rustix:: net:: ipproto:: TCP ) {
75102 return Err ( io:: Error :: new (
76103 io:: ErrorKind :: InvalidInput ,
77- format ! ( "descriptor index {index} is not a TCP stream socket " ) ,
104+ format ! ( "descriptor index {index} does not use TCP" ) ,
78105 ) ) ;
79106 }
80- if !rustix :: net :: sockopt :: socket_acceptconn ( & owned ) ? {
107+ if !listening {
81108 return Err ( io:: Error :: new (
82109 io:: ErrorKind :: InvalidInput ,
83110 format ! ( "descriptor index {index} is not in listening state" ) ,
84111 ) ) ;
85112 }
86- Ok ( TcpListener :: from ( owned) )
113+ Ok ( ( ) )
114+ }
115+
116+ #[ cfg( target_os = "linux" ) ]
117+ fn activation_listener_declaration ( ) -> io:: Result < Option < String > > {
118+ std:: env:: var_os ( "LISTEN_FDS" )
119+ . map ( |value| {
120+ value. into_string ( ) . map_err ( |_| {
121+ io:: Error :: new (
122+ io:: ErrorKind :: InvalidInput ,
123+ "LISTEN_FDS is not valid Unicode" ,
124+ )
125+ } )
126+ } )
127+ . transpose ( )
128+ }
129+
130+ #[ cfg( target_os = "linux" ) ]
131+ fn validate_declared_count ( expected : usize , declared : Option < & str > ) -> io:: Result < ( ) > {
132+ if !( 1 ..=MAX_ACTIVATION_LISTENERS ) . contains ( & expected) {
133+ return Err ( io:: Error :: new (
134+ io:: ErrorKind :: InvalidInput ,
135+ "expected listener count must be between 1 and 128" ,
136+ ) ) ;
137+ }
138+ let declared = declared
139+ . ok_or_else ( || io:: Error :: new ( io:: ErrorKind :: InvalidInput , "LISTEN_FDS is missing" ) ) ?
140+ . parse :: < usize > ( )
141+ . map_err ( |_| {
142+ io:: Error :: new (
143+ io:: ErrorKind :: InvalidInput ,
144+ "LISTEN_FDS is not a valid integer" ,
145+ )
146+ } ) ?;
147+ if declared != expected || declared > MAX_ACTIVATION_LISTENERS {
148+ return Err ( io:: Error :: new (
149+ io:: ErrorKind :: InvalidInput ,
150+ format ! (
151+ "expected {expected} inherited listener(s), but LISTEN_FDS declares {declared}"
152+ ) ,
153+ ) ) ;
154+ }
155+ Ok ( ( ) )
87156}
88157
89158#[ cfg( not( target_os = "linux" ) ) ]
@@ -109,6 +178,32 @@ mod tests {
109178 assert_eq ! ( listener. local_addr( ) . unwrap( ) , expected) ;
110179 }
111180
181+ #[ test]
182+ fn rejects_non_tcp_stream_protocol ( ) {
183+ let error = validate_tcp_listener_properties (
184+ 2 ,
185+ rustix:: net:: SocketType :: STREAM ,
186+ Some ( rustix:: net:: ipproto:: UDP ) ,
187+ true ,
188+ )
189+ . unwrap_err ( ) ;
190+
191+ assert ! ( error. to_string( ) . contains( "index 2 does not use TCP" ) ) ;
192+ }
193+
194+ #[ test]
195+ fn bounds_declared_listener_count_before_descriptor_receipt ( ) {
196+ assert ! ( validate_declared_count( 1 , Some ( "1" ) ) . is_ok( ) ) ;
197+ assert ! ( validate_declared_count( 0 , Some ( "0" ) ) . is_err( ) ) ;
198+ assert ! ( validate_declared_count( 129 , Some ( "129" ) ) . is_err( ) ) ;
199+ assert ! ( validate_declared_count( 1 , None ) . is_err( ) ) ;
200+ assert ! ( validate_declared_count( 1 , Some ( "invalid" ) ) . is_err( ) ) ;
201+ assert ! ( validate_declared_count( 1 , Some ( "2" ) ) . is_err( ) ) ;
202+ assert ! (
203+ validate_declared_count( 1 , Some ( "184467440737095516160000000000000000000" ) ) . is_err( )
204+ ) ;
205+ }
206+
112207 #[ test]
113208 fn rejects_owned_connected_tcp_stream ( ) {
114209 let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
0 commit comments