Skip to content

Commit 83c8a0e

Browse files
authored
Fix audit findings (#68)
1 parent 3f35798 commit 83c8a0e

File tree

5 files changed

+92
-20
lines changed

5 files changed

+92
-20
lines changed

solana-programs/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solana-programs/programs/tuktuk/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tuktuk"
3-
version = "0.2.3"
3+
version = "0.2.4"
44
description = "Created with Anchor"
55
edition = "2021"
66

solana-programs/programs/tuktuk/src/error.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,18 @@ pub enum ErrorCode {
4040
TaskQueueHasQueueAuthorities,
4141
#[msg("Free tasks must be less than the capacity of the task queue")]
4242
FreeTasksGreaterThanCapacity,
43+
#[msg("Task ID is already in use")]
44+
TaskIdAlreadyInUse,
45+
#[msg("Duplicate task IDs provided")]
46+
DuplicateTaskIds,
47+
#[msg("Number of free task IDs does not match number of free task accounts")]
48+
MismatchedFreeTaskCounts,
49+
#[msg("Too many returned tasks, increase free tasks count")]
50+
TooManyReturnedTasks,
51+
#[msg("Malformed remote transaction")]
52+
MalformedRemoteTransaction,
53+
#[msg("Invalid account key")]
54+
InvalidAccountKey,
55+
#[msg("Invalid crank reward")]
56+
InvalidCrankReward,
4357
}

solana-programs/programs/tuktuk/src/instructions/initialize_task_queue_v0.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub struct InitializeTaskQueueV0<'info> {
3535
payer = payer,
3636
seeds = ["task_queue".as_bytes(), tuktuk_config.key().as_ref(), &tuktuk_config.next_task_queue_id.to_le_bytes()[..]],
3737
bump,
38-
space = 60 + std::mem::size_of::<TaskQueueV0>() + args.name.len() + ((args.capacity + 7) / 8) as usize,
38+
space = 60 + std::mem::size_of::<TaskQueueV0>() + args.name.len() + ((args.capacity + 7) / 8) as usize + args.lookup_tables.len() * 32,
3939
)]
4040
pub task_queue: Box<Account<'info, TaskQueueV0>>,
4141
#[account(

solana-programs/programs/tuktuk/src/instructions/run_task_v0.rs

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -294,12 +294,43 @@ impl<'a, 'info> TaskProcessor<'a, 'info> {
294294
}
295295

