@@ -340,10 +340,18 @@ impl Authorized {
340
340
new_authorized : & Pubkey ,
341
341
stake_authorize : StakeAuthorize ,
342
342
) -> Result < ( ) , InstructionError > {
343
- self . check ( signers, stake_authorize) ?;
344
343
match stake_authorize {
345
- StakeAuthorize :: Staker => self . staker = * new_authorized,
346
- StakeAuthorize :: Withdrawer => self . withdrawer = * new_authorized,
344
+ StakeAuthorize :: Staker => {
345
+ // Allow either the staker or the withdrawer to change the staker key
346
+ if !signers. contains ( & self . staker ) && !signers. contains ( & self . withdrawer ) {
347
+ return Err ( InstructionError :: MissingRequiredSignature ) ;
348
+ }
349
+ self . staker = * new_authorized
350
+ }
351
+ StakeAuthorize :: Withdrawer => {
352
+ self . check ( signers, stake_authorize) ?;
353
+ self . withdrawer = * new_authorized
354
+ }
347
355
}
348
356
Ok ( ( ) )
349
357
}
@@ -2274,6 +2282,83 @@ mod tests {
2274
2282
) ;
2275
2283
}
2276
2284
2285
+ #[ test]
2286
+ fn test_authorize_override ( ) {
2287
+ let withdrawer_pubkey = Pubkey :: new_rand ( ) ;
2288
+ let stake_lamports = 42 ;
2289
+ let stake_account = Account :: new_ref_data_with_space (
2290
+ stake_lamports,
2291
+ & StakeState :: Initialized ( Meta :: auto ( & withdrawer_pubkey) ) ,
2292
+ std:: mem:: size_of :: < StakeState > ( ) ,
2293
+ & id ( ) ,
2294
+ )
2295
+ . expect ( "stake_account" ) ;
2296
+
2297
+ let stake_keyed_account = KeyedAccount :: new ( & withdrawer_pubkey, true , & stake_account) ;
2298
+
2299
+ // Authorize a staker pubkey and move the withdrawer key into cold storage.
2300
+ let stake_pubkey = Pubkey :: new_rand ( ) ;
2301
+ let signers = vec ! [ withdrawer_pubkey] . into_iter ( ) . collect ( ) ;
2302
+ assert_eq ! (
2303
+ stake_keyed_account. authorize(
2304
+ & stake_pubkey,
2305
+ StakeAuthorize :: Staker ,
2306
+ & signers,
2307
+ & Clock :: default ( )
2308
+ ) ,
2309
+ Ok ( ( ) )
2310
+ ) ;
2311
+
2312
+ // Attack! The stake key (a hot key) is stolen and used to authorize a new staker.
2313
+ let mallory_pubkey = Pubkey :: new_rand ( ) ;
2314
+ let signers = vec ! [ stake_pubkey] . into_iter ( ) . collect ( ) ;
2315
+ assert_eq ! (
2316
+ stake_keyed_account. authorize(
2317
+ & mallory_pubkey,
2318
+ StakeAuthorize :: Staker ,
2319
+ & signers,
2320
+ & Clock :: default ( )
2321
+ ) ,
2322
+ Ok ( ( ) )
2323
+ ) ;
2324
+
2325
+ // Verify the original staker no longer has access.
2326
+ let new_stake_pubkey = Pubkey :: new_rand ( ) ;
2327
+ assert_eq ! (
2328
+ stake_keyed_account. authorize(
2329
+ & new_stake_pubkey,
2330
+ StakeAuthorize :: Staker ,
2331
+ & signers,
2332
+ & Clock :: default ( )
2333
+ ) ,
2334
+ Err ( InstructionError :: MissingRequiredSignature )
2335
+ ) ;
2336
+
2337
+ // Verify the withdrawer (pulled from cold storage) can save the day.
2338
+ let signers = vec ! [ withdrawer_pubkey] . into_iter ( ) . collect ( ) ;
2339
+ assert_eq ! (
2340
+ stake_keyed_account. authorize(
2341
+ & new_stake_pubkey,
2342
+ StakeAuthorize :: Withdrawer ,
2343
+ & signers,
2344
+ & Clock :: default ( )
2345
+ ) ,
2346
+ Ok ( ( ) )
2347
+ ) ;
2348
+
2349
+ // Attack! Verify the staker cannot be used to authorize a withdraw.
2350
+ let signers = vec ! [ new_stake_pubkey] . into_iter ( ) . collect ( ) ;
2351
+ assert_eq ! (
2352
+ stake_keyed_account. authorize(
2353
+ & mallory_pubkey,
2354
+ StakeAuthorize :: Withdrawer ,
2355
+ & signers,
2356
+ & Clock :: default ( )
2357
+ ) ,
2358
+ Ok ( ( ) )
2359
+ ) ;
2360
+ }
2361
+
2277
2362
#[ test]
2278
2363
fn test_split_source_uninitialized ( ) {
2279
2364
let stake_pubkey = Pubkey :: new_rand ( ) ;
0 commit comments