diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2021-12-08 17:02:32 +0000 |
|---|---|---|
| committer | Aleksandr Nogikh <wp32pw@gmail.com> | 2021-12-09 14:31:10 +0100 |
| commit | 4459585c043faace507c685bcd9997da15809aae (patch) | |
| tree | e37e0d561e243521cbcf9169bbdb0180e658f3cb /pkg/host/features_linux.go | |
| parent | b54aa474a6b13cc9b8c0a68f07d71873f30dfa02 (diff) | |
all: adapt to how mmapping a kcov instance works in Linux
It turns out that the current Linux implementation of KCOV does not
properly handle multiple mmap invocations on the same instance. The
first one succeedes, but the subsequent ones do not actually mmap
anything, yet returning no error at all.
The ability to mmap that memory multiple times allows us to increase
syz-executor performance and it would be a pity to completely lose it
(especially given that mmapping kcov works fine on *BSD).
In some time a patch will be prepared, but still we will have to support
both versions at the same time - the buggy one and the correct one.
Detect whether the bug is present by writing a value at the pointer
returned by mmap. If it is present, disable dynamic kcov mmapping and
pre-mmap 5 instances in the main() function - it should be enough for
all reasonable uses. Otherwise, pre-mmap 3 and let syz-executor mmap
them as needed.
Diffstat (limited to 'pkg/host/features_linux.go')
| -rw-r--r-- | pkg/host/features_linux.go | 62 |
1 files changed, 48 insertions, 14 deletions
diff --git a/pkg/host/features_linux.go b/pkg/host/features_linux.go index 5c45bdcc1..de04e7a4d 100644 --- a/pkg/host/features_linux.go +++ b/pkg/host/features_linux.go @@ -7,6 +7,7 @@ import ( "fmt" "regexp" "runtime" + "runtime/debug" "strconv" "syscall" "unsafe" @@ -21,6 +22,7 @@ func init() { checkFeature[FeatureCoverage] = checkCoverage checkFeature[FeatureComparisons] = checkComparisons checkFeature[FeatureExtraCoverage] = checkExtraCoverage + checkFeature[FeatureDelayKcovMmap] = checkDelayKcovMmap checkFeature[FeatureSandboxSetuid] = unconditionallyEnabled checkFeature[FeatureSandboxNamespace] = checkSandboxNamespace checkFeature[FeatureSandboxAndroid] = checkSandboxAndroid @@ -57,6 +59,39 @@ func checkExtraCoverage() (reason string) { return checkCoverageFeature(FeatureExtraCoverage) } +func checkDelayKcovMmap() string { + // The kcov implementation in Linux (currently) does not adequately handle subsequent + // mmap invocations on the same descriptor. When that problem is fixed, we want + // syzkaller to differentiate between distributions as this allows to noticeably speed + // up fuzzing. + return checkCoverageFeature(FeatureDelayKcovMmap) +} + +func kcovTestMmap(fd int, coverSize uintptr) (reason string) { + mem, err := syscall.Mmap(fd, 0, int(coverSize*unsafe.Sizeof(uintptr(0))), + syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + return fmt.Sprintf("KCOV mmap failed: %v", err) + } + defer func() { + if err := syscall.Munmap(mem); err != nil { + reason = fmt.Sprintf("munmap failed: %v", err) + } + }() + // Now the tricky part - mmap may say it has succeeded, but the memory is + // in fact not allocated. + // We try to access it. If go does not panic, everything is fine. + prevValue := debug.SetPanicOnFault(true) + defer debug.SetPanicOnFault(prevValue) + defer func() { + if r := recover(); r != nil { + reason = "mmap returned an invalid pointer" + } + }() + mem[0] = 0 + return "" +} + func checkCoverageFeature(feature int) (reason string) { if reason = checkDebugFS(); reason != "" { return reason @@ -80,16 +115,15 @@ func checkCoverageFeature(feature int) (reason string) { if errno != 0 { return fmt.Sprintf("ioctl(KCOV_INIT_TRACE) failed: %v", errno) } - mem, err := syscall.Mmap(fd, 0, int(coverSize*unsafe.Sizeof(uintptr(0))), - syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) - if err != nil { - return fmt.Sprintf("KCOV mmap failed: %v", err) + if reason = kcovTestMmap(fd, coverSize); reason != "" { + return reason } - defer func() { - if err := syscall.Munmap(mem); err != nil { - reason = fmt.Sprintf("munmap failed: %v", err) + disableKcov := func() { + _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_DISABLE, 0) + if errno != 0 { + reason = fmt.Sprintf("ioctl(KCOV_DISABLE) failed: %v", errno) } - }() + } switch feature { case FeatureComparisons: _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, @@ -100,6 +134,7 @@ func checkCoverageFeature(feature int) (reason string) { } return fmt.Sprintf("ioctl(KCOV_TRACE_CMP) failed: %v", errno) } + defer disableKcov() case FeatureExtraCoverage: arg := KcovRemoteArg{ TraceMode: uint32(linux.KCOV_TRACE_PC), @@ -115,15 +150,14 @@ func checkCoverageFeature(feature int) (reason string) { } return fmt.Sprintf("ioctl(KCOV_REMOTE_ENABLE) failed: %v", errno) } + defer disableKcov() + case FeatureDelayKcovMmap: + if reason = kcovTestMmap(fd, coverSize); reason != "" { + return reason + } default: panic("unknown feature in checkCoverageFeature") } - defer func() { - _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), linux.KCOV_DISABLE, 0) - if errno != 0 { - reason = fmt.Sprintf("ioctl(KCOV_DISABLE) failed: %v", errno) - } - }() return "" } |
