Skip to content

Commit c405ff6

Browse files
committed
Fixed use-after-free style overwrite in SingleBuffer
Fixed incorrect usage of `Atomics.wait`
1 parent aace1ed commit c405ff6

File tree

1 file changed

+38
-57
lines changed

1 file changed

+38
-57
lines changed

src/backends/single_buffer.ts

Lines changed: 38 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,17 @@ import { withErrno } from 'kerium';
22
import { alert, crit, err, warn } from 'kerium/log';
33
import { field, offsetof, packed, sizeof, struct, types as t, type StructArray } from 'memium';
44
import type { UUID } from 'node:crypto';
5-
import { randomInt } from 'utilium';
65
import { BufferView } from 'utilium/buffer.js';
76
import { crc32c } from 'utilium/checksum.js';
87
import { decodeUUID, encodeUUID } from 'utilium/string.js';
98
import type { UsageInfo } from '../internal/filesystem.js';
109
import { _inode_version, Inode } from '../internal/inode.js';
11-
import { size_max } from '../vfs/constants.js';
1210
import type { Backend } from './backend.js';
1311
import { StoreFS } from './store/fs.js';
1412
import { SyncMapTransaction, type SyncMapStore } from './store/map.js';
1513
import type { Store } from './store/store.js';
1614

17-
interface Lock extends Disposable {
18-
nonce: number;
19-
release(): void;
20-
}
15+
type Lock = Disposable & (() => void);
2116

