Agoric / agoric-sdk

monorepo for the Agoric Javascript smart contract platform
Apache License 2.0
327 stars 207 forks source link

Unrestricted number of vat Slots per vat #3243

Open andrey-kuprianov opened 3 years ago

andrey-kuprianov commented 3 years ago

Surfaced from @informalsystems audit of Agoric/agoric-sdk/SwingSet hash 9feea16732944e01749feedc7f6fb024af4fc70d

Notice: As agreed with Agoric, we performed the analysis under the assumption of possible bugs in the liveslots implementation. In that case, userspace vat code together with the liveslots layer operate as a whole against the kernel, which is the necessary condition for triggering this issue. Correctly functioning liveslots implementation should prevent this issue from being exploitable. We will perform assessment of liveslots correctness in the subsequent phases of our analysis.

The kernel allocates a lot of storage in kvStore per each unknown VAT Slot Id that it encounters in a syscall. E.g. the function translateSend:

const target = mapVatSlotToKernelSlot(targetSlot);
const argList = legibilizeMessageArgs(args).join(', ');
// prettier-ignore
kdebug(`syscall[${vatID}].send(${targetSlot}/${target}).${method}(${argList})`);
const kernelSlots = args.slots.map(slot => mapVatSlotToKernelSlot(slot));

The The code above calls mapVatSlotToKernelSlot, which, in turn, calls addKernelPromise:

function addKernelPromise(policy) {
  const kpidNum = Nat(BigInt(getRequired('kp.nextID')));
  kvStore.set('kp.nextID', `${kpidNum + 1n}`);
  const kpid = makeKernelSlot('promise', kpidNum);
  kvStore.set(`${kpid}.state`, 'unresolved');
  kvStore.set(`${kpid}.subscribers`, '');
  kvStore.set(`${kpid}.queue.nextID`, `0`);
  kvStore.set(`${kpid}.refCount`, `0`);
  kvStore.set(`${kpid}.decider`, '');
  if (policy && policy !== 'ignore') {
    kvStore.set(`${kpid}.policy`, policy);
  }
  // queue is empty, so no state[kp$NN.queue.$NN] keys yet
  incStat('kernelPromises');
  incStat('kpUnresolved');
  return kpid;
}

This allocates a lot of kernel storage as a result of a single mentioning of a VAT slot in a call from VAT to the kernel. Moreover, the number of VAT slots in the arguments of a syscall is unrestricted. This makes it possible to fill the kernel storage with promises, and their respective data.

Recommendation: Restrict the number of VAT slots per VAT.

warner commented 3 years ago

Hm. We could kill the vat when the c-list gets too large, or pause it somehow, or panic the kernel. This could be adversarial behavior, or a GC leak.