Eh2406 opened a new issue, #430:
URL: https://github.com/apache/casbin-rs/issues/430

   Hi,
   
   I am Co-Lead of the Rust Cargo Team and was recently hired by the Rust 
Foundation to help triage AI discovered bugs. This bug was found with AI, 
namely scrutineer and Fable. Except where specifically labeled this report was 
written by me. I am posting this as a public issue as I have not received a 
response to our attempts to reach out on 
https://security.apache.org/report-code/, by email, and on your discord.
   
   From the AI: 
   
   > ## Summary
   > 
   > Revoked access remains allowed in CachedEnforcer. The cached decision 
belongs to the `CachedEnforcer` wrapper, but several public lifecycle methods 
update only the wrapped `Enforcer` and do not emit `Event::ClearCache`. A 
previously cached allow therefore survives policy reloads, policy clearing, 
model or adapter replacement, role-manager changes, role-link rebuilds, and 
enforcement disable/enable transitions.
   > 
   > ## Impact
   > 
   > An authenticated user whose permission has been revoked can continue 
receiving authorization for a previously cached request tuple. The stale allow 
has no time-to-live and can persist until other distinct tuples evict it, the 
process restarts, or an unrelated management API mutation clears the cache; 
protected reads or writes represented by that decision may therefore remain 
available after the operator believes revocation has completed.
   > 
   > ## Proof of concept
   > 
   > Create a small Cargo binary depending on `casbin` with features `cached` 
and `tokio` with features `macros,rt-multi-thread`, then run the following 
program:
   > 
   > ```rust
   > use casbin::prelude::*;
   > use casbin::CachedEnforcer;
   > use std::fs;
   > 
   > #[tokio::main]
   > async fn main() {
   >     fs::write("/tmp/model.conf", "[request_definition]\nr = sub, obj, 
act\n\n[policy_definition]\np = sub, obj, act\n\n[policy_effect]\ne = 
some(where (p.eft == allow))\n\n[matchers]\nm = r.sub == p.sub && r.obj == 
p.obj && r.act == p.act\n").unwrap();
   >     fs::write("/tmp/policy.csv", "p, alice, data1, read\n").unwrap();
   >     let mut e = CachedEnforcer::new("/tmp/model.conf", 
"/tmp/policy.csv").await.unwrap();
   >     assert!(e.enforce(("alice", "data1", "read")).unwrap());
   > 
   >     fs::write("/tmp/policy.csv", "").unwrap();
   >     e.load_policy().await.unwrap();
   >     assert!(e.get_policy().is_empty());
   > 
   >     // Fails securely only if the stale allow was invalidated.
   >     assert!(!e.enforce(("alice", "data1", "read")).unwrap());
   > }
   > ```
   > 
   > On the affected code, the final assertion fails because `enforce(("alice", 
"data1", "read"))` remains `true` even though `get_policy()` is empty. The 
verification run also reproduced stale allows after `clear_policy()`, 
`set_model()`, and `enable_enforce(false)` followed by `enable_enforce(true)`; 
a control using `remove_policy()` correctly invalidated the cache.
   > 
   > ## Fix suggestion
   > 
   > Invalidate the wrapper's decision cache after every successful operation 
that can change authorization results, including the lifecycle delegations in 
`src/cached_enforcer.rs`. Centralizing this invariant in the wrapper is 
preferable to relying only on the five `InternalApi` mutation hooks, and 
enforcement state changes must not leave allows created while enforcement was 
disabled in the cache.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to