55use std:: {
66 collections:: { BTreeMap , BTreeSet } ,
77 fmt:: Display ,
8+ net:: { Ipv4Addr , Ipv6Addr } ,
89 ops:: Deref ,
910 path:: { Path , PathBuf } ,
1011 process:: Command ,
@@ -370,6 +371,43 @@ impl HostShared {
370371 }
371372}
372373
374+ // The gateway's WireGuard fields are rendered into a wg-quick config, and
375+ // wg-quick runs its `PreUp`/`PostUp` hooks as root. Accept only values that
376+ // cannot carry config syntax, so the gateway cannot inject lines.
377+ fn parse_wg_ip ( ip : & str ) -> Result < Ipv4Addr > {
378+ ip. parse ( )
379+ . with_context ( || format ! ( "invalid WireGuard address: {ip:?}" ) )
380+ }
381+
382+ fn parse_wg_public_key ( key : & str ) -> Result < String > {
383+ use base64:: { engine:: general_purpose:: STANDARD , Engine as _} ;
384+ let decoded = STANDARD
385+ . decode ( key)
386+ . ok ( )
387+ . filter ( |decoded| decoded. len ( ) == 32 )
388+ . with_context ( || format ! ( "invalid WireGuard public key: {key:?}" ) ) ?;
389+ Ok ( STANDARD . encode ( decoded) )
390+ }
391+
392+ fn validate_wg_endpoint ( endpoint : & str ) -> Result < & str > {
393+ let valid = endpoint. rsplit_once ( ':' ) . is_some_and ( |( host, port) | {
394+ let host_ok = match host. strip_prefix ( '[' ) . and_then ( |h| h. strip_suffix ( ']' ) ) {
395+ Some ( v6) => v6. parse :: < Ipv6Addr > ( ) . is_ok ( ) ,
396+ None => {
397+ !host. is_empty ( )
398+ && host
399+ . chars ( )
400+ . all ( |c| c. is_ascii_alphanumeric ( ) || c == '.' || c == '-' )
401+ }
402+ } ;
403+ host_ok && port. parse :: < u16 > ( ) . is_ok ( )
404+ } ) ;
405+ if !valid {
406+ bail ! ( "invalid WireGuard endpoint: {endpoint:?}" ) ;
407+ }
408+ Ok ( endpoint)
409+ }
410+
373411const GATEWAY_CACHE_PATH : & str = "/run/dstack/gateway-cache.json" ;
374412const WG_CONFIG_PATH : & str = "/etc/wireguard/dstack-wg0.conf" ;
375413/// Certificate validity period in seconds (10 days)
@@ -628,7 +666,7 @@ impl<'a> GatewayContext<'a> {
628666 } ;
629667 let mut wg_info = response. wg . context ( "Missing wg info" ) ?;
630668
631- let client_ip = & wg_info. client_ip ;
669+ let client_ip = parse_wg_ip ( & wg_info. client_ip ) ? ;
632670
633671 // Sort peers by public key for consistent config generation
634672 wg_info. servers . sort_by ( |a, b| a. pk . cmp ( & b. pk ) ) ;
@@ -643,7 +681,9 @@ impl<'a> GatewayContext<'a> {
643681 key_store. wg_sk
644682 ) ;
645683 for WireGuardPeer { pk, ip, endpoint } in & wg_info. servers {
646- let ip = ip. split ( '/' ) . next ( ) . unwrap_or_default ( ) ;
684+ let pk = parse_wg_public_key ( pk) ?;
685+ let ip = parse_wg_ip ( ip. split ( '/' ) . next ( ) . unwrap_or_default ( ) ) ?;
686+ let endpoint = validate_wg_endpoint ( endpoint) ?;
647687 new_config. push_str ( & format ! (
648688 "[Peer]\n \
649689 PublicKey = {pk}\n \
@@ -1449,12 +1489,11 @@ impl Stage1<'_> {
14491489 ciphertext : & [ u8 ] ,
14501490 allowed : & BTreeSet < String > ,
14511491 ) -> Result < BTreeMap < String , String > > {
1452- let vars = if !key . is_empty ( ) && ! ciphertext. is_empty ( ) {
1492+ let vars = if !ciphertext. is_empty ( ) {
14531493 info ! ( "Processing encrypted env" ) ;
1454- let env_crypt_key: [ u8 ; 32 ] = key
1455- . try_into ( )
1456- . ok ( )
1457- . context ( "Invalid env crypt key length" ) ?;
1494+ let env_crypt_key: [ u8 ; 32 ] = key. try_into ( ) . ok ( ) . context (
1495+ "encrypted env is present but the key provider gave no valid env crypt key" ,
1496+ ) ?;
14581497 let decrypted_json =
14591498 dh_decrypt ( env_crypt_key, ciphertext) . context ( "Failed to decrypt env file" ) ?;
14601499 crate :: parse_env_file:: parse_env ( & decrypted_json, allowed) ?
@@ -1872,3 +1911,32 @@ fn test_validate_luks2_header_rejects_keyslot_area_that_overflows() {
18721911 "{error:#}"
18731912 ) ;
18741913}
1914+
1915+ #[ test]
1916+ fn wireguard_fields_reject_config_syntax ( ) {
1917+ for endpoint in [
1918+ "gateway.example.com:51820" ,
1919+ "192.0.2.1:51821" ,
1920+ "[2001:db8::1]:51822" ,
1921+ ] {
1922+ assert_eq ! ( validate_wg_endpoint( endpoint) . unwrap( ) , endpoint) ;
1923+ }
1924+ for endpoint in [
1925+ "" ,
1926+ "192.0.2.1" ,
1927+ "192.0.2.1:99999" ,
1928+ "0.0.0.0/0:1" ,
1929+ "192.0.2.1:51820\n [Interface]\n PostUp = id" ,
1930+ "[not-v6]:51820" ,
1931+ ] {
1932+ assert ! ( validate_wg_endpoint( endpoint) . is_err( ) , "{endpoint:?}" ) ;
1933+ }
1934+
1935+ let key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" ;
1936+ assert_eq ! ( parse_wg_public_key( key) . unwrap( ) , key) ;
1937+ assert ! ( parse_wg_public_key( & format!( "{key}\n PostUp = id" ) ) . is_err( ) ) ;
1938+ assert ! ( parse_wg_public_key( "AAAA" ) . is_err( ) ) ;
1939+
1940+ assert ! ( parse_wg_ip( "10.0.0.2" ) . is_ok( ) ) ;
1941+ assert ! ( parse_wg_ip( "10.0.0.2/32\n [Interface]" ) . is_err( ) ) ;
1942+ }
0 commit comments