2217
// eslint-disable-next-line @typescript-eslint/unbound-method
2318
const { format } = new Intl.NumberFormat('en-US', {
@@ -41,7 +36,7 @@ class MetadataEntry extends BufferView {
4136
/** The size of the data */
4237
@t.uint32 accessor size!: number;
4338

44-
toString(long: boolean = false) {
39+
public toString(long: boolean = false) {
4540
if (!long) return `<MetadataEntry @ 0x${this.byteOffset.toString(16).padStart(8, '0')}>`;
4641

4742
return `0x${this.id.toString(16).padStart(8, '0')}: ${format(this.size).padStart(5)} at 0x${this.offset.toString(16).padStart(8, '0')}`;
@@ -126,39 +121,34 @@ export class MetadataBlock extends Int32Array<ArrayBufferLike> {
126121
if (depth > max_lock_attempts)
127122
throw crit(withErrno('EBUSY', `sbfs: exceeded max attempts waiting for metadata block at ${this.offsetHex} to be unlocked`));
128123

129-
const i = offsetof(this, 'locked');
130-
if (Atomics.load(this, i) !== 0) {
131-
switch (Atomics.wait(this, i, 0)) {
132-
case 'ok':
133-
break;
134-
case 'not-equal':
135-
depth++;
136-
err(`sbfs: waiting for metadata block at ${this.offsetHex} to be unlocked (${depth}/${max_lock_attempts})`);
137-
return this.waitUnlocked(depth);
138-
case 'timed-out':
139-
throw crit(withErrno('EBUSY', `sbfs: timed out waiting for metadata block at ${this.offsetHex} to be unlocked`));
140-
}
124+
const i = this.length - 1;
125+
if (!Atomics.load(this, i)) return;
126+
switch (Atomics.wait(this, i, 1)) {
127+
case 'ok':
128+
break;
129+
case 'not-equal':
130+
depth++;
131+
err(`sbfs: waiting for metadata block at ${this.offsetHex} to be unlocked (${depth}/${max_lock_attempts})`);
132+
return this.waitUnlocked(depth);
133+
case 'timed-out':
134+
throw crit(withErrno('EBUSY', `sbfs: timed out waiting for metadata block at ${this.offsetHex} to be unlocked`));
141135
}
142136
}
143137

144-
public lock(nonce: number = randomInt(1, size_max / 2)): Lock {
138+
public lock(): Lock {
145139
this.waitUnlocked();
146140

147141
const i = offsetof(this, 'locked');
148-
Atomics.store(this, i, nonce);
142+
Atomics.store(this, i, 1);
149143

150144
const release = () => {
151145
Atomics.store(this, i, 0);
152146
Atomics.notify(this, i, 1);
153147
};
154148

155-
return {
156-
nonce,
157-
release,
158-
[Symbol.dispose]() {
159-
release();
160-
},
161-
};
149+
release[Symbol.dispose] = release;
150+
151+
return release;
162152
}
163153
}
164154

@@ -193,11 +183,11 @@ export class SuperBlock extends BufferView {
193183
return;
194184
}
195185

196-
if (!checksumMatches(this)) throw crit(withErrno('EIO', 'sbfs: checksum mismatch for super block'));
186+
if (this.checksum !== checksum(this)) throw crit(withErrno('EIO', 'sbfs: checksum mismatch for super block'));
197187

198188
this.metadata = new MetadataBlock(this.buffer, this.metadata_offset);
199189

200-
if (!checksumMatches(this.metadata))
190+
if (this.metadata.checksum !== checksum(this.metadata))
201191
throw crit(
202192
withErrno(
203193
'EIO',
@@ -310,16 +300,11 @@ export class SuperBlock extends BufferView {
310300
* Note we don't include the checksum when computing a new one.
311301
*/
312302
function checksum(value: SuperBlock | MetadataBlock): number {
313-
const buffer = new Uint8Array(value.buffer, value.byteOffset + 4, sizeof(value) - 4);
314-
return crc32c(buffer);
315-
}
316-
317-
function checksumMatches(value: SuperBlock | MetadataBlock): boolean {
318-
return value.checksum === checksum(value);
303+
return crc32c(new Uint8Array(value.buffer, value.byteOffset + 4, sizeof(value) - 4));
319304
}
320305

321306
/**
322-
* Update a block's checksum and write it to the store's buffer.
307+
* Update a block's checksum and timestamp.
323308
* @internal @hidden
324309
*/
325310
function _update(value: SuperBlock | MetadataBlock): void {
@@ -360,22 +345,25 @@ export class SingleBufferStore extends BufferView implements SyncMapStore {
360345
this.superblock = new SuperBlock(this.buffer, this.byteOffset);
361346
}
362347

363-
public keys(): Iterable<number> {
348+
public *keys(): Iterable<number> {
364349
const keys = new Set<number>();
365350
for (let block: MetadataBlock | undefined = this.superblock.metadata; block; block = block.previous) {
366351
block.waitUnlocked();
367-
for (const entry of block.items) if (entry.offset) keys.add(entry.id);
352+
for (const entry of block.items) {
353+
if (!entry.offset || keys.has(entry.id)) continue;
354+
keys.add(entry.id);
355+
yield entry.id;
356+
}
368357
}
369-
return keys;
370358
}
371359

372360
public get(id: number): Uint8Array | undefined {
373361
for (let block: MetadataBlock | undefined = this.superblock.metadata; block; block = block.previous) {
374362
block.waitUnlocked();
375363
for (const entry of block.items) {
376-
if (entry.offset && entry.id == id) {
377-
return new Uint8Array(this.buffer, entry.offset, entry.size);
378-
}
364+
if (!entry.offset || entry.id != id) continue;
365+
const off = this.byteOffset + entry.offset;
366+
return new Uint8Array(this.buffer.slice(off, off + entry.size));
379367
}
380368
}
381369
}
@@ -390,29 +378,21 @@ export class SingleBufferStore extends BufferView implements SyncMapStore {
390378

391379
using lock = block.lock();
392380

393-
const _write = () => {
394-
for (let i = 0; i < data.length; i++) this._u8[entry.offset + i] = data[i];
395-
};
396-
397-
if (data.length <= entry.size) {
398-
_write();
399-
if (data.length < entry.size) {
400-
entry.size = data.length;
401-
_update(block);
402-
}
381+
if (data.length == entry.size) {
382+
this._u8.set(data, entry.offset);
403383
return;
404384
}
405385

406-
if (this.superblock.isUnused(entry.offset, data.length)) {
386+
if (data.length < entry.size || this.superblock.isUnused(entry.offset, data.length)) {
387+
this._u8.set(data, entry.offset);
407388
entry.size = data.length;
408-
_write();
409389
_update(block);
410390
return;
411391
}
412392

413393
entry.offset = Number(this.superblock.used_bytes);
414394
entry.size = data.length;
415-
_write();
395+
this._u8.set(data, entry.offset);
416396
_update(block);
417397
this.superblock.used_bytes += BigInt(data.length);
418398
_update(this.superblock);
@@ -435,7 +415,7 @@ export class SingleBufferStore extends BufferView implements SyncMapStore {
435415
entry.offset = offset;
436416
entry.size = data.length;
437417

438-
for (let i = 0; i < data.length; i++) this._u8[offset + i] = data[i];
418+
this._u8.set(data, offset);
439419

440420
this.superblock.used_bytes += BigInt(data.length);
441421
_update(this.superblock.metadata);
@@ -449,6 +429,7 @@ export class SingleBufferStore extends BufferView implements SyncMapStore {
449429
if (entry.id != id) continue;
450430
entry.offset = 0;
451431
entry.size = 0;
432+
entry.id = 0;
452433
_update(block);
453434
return;
454435
}

0 commit comments

Comments
 (0)