fix: add column, line, and method check to integrity check (#25094)

This commit is contained in:
Ryan Manuel
2022-12-12 14:42:46 -06:00
committed by GitHub
parent 2062670f5c
commit 8888cd9e21
8 changed files with 195 additions and 13 deletions

View File

@@ -6,7 +6,6 @@ const esbuild = require('esbuild')
const snapshotMetadata = require('@tooling/v8-snapshot/cache/prod-darwin/snapshot-meta.cache.json')
const tempDir = require('temp-dir')
const workingDir = path.join(tempDir, 'binary-cleanup-workdir')
const bytenode = require('bytenode')
fs.ensureDirSync(workingDir)
@@ -137,6 +136,8 @@ const createServerEntryPointBundle = async (buildAppDir) => {
console.log(`compiling server entry point bundle to ${path.join(buildAppDir, 'packages', 'server', 'index.jsc')}`)
// Use bytenode to compile the entry point bundle. This will save time on the v8 compile step and ensure the integrity of the entry point
const bytenode = await import('bytenode')
await bytenode.compileFile({
filename: path.join(buildAppDir, 'packages', 'server', 'index.js'),
output: path.join(buildAppDir, 'packages', 'server', 'index.jsc'),

View File

@@ -1,13 +1,13 @@
const Module = require('module')
const path = require('path')
import Module from 'module'
import path from 'path'
import { runBytecodeAsModule } from 'bytenode'
process.env.CYPRESS_INTERNAL_ENV = process.env.CYPRESS_INTERNAL_ENV || 'production'
try {
require('bytenode')
const filename = path.join(__dirname, 'packages', 'server', 'index.jsc')
const dirname = path.dirname(filename)
Module._extensions['.jsc']({
runBytecodeAsModule({
require: module.require,
id: filename,
filename,

View File

@@ -2,6 +2,8 @@ const OrigError = Error
const captureStackTrace = Error.captureStackTrace
const toString = Function.prototype.toString
const callFn = Function.call
const filter = Array.prototype.filter
const startsWith = String.prototype.startsWith
const integrityErrorMessage = `
We detected an issue with the integrity of the Cypress binary. It may have been modified and cannot run. We recommend re-installing the Cypress binary with:
@@ -22,7 +24,7 @@ const stackIntegrityCheck = function stackIntegrityCheck (options) {
const tempError = new OrigError
captureStackTrace(tempError, arguments.callee)
const stack = tempError.stack.filter((frame) => !frame.getFileName().startsWith('node:internal') && !frame.getFileName().startsWith('node:electron'))
const stack = filter.call(tempError.stack, (frame) => !startsWith.call(frame.getFileName(), 'node:internal') && !startsWith.call(frame.getFileName(), 'node:electron'))
OrigError.prepareStackTrace = originalPrepareStackTrace
OrigError.stackTraceLimit = originalStackTraceLimit
@@ -33,9 +35,11 @@ const stackIntegrityCheck = function stackIntegrityCheck (options) {
}
for (let index = 0; index < options.stackToMatch.length; index++) {
const { functionName: expectedFunctionName, fileName: expectedFileName } = options.stackToMatch[index]
const { functionName: expectedFunctionName, fileName: expectedFileName, line: expectedLineNumber, column: expectedColumnNumber } = options.stackToMatch[index]
const actualFunctionName = stack[index].getFunctionName()
const actualFileName = stack[index].getFileName()
const actualColumnNumber = stack[index].getColumnNumber()
const actualLineNumber = stack[index].getLineNumber()
if (expectedFunctionName && actualFunctionName !== expectedFunctionName) {
console.error(`Integrity check failed with expected function name ${expectedFunctionName} but got ${actualFunctionName}`)
@@ -46,13 +50,37 @@ const stackIntegrityCheck = function stackIntegrityCheck (options) {
console.error(`Integrity check failed with expected file name ${expectedFileName} but got ${actualFileName}`)
throw new Error(integrityErrorMessage)
}
if (expectedLineNumber && actualLineNumber !== expectedLineNumber) {
console.error(`Integrity check failed with expected line number ${expectedLineNumber} but got ${actualLineNumber}`)
throw new Error(integrityErrorMessage)
}
if (expectedColumnNumber && actualColumnNumber !== expectedColumnNumber) {
console.error(`Integrity check failed with expected column number ${expectedColumnNumber} but got ${actualColumnNumber}`)
throw new Error(integrityErrorMessage)
}
}
}
function validateStartsWith () {
if (startsWith.call !== callFn) {
console.error(`Integrity check failed for startsWith.call`)
throw new Error(integrityErrorMessage)
}
}
function validateFilter () {
if (filter.call !== callFn) {
console.error(`Integrity check failed for filter.call`)
throw new Error(integrityErrorMessage)
}
}
function validateToString () {
if (toString.call !== callFn) {
console.error(`Integrity check failed for toString.call`)
throw new Error('Integrity check failed for toString.call')
throw new Error(integrityErrorMessage)
}
}
@@ -60,7 +88,7 @@ function validateElectron (electron) {
// Hard coded function as this is electron code and there's not an easy way to get the function string at package time. If this fails on an updated version of electron, we'll need to update this.
if (toString.call(electron.app.getAppPath) !== 'function getAppPath() { [native code] }') {
console.error(`Integrity check failed for toString.call(electron.app.getAppPath)`)
throw new Error(`Integrity check failed for toString.call(electron.app.getAppPath)`)
throw new Error(integrityErrorMessage)
}
}
@@ -106,6 +134,8 @@ function integrityCheck (options) {
const crypto = require('crypto')
// 1. Validate that the native functions we are using haven't been tampered with
validateStartsWith()
validateFilter()
validateToString()
validateElectron(electron)
validateFs(fs)
@@ -143,13 +173,17 @@ function integrityCheck (options) {
fileName: 'evalmachine.<anonymous>',
},
{
functionName: 'Module2._extensions.<computed>',
functionName: 'v',
// eslint-disable-next-line no-undef
fileName: [appPath, 'index.js'].join(PATH_SEP),
line: 1,
column: 2573,
},
{
// eslint-disable-next-line no-undef
fileName: [appPath, 'index.js'].join(PATH_SEP),
line: 1,
column: 2764,
},
],
})

View File

@@ -17,6 +17,8 @@ const getBinaryEntryPointSource = async () => {
bundle: true,
platform: 'node',
write: false,
minify: true,
treeShaking: true,
})
return esbuildResult.outputFiles[0].text

View File

@@ -302,6 +302,24 @@ const runIntegrityTest = async function (buildAppExecutable, buildAppDir, e2e) {
const allowList = ['regeneratorRuntime', '__core-js_shared__', 'getSnapshotResult', 'supportTypeScript']
await testAlteringEntryPoint(`(${compareGlobals.toString()})()`, `extra keys in electron process: ${allowList}\nIntegrity check failed with expected stack length 9 but got 10`)
const testTemporarilyRewritingEntryPoint = async () => {
const file = path.join(buildAppDir, 'index.js')
const backupFile = path.join(buildAppDir, 'index.js.bak')
const contents = await fs.readFile(file)
// Backup state
await fs.move(file, backupFile)
// Modify app
await fs.writeFile(file, `console.log("rewritten code");const fs=require('fs');const { join } = require('path');fs.writeFileSync(join(__dirname,'index.js'),fs.readFileSync(join(__dirname,'index.js.bak')));${contents}`)
await runErroringProjectTest(buildAppExecutable, e2e, 'temporarily rewriting index.js', 'Integrity check failed with expected column number 2573 but got')
// Restore original state
await fs.move(backupFile, file, { overwrite: true })
}
await testTemporarilyRewritingEntryPoint()
}
const test = async function (buildAppExecutable, buildAppDir) {