296296
fn create_new_task(&mut self, task: TaskReturnV0) -> Result<()> {
297+
require_gte!(
298+
40,
299+
task.description.len(),
300+
ErrorCode::InvalidDescriptionLength
301+
);
302+
require_gte!(
303+
task.crank_reward
304+
.unwrap_or(self.ctx.accounts.task_queue.min_crank_reward),
305+
self.ctx.accounts.task_queue.min_crank_reward,
306+
ErrorCode::InvalidCrankReward
307+
);
308+
require_gte!(
309+
self.ctx.accounts.task_queue.capacity,
310+
(task.free_tasks + 1) as u16,
311+
ErrorCode::FreeTasksGreaterThanCapacity
312+
);
313+
297314
let free_task_account = &self.ctx.remaining_accounts[self.free_task_index];
298315
self.free_task_index += 1;
299316
let task_queue = &mut self.ctx.accounts.task_queue;
300317
let task_queue_key = task_queue.key();
301318

302-
let task_id = self.free_task_ids.pop().unwrap();
319+
let task_id = self
320+
.free_task_ids
321+
.pop()
322+
.ok_or(error!(ErrorCode::TooManyReturnedTasks))?;
323+
324+
require!(
325+
!task_queue.task_exists(task_id),
326+
ErrorCode::TaskIdAlreadyInUse
327+
);
328+
329+
// Verify the account is empty
330+
require!(
331+
free_task_account.data_is_empty(),
332+
ErrorCode::FreeTaskAccountNotEmpty
333+
);
303334

304335
let seeds = [b"task", task_queue_key.as_ref(), &task_id.to_le_bytes()];
305336
let (key, bump_seed) = Pubkey::find_program_address(&seeds, self.ctx.program_id);
@@ -324,10 +355,10 @@ impl<'a, 'info> TaskProcessor<'a, 'info> {
324355
let task_size = task_data.try_to_vec()?.len() + 8 + 60;
325356
let rent_lamports = Rent::get()?.minimum_balance(task_size);
326357
let lamports = rent_lamports + task_data.crank_reward;
327-
task_data.rent_amount = lamports;
358+
task_data.rent_amount = rent_lamports;
328359

329360
let task_queue_info = self.ctx.accounts.task_queue.to_account_info();
330-
let task_queue_min_lamports = Rent::get()?.minimum_balance(task_queue_info.data_len() + 60);
361+
let task_queue_min_lamports = Rent::get()?.minimum_balance(task_queue_info.data_len());
331362

332363
require_gt!(
333364
task_queue_info.lamports(),
@@ -349,8 +380,13 @@ impl<'a, 'info> TaskProcessor<'a, 'info> {
349380
free_task_account.realloc(task_size, false)?;
350381

351382
let task_info = self.ctx.accounts.task.to_account_info();
352-
let task_remaining_lamports = self.ctx.accounts.task.to_account_info().lamports()
353-
- self.ctx.accounts.task.crank_reward;
383+
let task_remaining_lamports = self
384+
.ctx
385+
.accounts
386+
.task
387+
.to_account_info()
388+
.lamports()
389+
.saturating_sub(self.ctx.accounts.task.crank_reward);
354390
let lamports_from_task = task_remaining_lamports.min(lamports);
355391
let lamports_needed_from_queue = lamports.saturating_sub(lamports_from_task);
356392

@@ -379,13 +415,23 @@ pub fn handler<'info>(
379415
TriggerV0::Timestamp(timestamp) => timestamp,
380416
};
381417
ctx.accounts.task_queue.updated_at = now;
418+
// Check for duplicate task IDs
419+
let mut seen_ids = std::collections::HashSet::new();
382420
for id in args.free_task_ids.clone() {
383421
require_gt!(
384422
ctx.accounts.task_queue.capacity,
385423
id,
386424
ErrorCode::InvalidTaskId
387425
);
426+
// Ensure ID is not already in use in the task queue
427+
require!(
428+
!ctx.accounts.task_queue.task_exists(id),
429+
ErrorCode::TaskIdAlreadyInUse
430+
);
431+
// Check for duplicates in provided IDs
432+
require!(seen_ids.insert(id), ErrorCode::DuplicateTaskIds);
388433
}
434+
389435
let remaining_accounts = ctx.remaining_accounts;
390436

391437
let transaction = match ctx.accounts.task.transaction.clone() {
@@ -399,6 +445,11 @@ pub fn handler<'info>(
399445
)?;
400446
let data = utils::ed25519::verify_ed25519_ix(&ix, signer.to_bytes().as_slice())?;
401447
let mut remote_tx = RemoteTaskTransactionV0::try_deserialize(&mut &data[..])?;
448+
require_eq!(
449+
remote_tx.transaction.accounts.len(),
450+
0,
451+
ErrorCode::MalformedRemoteTransaction
452+
);
402453

403454
let num_accounts = remote_tx
404455
.transaction
@@ -431,7 +482,9 @@ pub fn handler<'info>(
431482
+ remote_tx.transaction.num_rw_signers;
432483
// The rent refund account may make an account that shouldn't be writable appear writable
433484
if i >= writable_end_idx as usize
434-
&& *acc.key == ctx.accounts.rent_refund.key()
485+
&& (*acc.key == ctx.accounts.rent_refund.key()
486+
|| *acc.key == ctx.accounts.task_queue.key()
487+
|| *acc.key == ctx.accounts.task.key())
435488
{
436489
data.push(0);
437490
} else {
@@ -470,22 +523,27 @@ pub fn handler<'info>(
470523
.task_queue
471524
.set_task_exists(ctx.accounts.task.id, false);
472525

473-
let free_tasks = ctx.accounts.task.free_tasks;
474-
475-
// Validate that all free task accounts are empty
526+
// Validate that all free task accounts are empty and are valid PDAs
476527
let free_tasks_start_index = transaction.accounts.len();
477-
for i in 0..free_tasks {
478-
let free_task_index = free_tasks_start_index + i as usize;
479-
let free_task_account = &remaining_accounts[free_task_index];
480-
require!(
481-
free_task_account.data_is_empty(),
482-
ErrorCode::FreeTaskAccountNotEmpty
483-
);
484-
}
528+
// Validate number of free task accounts matches number of task IDs
529+
require_eq!(
530+
args.free_task_ids.len(),
531+
ctx.remaining_accounts.len() - free_tasks_start_index,
532+
ErrorCode::MismatchedFreeTaskCounts
533+
);
485534

486535
if now.saturating_sub(task_time) <= ctx.accounts.task_queue.stale_task_age as i64 {
487536
let mut processor = TaskProcessor::new(ctx, &transaction, args.free_task_ids)?;
488537

538+
// Validate account keys match
539+
for (i, account) in transaction.accounts.iter().enumerate() {
540+
require_eq!(
541+
account,
542+
remaining_accounts[i].key,
543+
ErrorCode::InvalidAccountKey
544+
);
545+
}
546+
489547
// Process each instruction
490548
for ix in &transaction.instructions {
491549
processor.process_instruction(ix, remaining_accounts)?;

0 commit comments

Comments
 (0)