-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Summary
In src/cortex-app-server/src/share.rs, the get_shared_session handler checks whether view_count >= max_views and then, in a separate operation, calls increment_view(). Because these two steps are not atomic, concurrent requests can both pass the check before either increments the counter, allowing the view limit to be exceeded.
Affected Code
// src/cortex-app-server/src/share.rs lines 347-354
// Check if max views exceeded
if let Some(max) = share.max_views
&& share.view_count >= max
{
return Err(AppError::Gone("Share view limit reached".to_string()));
}
// Increment view count ← separate, non-atomic step
state.share_manager.increment_view(&token).await;The get() call (line 334) returns a clone of the SharedSessionData. The check is performed on that stale clone. By the time increment_view() acquires the write lock, another concurrent request may have already incremented the counter past max_views.
Root Cause
The check-then-act pattern here is a classic Time-of-Check / Time-of-Use (TOCTOU) race:
- Request A reads
view_count = 9,max_views = 10→ passes check. - Request B reads
view_count = 9,max_views = 10→ passes check. - Request A calls
increment_view→view_count = 10. - Request B calls
increment_view→view_count = 11(exceeds limit).
Impact
- Share links with
max_viewsset can be accessed more times than the creator intended. - Under high concurrency the overshoot is unbounded (N concurrent requests all pass the check simultaneously).
Suggested Fix
Add a combined check_and_increment method to ShareManager that performs the check and increment atomically under a single write lock:
/// Atomically check max_views and increment if allowed.
/// Returns Ok(new_count) if allowed, Err if limit reached or share not found.
pub async fn check_and_increment_view(
&self,
token: &str,
) -> Result<u32, &'static str> {
let mut shares = self.shares.write().await;
let share = shares.get_mut(token).ok_or("not found")?;
if let Some(max) = share.max_views {
if share.view_count >= max {
return Err("limit reached");
}
}
share.view_count += 1;
Ok(share.view_count)
}Then replace the two-step check + increment in the handler with a single call to this method.