-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
core: bail if encounter insecure ssl cert, to avoid hanging forever #6300
Changes from 1 commit
104e399
2eafa44
8790ebd
089147a
b2d7c42
e7c463d
25a9853
9f14eff
3a3015c
bb2ce13
ef5ac10
f3bb0bd
157e84a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -849,6 +849,24 @@ class Driver { | |
}); | ||
} | ||
|
||
/** | ||
* @param {number} [timeout] | ||
* @return {Promise<LH.Crdp.Security.SecurityStateChangedEvent>} | ||
*/ | ||
getSecurityState(timeout = 1000) { | ||
return new Promise((resolve, reject) => { | ||
const err = new LHError(LHError.errors.SECURITY_STATE_TIMEOUT); | ||
const asyncTimeout = setTimeout((_ => reject(err)), timeout); | ||
|
||
this.sendCommand('Security.enable'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also, @paulirish is Security domain still broken on Android, is this going to break us on real devices again? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well someone's behind the times 😆 |
||
this.once('Security.securityStateChanged', (e) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. somewhat tangential... @paulirish are we just paranoid for doing all our listeners before enabling or do we actually need to? I seem to remember dgozman scolding me for not trusting in JS microtasks when we had listeners before There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm good call. This code here would be a problem if there was an So yeah +1 to defining the once handler before we flip enable() on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point, fixed. I don't quite understand the execution model here. It's clear to me why (note, this is literally all I know about micro/macrotasks: https://stackoverflow.com/a/25933985 ) |
||
clearTimeout(asyncTimeout); | ||
resolve(e); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. e => state ? |
||
this.sendCommand('Security.disable').catch(reject); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need this catch(). The timeout one is fine. |
||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* @param {string} name The name of API whose permission you wish to query | ||
* @return {Promise<string>} The state of permissions, resolved in a promise. | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -146,11 +146,12 @@ class GatherRunner { | |||||
|
||||||
/** | ||||||
* Returns an error if the original network request failed or wasn't found. | ||||||
* @param {Driver} driver | ||||||
* @param {string} url The URL of the original requested page. | ||||||
* @param {Array<LH.Artifacts.NetworkRequest>} networkRecords | ||||||
* @return {LHError|undefined} | ||||||
* @return {Promise<LHError|undefined>} | ||||||
*/ | ||||||
static getPageLoadError(url, networkRecords) { | ||||||
static async getPageLoadError(driver, url, networkRecords) { | ||||||
const mainRecord = networkRecords.find(record => { | ||||||
// record.url is actual request url, so needs to be compared without any URL fragment. | ||||||
return URL.equalWithExcludedFragments(record.url, url); | ||||||
|
@@ -165,6 +166,20 @@ class GatherRunner { | |||||
} else if (mainRecord.hasErrorStatusCode()) { | ||||||
errorDef = {...LHError.errors.ERRORED_DOCUMENT_REQUEST}; | ||||||
errorDef.message += ` Status code: ${mainRecord.statusCode}.`; | ||||||
} else if (!mainRecord.finished) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK, an insecure security state will always have |
||||||
// could be security error | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this may be just certificate errors. Seems like most of the connection errors are |
||||||
const securityState = await driver.getSecurityState(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small nit but I think it'd be slightly better to collect this on L288 and pass that into getPageLoadError() rather than all of driver. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. super nit: WDYT about sprinkling some nice destructuring here, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't even notice that :O |
||||||
if (securityState.securityState === 'insecure') { | ||||||
errorDef = {...LHError.errors.INSECURE_DOCUMENT_REQUEST}; | ||||||
const insecureDescriptions = securityState.explanations | ||||||
.filter(exp => exp.securityState === 'insecure') | ||||||
.map(exp => exp.description) | ||||||
.join(' '); | ||||||
errorDef.message += ` Insecure: ${insecureDescriptions}`; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree the explanation gathering is the best we can do.
Suggested change
|
||||||
} else { | ||||||
// Not sure what the error is. Could be just a redirect. For now, | ||||||
// treat as no error. | ||||||
} | ||||||
} | ||||||
|
||||||
if (errorDef) { | ||||||
|
@@ -251,7 +266,7 @@ class GatherRunner { | |||||
* object containing trace and network data. | ||||||
* @param {LH.Gatherer.PassContext} passContext | ||||||
* @param {Partial<GathererResults>} gathererResults | ||||||
* @return {Promise<LH.Gatherer.LoadData>} | ||||||
* @return {Promise<[LH.Gatherer.LoadData, LH.LighthouseError | undefined]>} | ||||||
*/ | ||||||
static async afterPass(passContext, gathererResults) { | ||||||
const driver = passContext.driver; | ||||||
|
@@ -271,7 +286,8 @@ class GatherRunner { | |||||
const networkRecords = NetworkRecorder.recordsFromLogs(devtoolsLog); | ||||||
log.verbose('statusEnd', status); | ||||||
|
||||||
let pageLoadError = GatherRunner.getPageLoadError(passContext.url, networkRecords); | ||||||
let pageLoadError = await GatherRunner.getPageLoadError(driver, | ||||||
passContext.url, networkRecords); | ||||||
// If the driver was offline, a page load error is expected, so do not save it. | ||||||
if (!driver.online) pageLoadError = undefined; | ||||||
|
||||||
|
@@ -288,8 +304,12 @@ class GatherRunner { | |||||
trace, | ||||||
}; | ||||||
|
||||||
// Disable throttling so the afterPass analysis isn't throttled | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. restore? |
||||||
await driver.setThrottling(passContext.settings, {useThrottling: false}); | ||||||
if (!pageLoadError) { | ||||||
// Disable throttling so the afterPass analysis isn't throttled | ||||||
// This will hang if there was a security error. But, there is no | ||||||
// need to throttle if there is such an error. See #6287 | ||||||
await driver.setThrottling(passContext.settings, {useThrottling: false}); | ||||||
} | ||||||
|
||||||
for (const gathererDefn of gatherers) { | ||||||
const gatherer = gathererDefn.instance; | ||||||
|
@@ -313,7 +333,7 @@ class GatherRunner { | |||||
} | ||||||
|
||||||
// Resolve on tracing data using passName from config. | ||||||
return passData; | ||||||
return [passData, pageLoadError]; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WDYT about attaching it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. I'll add it as an optional property. I avoided it in the first place b/c I didn't want to modify all usages of this type, but I completely forgot about optional properties :P |
||||||
} | ||||||
|
||||||
/** | ||||||
|
@@ -410,7 +430,8 @@ class GatherRunner { | |||||
await driver.setThrottling(options.settings, passConfig); | ||||||
await GatherRunner.beforePass(passContext, gathererResults); | ||||||
await GatherRunner.pass(passContext, gathererResults); | ||||||
const passData = await GatherRunner.afterPass(passContext, gathererResults); | ||||||
const [passData, pageLoadError] = | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @brendankenny @patrickhulce either of you have an idea on how else to deliver this error? see also the L462 here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
await GatherRunner.afterPass(passContext, gathererResults); | ||||||
|
||||||
// Save devtoolsLog, but networkRecords are discarded and not added onto artifacts. | ||||||
baseArtifacts.devtoolsLogs[passConfig.passName] = passData.devtoolsLog; | ||||||
|
@@ -435,6 +456,11 @@ class GatherRunner { | |||||
baseArtifacts.URL.finalUrl = passContext.url; | ||||||
firstPass = false; | ||||||
} | ||||||
|
||||||
if (pageLoadError && pageLoadError.code === LHError.errors.INSECURE_DOCUMENT_REQUEST.code) { | ||||||
// Some protocol commands will hang, so let's just bail. See #6287 | ||||||
break; | ||||||
} | ||||||
} | ||||||
const resetStorage = !options.settings.disableStorageReset; | ||||||
if (resetStorage) await driver.clearDataForOrigin(options.requestedUrl); | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -142,6 +142,12 @@ const ERRORS = { | |
message: strings.pageLoadFailed, | ||
lhrRuntimeError: true, | ||
}, | ||
/* Used when security error prevents page load. */ | ||
INSECURE_DOCUMENT_REQUEST: { | ||
code: 'INSECURE_DOCUMENT_REQUEST', | ||
message: strings.pageLoadFailedInsecure, | ||
lhrRuntimeError: true, | ||
}, | ||
|
||
// Protocol internal failures | ||
TRACING_ALREADY_STARTED: { | ||
|
@@ -169,6 +175,12 @@ const ERRORS = { | |
message: strings.requestContentTimeout, | ||
}, | ||
|
||
// Protocol timeout failures | ||
SECURITY_STATE_TIMEOUT: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the usual case for the security state check, it should just reject on an insecure security state, right? If so, we probably want to make this a more general protocol communication timeout error (anticipating #6296) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My plan was to modify that bit in the referenced issue. but now that we have this concrete proto definition it makes sense to make it good sooner rather than later There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I actually removed the timeout stuff for security checking. I'll remove this too. |
||
code: 'SECURITY_STATE_TIMEOUT', | ||
message: strings.securityStateTimeout, | ||
}, | ||
|
||
// URL parsing failures | ||
INVALID_URL: { | ||
code: 'INVALID_URL', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,9 @@ module.exports = { | |
badTraceRecording: `Something went wrong with recording the trace over your page load. Please run Lighthouse again.`, | ||
pageLoadTookTooLong: `Your page took too long to load. Please follow the opportunities in the report to reduce your page load time, and then try re-running Lighthouse.`, | ||
pageLoadFailed: `Lighthouse was unable to reliably load the page you requested. Make sure you are testing the correct URL and that the server is properly responding to all requests.`, | ||
pageLoadFailedInsecure: `The URL you have provided does not have a valid security certificate.`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm technically not just certificate, but admittedly most other security errors fall through the ERRORED_DOC_REQ path right now. how about
|
||
internalChromeError: `An internal Chrome error occurred. Please restart Chrome and try re-running Lighthouse.`, | ||
requestContentTimeout: 'Fetching resource content has exceeded the allotted time', | ||
securityStateTimeout: 'Fetching security state has exceeded the allotted time', | ||
urlInvalid: `The URL you have provided appears to be invalid.`, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will eventually be replaced by #6296