Skip to content

Reproducible Segfault in HTTP2 module #48567

@JoshuaWise

Description

@JoshuaWise

Version

v18.16.1

Platform

Darwin Kernel Version 19.6.0

Subsystem

none

What steps will reproduce the bug?

I can reliably reproduce a Segmentation fault by using the core http2 module.

Step 1: Acquire a TLS/SSL public+private key pair

I followed this guide in the Node.js documentation to create a self-signed certificate.

Step 2: Run this script to start an HTTP/2 server:

'use strict';
const fs = require('fs');
const http2 = require('http2');

(async function main() {
	const sockets = new Set();
	const sessions = new Set();
	const server = http2.createSecureServer({
		cert: fs.readFileSync('test-cert.pem'),
		key: fs.readFileSync('test-key.pem'),
	});

	server.on('request', (req, res) => {
		const message = 'hello world.';
		res.setHeader('Content-Length', String(Buffer.byteLength(message)));
		res.setHeader('Content-Type', 'text/plain; charset=utf-8');
		res.writeHead(200);
		res.end(message);
	});
	server.on('connection', (socket) => {
		sockets.add(socket);
		socket.on('close', () => sockets.delete(socket));
		socket.setTimeout(2000, () => {
			console.log('socket timeout');
			socket.resetAndDestroy();
		});
	});
	server.on('session', (session) => {
		sessions.add(session);
		session.on('close', () => sessions.delete(session));
	});

	async function gracefullyClose() {
		for (const session of sessions) {
			session.close();
		}
		await new Promise(resolve => server.close(resolve));
	}

	await new Promise(resolve => server.listen(443, resolve));
	console.log('Listening at https://localhost:443');

	process.once('SIGINT', () => {
		console.log('closing...');
		gracefullyClose().then(() => console.log('done'));
	});
})();

Step 3:

Open up Google Chrome (or probably any browser or HTTP client) and open the URL: https://localhost:443. You should get a hello world. plain text response. The only purpose of this step is to open an HTTP/2 session on the server. You don't really need Google Chrome specifically.

Step 4:

Wait for the Node.js server to print socket timeout in the console. This means the there was no activity on the socket for 2 seconds and so the socket was destroyed.

Step 5:

Close the Node.js server by triggering a SIGINT (i.e., press ctrl+c in the console). This will initiate a graceful shutdown of the server, which involves calling session.close() on every open session. This will cause the program to crash and print Segmentation fault: 11.

How often does it reproduce? Is there a required condition?

I can reliably reproduce it 100% of the time.

What is the expected behavior? Why is that the expected behavior?

The expected behavior is for the session.close() to not cause a segfault. Additionally, when the socket times out and socket.resetAndDestroy() is called, the session's close event should be emitted (which is currently not happening). The current behavior contradicts the Node.js docs:

When either the Socket or the Http2Session are destroyed, both will be destroyed.

What do you see instead?

Segmentation fault: 11

Additional information

I know some people might suggest that I call setTimeout on the session instead of the socket, and that I use session.destroy() instead of socket.resetAndDestroy(). However, that isn't really an acceptable solution, because I need to close ALL connections that are established, even if an HTTP/2 session hasn't yet been established on that connection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    http2Issues or PRs related to the http2 subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions