go: archive, image, debug, encoding, x/net/html: DO NOT PANIC

We have a number of packages that implement parsers where a panic might lead to a Denial of Service, but returning an invalid input error instead would be perfectly harmless. We should wrap them all in a recover() and prevent the panic from propagating, as a robustness and defense in depth measure.

We need to be careful about preserving documented panic conditions, and about not leaving behind persistent state that might be corrupt following a panic.

Ideas for other packages that can benefit are welcome. Crypto packages were intentionally left out, as we should be confident in their operation. math/big has a lot of entry points and persistent state by definition (and we have a plan to drag it out of the security perimeter).

/cc @golang/security

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 7
  • Comments: 31 (9 by maintainers)

Commits related to this issue

Most upvoted comments

That said, now that we have fuzzing coming I wonder whether the recovers are even necessary long-term. These packages seem like they should be extremely deterministic, so they should be very effective targets for fuzzer-generated inputs — and if we can find (and fix) the panics through fuzzing, there should be essentially nothing left to recover.

(That’s in contrast with, say, packages with a large amount of nondeterminism like net/http.)

For libraries that wrap user-provided interfaces (such as reading from a user-provided io.Reader), I think it’s important that we recover only panics that originate within the library, and not those that originate in the user code. Otherwise the recover can turn a diagnosable crash (or, worse, an intended panic/recover pair) into a hard-to-diagnose deadlock, which has its own problems.

One way to detect panics that originate within the library might be to use runtime.Callers to snoop the package for the first non-runtime frame. But then, the caller may be doing that already, and recovering the panic at all (even if it is re-panicked) will destroy the original stack trace. (The language provides no way to re-throw a recovered panic without destroying it, and since the language defines the behavior of panic and recover, any change to a package’s recovery behavior is arguably a breaking change!)

Fortunately, we can use a variable to detect an abnormal exit, then snoop the caller stack, and finally recover only if the stack originates in the specific package (or perhaps one of a specific set of packages): https://play.golang.org/p/NAzk0ATvGx5

That would allow these packages to recover from internal bugs, but without masking (or destroying information from) panics in user code.