The VFS calls backends from goroutines of its own. A panic on any of these cannot be recovered. So a backend panicking on a single file killed the whole process, taking down a mount or every user of a serve command, even for servers such as serve http whose library recovers panics raised on its own request goroutines. Recover panics at those goroutines and log them with a stack trace. Where the surrounding code already handles a failure, recover around the backend call itself rather than the whole goroutine, so a panicking upload is retried like any other failed upload and a panicking download is reported to the waiters, instead of abandoning the work part way through and leaving the bookkeeping inconsistent. Addresses GHSA-6jcg-q3wp-x2f4
30 lines
808 B
Go
30 lines
808 B
Go
package vfscommon
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime/debug"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
)
|
|
|
|
// RecoverPanic recovers a panic, logs it against o with a stack trace and,
|
|
// if err is not nil, stores it there. Use it as `defer RecoverPanic(o, &err)`.
|
|
func RecoverPanic(o any, err *error) {
|
|
if r := recover(); r != nil {
|
|
fs.Errorf(o, "panic: %v\n%s", r, debug.Stack())
|
|
if err != nil {
|
|
*err = fmt.Errorf("panic: %v", r)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RecoverCall calls fn, converting a panic into an error.
|
|
//
|
|
// Prefer this to recovering a whole goroutine where the caller already
|
|
// handles an error, so a panic is retried or reported the same way a failure
|
|
// is, rather than abandoning the surrounding work part way through.
|
|
func RecoverCall(o any, fn func() error) (err error) {
|
|
defer RecoverPanic(o, &err)
|
|
return fn()
|
|
}
|