Skip to content

Commit

Permalink
Limit decoded result imflation ratio from ABI-encoded data (ethers-io…
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo authored and Woodpile37 committed Jan 14, 2024
1 parent c8618df commit 2a17804
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 4 deletions.
9 changes: 7 additions & 2 deletions src.ts/abi/abi-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);


let defaultCoder: null | AbiCoder = null;

let defaultMaxInflation = 1024;

function getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike, abiCoder: AbiCoder): CallExceptionError {
let message = "missing revert data";
Expand Down Expand Up @@ -206,7 +206,12 @@ export class AbiCoder {
decode(types: ReadonlyArray<string | ParamType>, data: BytesLike, loose?: boolean): Result {
const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type)));
const coder = new TupleCoder(coders, "_");
return coder.decode(new Reader(data, loose));
return coder.decode(new Reader(data, loose, defaultMaxInflation));
}

static _setDefaultMaxInflation(value: number): void {
assertArgument(typeof(value) === "number" && Number.isInteger(value), "invalid defaultMaxInflation factor", "value", value);
defaultMaxInflation = value;
}

/**
Expand Down
29 changes: 27 additions & 2 deletions src.ts/abi/coders/abstract-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,17 @@ export class Reader {
readonly #data: Uint8Array;
#offset: number;

constructor(data: BytesLike, allowLoose?: boolean) {
#bytesRead: number;
#parent: null | Reader;
#maxInflation: number;

constructor(data: BytesLike, allowLoose?: boolean, maxInflation?: number) {
defineProperties<Reader>(this, { allowLoose: !!allowLoose });

this.#data = getBytesCopy(data);
this.#bytesRead = 0;
this.#parent = null;
this.#maxInflation = (maxInflation != null) ? maxInflation: 1024;

this.#offset = 0;
}
Expand All @@ -427,6 +434,21 @@ export class Reader {
get consumed(): number { return this.#offset; }
get bytes(): Uint8Array { return new Uint8Array(this.#data); }

#incrementBytesRead(count: number): void {
if (this.#parent) { return this.#parent.#incrementBytesRead(count); }

this.#bytesRead += count;

// Check for excessive inflation (see: #4537)
assert(this.#maxInflation < 1 || this.#bytesRead <= this.#maxInflation * this.dataLength, `compressed ABI data exceeds inflation ratio of ${ this.#maxInflation } ( see: https:/\/github.com/ethers-io/ethers.js/issues/4537 )`, "BUFFER_OVERRUN", {
buffer: getBytesCopy(this.#data), offset: this.#offset,
length: count, info: {
bytesRead: this.#bytesRead,
dataLength: this.dataLength
}
});
}

#peekBytes(offset: number, length: number, loose?: boolean): Uint8Array {
let alignedLength = Math.ceil(length / WordSize) * WordSize;
if (this.#offset + alignedLength > this.#data.length) {
Expand All @@ -445,12 +467,15 @@ export class Reader {

// Create a sub-reader with the same underlying data, but offset
subReader(offset: number): Reader {
return new Reader(this.#data.slice(this.#offset + offset), this.allowLoose);
const reader = new Reader(this.#data.slice(this.#offset + offset), this.allowLoose, this.#maxInflation);
reader.#parent = this;
return reader;
}

// Read bytes
readBytes(length: number, loose?: boolean): Uint8Array {
let bytes = this.#peekBytes(0, length, !!loose);
this.#incrementBytesRead(length);
this.#offset += bytes.length;
// @TODO: Make sure the length..end bytes are all 0?
return bytes.slice(0, length);
Expand Down

0 comments on commit 2a17804

Please sign in to comment.