Skip to content

Commit 2604539

Browse files
authored
fix: sanitize event observer strings (#2361)
* chore: test * fix: manual event server * fix: tests * fix: remove extras * fix: extra import * fix: test
1 parent c6de314 commit 2604539

File tree

4 files changed

+118
-10
lines changed

4 files changed

+118
-10
lines changed

src/datastore/helpers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,10 @@ export function newReOrgUpdatedEntities(): ReOrgUpdatedEntities {
13631363
};
13641364
}
13651365

1366+
export function removeNullBytes(str: string): string {
1367+
return str.replace(/\x00/g, '');
1368+
}
1369+
13661370
/**
13671371
* Priority queue for parallel Postgres write query execution. This helps performance because it
13681372
* parallelizes the work postgres.js has to do when serializing JS types to PG types.

src/datastore/pg-write-store.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import {
7878
validateZonefileHash,
7979
newReOrgUpdatedEntities,
8080
PgWriteQueue,
81+
removeNullBytes,
8182
} from './helpers';
8283
import { PgNotifier } from './pg-notifier';
8384
import { MIGRATIONS_DIR, PgStore } from './pg-store';
@@ -203,6 +204,13 @@ export class PgWriteStore extends PgStore {
203204
}
204205

205206
async storeRawEventRequest(eventPath: string, payload: any): Promise<void> {
207+
if (eventPath === '/new_block' && typeof payload === 'object') {
208+
for (const tx of payload.transactions) {
209+
if ('vm_error' in tx && tx.vm_error) {
210+
tx.vm_error = removeNullBytes(tx.vm_error);
211+
}
212+
}
213+
}
206214
await this.sqlWriteTransaction(async sql => {
207215
const insertResult = await sql<
208216
{
@@ -2028,7 +2036,9 @@ export class PgWriteStore extends PgStore {
20282036
token_transfer_memo: tx.token_transfer_memo ?? null,
20292037
smart_contract_clarity_version: tx.smart_contract_clarity_version ?? null,
20302038
smart_contract_contract_id: tx.smart_contract_contract_id ?? null,
2031-
smart_contract_source_code: tx.smart_contract_source_code ?? null,
2039+
smart_contract_source_code: tx.smart_contract_source_code
2040+
? removeNullBytes(tx.smart_contract_source_code)
2041+
: null,
20322042
contract_call_contract_id: tx.contract_call_contract_id ?? null,
20332043
contract_call_function_name: tx.contract_call_function_name ?? null,
20342044
contract_call_function_args: tx.contract_call_function_args ?? null,
@@ -2051,7 +2061,7 @@ export class PgWriteStore extends PgStore {
20512061
execution_cost_runtime: tx.execution_cost_runtime,
20522062
execution_cost_write_count: tx.execution_cost_write_count,
20532063
execution_cost_write_length: tx.execution_cost_write_length,
2054-
vm_error: tx.vm_error ?? null,
2064+
vm_error: tx.vm_error ? removeNullBytes(tx.vm_error) : null,
20552065
}));
20562066

20572067
let count = 0;

tests/api/parse-db-tx.test.ts

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,77 @@
1+
import { migrate } from '../utils/test-helpers';
2+
import { importEventsFromTsv } from '../../src/event-replay/event-replay';
13
import { decodeClarityValueList } from 'stacks-encoding-native-js';
4+
import * as fs from 'fs';
5+
import * as readline from 'readline';
6+
import { PgSqlClient, timeout } from '@hirosystems/api-toolkit';
7+
import { ChainID } from '@stacks/common';
8+
import { ApiServer, startApiServer } from '../../src/api/init';
9+
import { PgWriteStore } from '../../src/datastore/pg-write-store';
10+
import { EventStreamServer, startEventServer } from '../../src/event-stream/event-server';
11+
import { httpPostRequest } from '../../src/helpers';
212

3-
test('buggy parsing of contract-call args', () => {
4-
// Contract-call args from tx 0xb066874942e97d6e7ecfedb999a788edf7fbdbe51ab4b172ea05e8ede9b0ae9c
5-
const contractCallArgsWithEmptyList =
6-
'000000030c00000007066865696768740100000000000000000000000000005cc20b6d65726b6c652d726f6f7402000000201302a1b1fa53c11ac01ce480937dcd1ef550b4799070878e20b3b97bb99b58ec056e62697473020000000424961417056e6f6e6365020000000434a32a2106706172656e740200000020d0c9ebf9784f0670eb9bfac963114a02cff58bdc2669000000000000000000000974696d657374616d70020000000452b003610776657273696f6e0200000004040000200c0000000403696e730b000000010c00000003086f7574706f696e740c0000000204686173680200000020e85fb1e809db705121d92f3ec7f9ea4bafd065a8651408f507223efa8ede146605696e64657802000000040300000009736372697074536967020000006b48304502210096638a2336c383a99b009025534ae00f0bb689888c3898e6723497324c92806802205a3eee656775961780676f48f2fc1514142cadcd49efe1893d75517ab47a7f620121022a0160b2ed13b803ddca6f6f04606f56dfadee571d683725c7a58d0ed199de790873657175656e63650200000004fdffffff086c6f636b74696d65020000000400000000046f7574730b000000000776657273696f6e0200000004010000000c00000003066861736865730b000000000a747265652d646570746801000000000000000000000000000000080874782d696e64657801ffffffffffffffffffffffffffffffff';
7-
const fnArgs = Buffer.from(contractCallArgsWithEmptyList, 'hex');
8-
const clarityVals = decodeClarityValueList(fnArgs);
9-
clarityVals.map(c => {
10-
expect(c.hex).not.toBe('');
13+
describe('transaction parsing', () => {
14+
test('buggy parsing of contract-call args', () => {
15+
// Contract-call args from tx 0xb066874942e97d6e7ecfedb999a788edf7fbdbe51ab4b172ea05e8ede9b0ae9c
16+
const contractCallArgsWithEmptyList =
17+
'000000030c00000007066865696768740100000000000000000000000000005cc20b6d65726b6c652d726f6f7402000000201302a1b1fa53c11ac01ce480937dcd1ef550b4799070878e20b3b97bb99b58ec056e62697473020000000424961417056e6f6e6365020000000434a32a2106706172656e740200000020d0c9ebf9784f0670eb9bfac963114a02cff58bdc2669000000000000000000000974696d657374616d70020000000452b003610776657273696f6e0200000004040000200c0000000403696e730b000000010c00000003086f7574706f696e740c0000000204686173680200000020e85fb1e809db705121d92f3ec7f9ea4bafd065a8651408f507223efa8ede146605696e64657802000000040300000009736372697074536967020000006b48304502210096638a2336c383a99b009025534ae00f0bb689888c3898e6723497324c92806802205a3eee656775961780676f48f2fc1514142cadcd49efe1893d75517ab47a7f620121022a0160b2ed13b803ddca6f6f04606f56dfadee571d683725c7a58d0ed199de790873657175656e63650200000004fdffffff086c6f636b74696d65020000000400000000046f7574730b000000000776657273696f6e0200000004010000000c00000003066861736865730b000000000a747265652d646570746801000000000000000000000000000000080874782d696e64657801ffffffffffffffffffffffffffffffff';
18+
const fnArgs = Buffer.from(contractCallArgsWithEmptyList, 'hex');
19+
const clarityVals = decodeClarityValueList(fnArgs);
20+
clarityVals.map(c => {
21+
expect(c.hex).not.toBe('');
22+
});
23+
});
24+
25+
describe('fuzzed tsvs', () => {
26+
let db: PgWriteStore;
27+
let client: PgSqlClient;
28+
let api: ApiServer;
29+
30+
beforeEach(async () => {
31+
await migrate('up');
32+
db = await PgWriteStore.connect({
33+
usageName: 'tests',
34+
withNotifier: true,
35+
skipMigrations: true,
36+
});
37+
client = db.sql;
38+
api = await startApiServer({ datastore: db, chainId: ChainID.Testnet });
39+
40+
// set chainId env, because TSV import reads it manually
41+
process.env['STACKS_CHAIN_ID'] = ChainID.Testnet.toString();
42+
});
43+
44+
afterEach(async () => {
45+
await api.terminate();
46+
await db?.close();
47+
await migrate('down');
48+
});
49+
50+
test('parse fuzzed transactions', async () => {
51+
const eventServer = await startEventServer({
52+
datastore: db,
53+
chainId: ChainID.Testnet,
54+
serverHost: '127.0.0.1',
55+
serverPort: 0,
56+
});
57+
58+
const readStream = readline.createInterface({
59+
input: fs.createReadStream('tests/api/tsv/fuzzed-transactions-1.tsv', { encoding: 'utf8' }),
60+
crlfDelay: Infinity,
61+
});
62+
for await (const line of readStream) {
63+
const [id, timestamp, eventPath, payload] = line.split('\t');
64+
await httpPostRequest({
65+
host: '127.0.0.1',
66+
port: eventServer.serverAddress.port,
67+
path: eventPath,
68+
headers: { 'Content-Type': 'application/json' },
69+
body: Buffer.from(payload, 'utf8'),
70+
throwOnNotOK: true,
71+
});
72+
}
73+
74+
await eventServer.closeAsync();
75+
});
1176
});
1277
});

0 commit comments

Comments
 (0)