Weekly Padgett

No. 0012 min read

Twenty-five years of corkscrew. All 291 damned lines.

I wrote 291 lines of C to get SSH through a corporate proxy in 2000. Linux distributions kept shipping it. Modern analysis has some things to tell me.

A person handles coiled sausages in a metal container beside a computer monitor and keyboard.
File photoDramatization. No corkscrew was harmed.

I wrote corkscrew[1]Ref. 1corkscrew en.wikipedia.org in an afternoon. A quarter century later I'm still discussing it, although I would have preferred to be discussing something other than its stack buffer overflow.

In 2000 I was behind a corporate HTTP proxy that let port 443 out and nothing else. I wanted SSH access to my own machine at home. The proxy spoke HTTP CONNECT[2]Ref. 2HTTP CONNECT rfc-editor.org, SSH spoke a raw TCP stream, and OpenSSH[3]Ref. 3OpenSSH openssh.com had this option nobody seemed to use, ProxyCommand. I wrote the thing to sit between them.

291 lines of C. Debian[4]Ref. 4Debian debian.org, Ubuntu, Red Hat, CentOS, FreeBSD and Cygwin have packaged it. Linux Magazine and 2600 reviewed it. It has a Wikipedia article. An afternoon's work has an encyclopedia entry now. I certainly didn't expect that.

I recently had the source put through GCC[5]Ref. 5GCC gcc.gnu.org static analysis, AddressSanitizer[6]Ref. 6AddressSanitizer clang.llvm.org, UndefinedBehaviorSanitizer, and a loopback mock proxy sending deliberately hostile responses. I wanted to know what 26-year-old C looks like under modern tooling, and, well... it looks like 26-year-old C. Fuck.

The big finding is a stack buffer overflow reachable from the proxy. The response handler does a read() into a 4096-byte buffer, doesn't add a terminator, and hands it to sscanf() with unbounded %s. Send 4096 non-whitespace bytes and you get a write one byte past the buffer. No code execution was demonstrated. A hostile proxy can still corrupt the client's stack, and I do not particularly enjoy typing that about my software.

The framing bug bothers me more, because I can't blame a typo. The code treats the first read() as the whole proxy response, and on a 200 it throws all of it away. If headers and the first bytes of the SSH greeting arrive in the same segment, the greeting goes with them. And with fragmented headers it tries to parse HTTP/1. and exits.

It worked for 25 years because proxies almost always send headers in their own packet. I wrote that assumption into the thing, and now I get to look at it again.

The rest I'd leave alone. But those damned buffers need terminators.

References

  1. corkscrew en.wikipedia.org ↩
  2. HTTP CONNECT rfc-editor.org ↩
  3. OpenSSH openssh.com ↩
  4. Debian debian.org ↩
  5. GCC gcc.gnu.org ↩
  6. AddressSanitizer clang.llvm.org ↩
corkscrewcsshopen-source

More on this beat