From 9f7580533796f744b9c5e09a155ba145c009cae2 Mon Sep 17 00:00:00 2001 From: Niko Storni Date: Thu, 7 Aug 2025 18:39:02 +0200 Subject: [PATCH] Improves DataItem validation --- src/DataItem.ts | 13 +++++++++---- src/__tests__/fileTests.spec.ts | 10 +++++----- src/file/FileDataItem.ts | 13 +++++++++---- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/DataItem.ts b/src/DataItem.ts index 4c4ceb4..4fece1e 100644 --- a/src/DataItem.ts +++ b/src/DataItem.ts @@ -14,6 +14,7 @@ import type { Base64URLString } from "./types"; export const MIN_BINARY_SIZE = 80; export const MAX_TAG_BYTES = 4096; +export const MAX_THEORETICAL_TAGS_COUNT = 128; export class DataItem implements BundleItem { private readonly binary: Buffer; @@ -216,15 +217,19 @@ export class DataItem implements BundleItem { const numberOfTags = byteArrayToLong(buffer.subarray(tagsStart, tagsStart + 8)); const numberOfTagBytesArray = buffer.subarray(tagsStart + 8, tagsStart + 16); const numberOfTagBytes = byteArrayToLong(numberOfTagBytesArray); - - if (numberOfTagBytes > MAX_TAG_BYTES) return false; - + if (numberOfTagBytes > MAX_THEORETICAL_TAGS_COUNT * MAX_TAG_BYTES) { + return false; + } if (numberOfTags > 0) { try { const tags: { name: string; value: string }[] = deserializeTags( Buffer.from(buffer.subarray(tagsStart + 16, tagsStart + 16 + numberOfTagBytes)), ); - + for (const tag of tags) { + if (tag.name.length + tag.value.length > MAX_TAG_BYTES) { + return false; + } + } if (tags.length !== numberOfTags) { return false; } diff --git a/src/__tests__/fileTests.spec.ts b/src/__tests__/fileTests.spec.ts index cc37680..b60c440 100644 --- a/src/__tests__/fileTests.spec.ts +++ b/src/__tests__/fileTests.spec.ts @@ -1,5 +1,5 @@ -import { DataItem, ArweaveSigner } from "../../index"; -import { bundleAndSignData, createData, FileDataItem } from "../../src/file/index"; +import { DataItem, ArweaveSigner, MAX_TAG_BYTES, MAX_THEORETICAL_TAGS_COUNT } from "../../index"; +import { bundleAndSignData, createData, FileDataItem } from "../file"; import { readFileSync } from "fs"; import path from "path"; import * as fs from "fs"; @@ -186,7 +186,7 @@ describe("DataItem", () => { it("should return false", async () => { await dataItem.sign(signer); const tagStart = await dataItem.getTagsStart(); - const fakeTagLength = longTo8ByteArray(4096 + 1); + const fakeTagLength = longTo8ByteArray(MAX_TAG_BYTES * (MAX_THEORETICAL_TAGS_COUNT + 1)); const fakeTagCnt = longTo8ByteArray(10); const handle = fs.openSync(dataItem.filename, "r+"); @@ -281,12 +281,12 @@ describe("static methods", () => { expect(await FileDataItem.verify(dataItem.filename)).toEqual(false); }); }); - describe("given a invalid DataItem due to having too many tags", () => { + describe("given a invalid DataItem due to having too many full tags", () => { it("should return false", async () => { const dataItem = await createData("loremIpsum", signer); await dataItem.sign(signer); const tagStart = await dataItem.getTagsStart(); - const fakeTagLength = longTo8ByteArray(4096 + 1); + const fakeTagLength = longTo8ByteArray(MAX_TAG_BYTES * (MAX_THEORETICAL_TAGS_COUNT + 1)); const handle = fs.openSync(dataItem.filename, "r+"); fs.writeSync(handle, fakeTagLength, 0, 8, tagStart + 8); fs.closeSync(handle); diff --git a/src/file/FileDataItem.ts b/src/file/FileDataItem.ts index fdf1f9f..5307c8a 100644 --- a/src/file/FileDataItem.ts +++ b/src/file/FileDataItem.ts @@ -3,7 +3,7 @@ import { createReadStream, promises, read as FSRead, write as FSWrite } from "fs import type { PathLike } from "fs"; import { byteArrayToLong } from "../utils"; import type { BundleItem } from "../BundleItem"; -import { deepHash, MAX_TAG_BYTES } from "../index"; +import { deepHash, MAX_TAG_BYTES, MAX_THEORETICAL_TAGS_COUNT } from "../index"; import { getCryptoDriver, stringToBuffer } from "$/utils"; import type { Signer } from "../signing/index"; import { indexToType } from "../signing/index"; @@ -69,7 +69,7 @@ export class FileDataItem implements BundleItem { const numberOfTags = await read(handle.fd, Buffer.allocUnsafe(8), 0, 8, tagsStart).then((r) => byteArrayToLong(r.buffer)); const numberOfTagsBytes = await read(handle.fd, Buffer.allocUnsafe(8), 0, 8, tagsStart + 8).then((r) => byteArrayToLong(r.buffer)); - if (numberOfTagsBytes > MAX_TAG_BYTES) { + if (numberOfTagsBytes > MAX_THEORETICAL_TAGS_COUNT * MAX_TAG_BYTES) { await handle.close(); return false; } @@ -77,7 +77,12 @@ export class FileDataItem implements BundleItem { const tagsBytes = await read(handle.fd, Buffer.allocUnsafe(numberOfTagsBytes), 0, numberOfTagsBytes, tagsStart + 16).then((r) => r.buffer); if (numberOfTags > 0) { try { - deserializeTags(tagsBytes); + const tags = deserializeTags(tagsBytes); + for (const tag of tags) { + if (tag.name.length + tag.value.length > MAX_TAG_BYTES) { + return false; + } + } } catch (e) { await handle.close(); return false; @@ -201,7 +206,7 @@ export class FileDataItem implements BundleItem { } const numberOfTagsBytesBuffer = await read(handle.fd, Buffer.allocUnsafe(8), 0, 8, tagsStart + 8).then((r) => r.buffer); const numberOfTagsBytes = byteArrayToLong(numberOfTagsBytesBuffer); - if (numberOfTagsBytes > MAX_TAG_BYTES) { + if (numberOfTagsBytes > MAX_TAG_BYTES * MAX_THEORETICAL_TAGS_COUNT) { await handle.close(); throw new Error("Tags too large"); }