Advertisement
fooker

fearless-concurrency

Apr 20th, 2024
1,088
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Rust 4.56 KB | None | 0 0
  1. use std::{collections::HashMap, sync::Arc};
  2.  
  3. use mysql_async::{prelude::Queryable, Pool};
  4. use axum::{
  5.     extract::State, response::IntoResponse, routing::{get, post}, Json, Router
  6. };
  7. use serde::Deserialize;
  8. use sha1::{Digest, Sha1};
  9. use tokio::sync::{Mutex, RwLock};
  10.  
  11. #[derive(Clone)]
  12. struct User {
  13.     lock: Arc<Mutex<()>>,
  14.     secret: u32
  15. }
  16.  
  17. impl User {
  18.     fn new() -> User {
  19.         User {
  20.             lock: Arc::new(Mutex::new(())),
  21.             secret: rand::random::<u32>()
  22.         }
  23.     }
  24. }
  25.  
  26. #[derive(Clone)]
  27. struct AppState {
  28.     users: Arc<RwLock<HashMap<u64, User>>>,
  29.     pool: Arc<Pool>
  30. }
  31.  
  32. impl AppState {
  33.     fn new(pool: Pool) -> AppState {
  34.         AppState {
  35.             users: Arc::new(RwLock::new(HashMap::new())),
  36.             pool: Arc::new(pool)
  37.         }
  38.     }
  39. }
  40.  
  41. #[tokio::main]
  42. async fn main() {
  43.     tracing_subscriber::fmt::init();
  44.  
  45.     let url = "mysql://fearless_concurrency:fearless_concurrency@database:3306/fearless_concurrency";
  46.  
  47.     let pool = Pool::new(url);
  48.  
  49.     let mut conn = pool.get_conn().await.unwrap();
  50.     conn.exec_drop("CREATE TABLE IF NOT EXISTS info (body varchar(255))", ()).await.unwrap();
  51.     conn.exec_drop("INSERT INTO info VALUES ('Hello, world')", ()).await.unwrap();
  52.  
  53.     let state = AppState::new(pool);
  54.     let app = Router::new()
  55.         .route("/", get(root))
  56.         .route("/register", post(register))
  57.         .route("/query", post(query))
  58.         .route("/flag", post(flag))
  59.         .with_state(state);
  60.  
  61.     let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
  62.     println!("Listener started on port 3000");
  63.     axum::serve(listener, app).await.unwrap();
  64. }
  65.  
  66. async fn root() -> &'static str {
  67.    "Hello, World!"
  68. }
  69.  
  70. async fn register(State(state): State<AppState>) -> impl IntoResponse {
  71.    let uid = rand::random::<u64>();
  72.    let mut users = state.users.write().await;
  73.    let user = User::new();
  74.    users.insert(uid, user);
  75.    uid.to_string()
  76. }
  77.  
  78. #[derive(Deserialize)]
  79. struct Query {
  80.    user_id: u64,
  81.    query_string: String
  82. }
  83.  
  84. async fn query(State(state): State<AppState>, Json(body): Json<Query>) -> axum::response::Result<String> {
  85.    let users = state.users.read().await;
  86.    let user = users.get(&body.user_id).ok_or_else(|| "User not found! Register first!")?;
  87.    let user = user.clone();
  88.  
  89.    // Prevent registrations from being blocked while query is running
  90.    // Fearless concurrency :tm:
  91.    drop(users);
  92.  
  93.    // Prevent concurrent access to the database!
  94.    // Don't even try any race condition thingies
  95.     // They don't exist in rust!
  96.     let _lock = user.lock.lock().await;
  97.     let mut conn = state.pool.get_conn().await.map_err(|_| "Failed to acquire connection")?;
  98.  
  99.     // Unguessable table name (requires knowledge of user id and random table id)
  100.     let table_id = rand::random::<u32>();
  101.     let mut hasher = Sha1::new();
  102.     hasher.update(b"fearless_concurrency");
  103.     hasher.update(body.user_id.to_le_bytes());
  104.     let table_name = format!("tbl_{}_{}", hex::encode(hasher.finalize()), table_id);
  105.  
  106.     let table_name = dbg!(table_name);
  107.     let qs = dbg!(body.query_string);
  108.  
  109.     // Create temporary, unguessable table to store user secret
  110.     conn.exec_drop(
  111.         format!("CREATE TABLE {} (secret int unsigned)", table_name), ()
  112.     ).await.map_err(|_| "Failed to create table")?;
  113.  
  114.     conn.exec_drop(
  115.         format!("INSERT INTO {} values ({})", table_name, user.secret), ()
  116.     ).await.map_err(|_| "Failed to insert secret")?;
  117.  
  118.  
  119.     // Secret can't be leaked here since table name is unguessable!
  120.     let res = conn.exec_first::<String, _, _>(
  121.         format!("SELECT * FROM info WHERE body LIKE '{}'", qs),
  122.         ()
  123.     ).await;
  124.  
  125.     // You'll never get the secret!
  126.     conn.exec_drop(
  127.         format!("DROP TABLE {}", table_name), ()
  128.     ).await.map_err(|_| "Failed to drop table")?;
  129.  
  130.     let res = res.map_err(|_| "Failed to run query")?;
  131.  
  132.     // _lock is automatically dropped when function exits, releasing the user lock
  133.  
  134.     if let Some(result) = res {
  135.         return Ok(result);
  136.     }
  137.     Ok(String::from("No results!"))
  138. }
  139.  
  140.  
  141. #[derive(Deserialize)]
  142. struct ClaimFlag {
  143.     user_id: u64,
  144.     secret: u32
  145. }
  146.  
  147. async fn flag(State(state): State<AppState>, Json(body): Json<ClaimFlag>)  -> axum::response::Result<String> {
  148.     let users = state.users.read().await;
  149.     let user = users.get(&body.user_id).ok_or_else(|| "User not found! Register first!")?;
  150.  
  151.     if user.secret == body.secret {
  152.         return Ok(String::from("grey{fake_flag_for_testing}"));
  153.     }
  154.     Ok(String::from("Wrong!"))
  155. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement