diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index cafbbbce152..b08114f212f 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -174,6 +174,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d *Auditbeat* - Enable System module config on Windows. {pull}10237[10237] +- Package: Disable librpm signal handlers. {pull}10694[10694] *Filebeat* diff --git a/x-pack/auditbeat/module/system/package/rpm_linux.go b/x-pack/auditbeat/module/system/package/rpm_linux.go index a1d395f54b1..292db1d5ed6 100644 --- a/x-pack/auditbeat/module/system/package/rpm_linux.go +++ b/x-pack/auditbeat/module/system/package/rpm_linux.go @@ -8,10 +8,12 @@ package pkg import ( "fmt" + "runtime" "time" "unsafe" "github.com/coreos/pkg/dlopen" + "github.com/joeshaw/multierror" ) /* @@ -22,6 +24,7 @@ import ( #include #include #include +#include rpmts my_rpmtsCreate(void *f) { @@ -100,7 +103,42 @@ my_rpmtsFree(void *f, rpmts ts) { rpmtsFree = (rpmts (*)(rpmts))f; rpmtsFree(ts); -}*/ +} + +// By default, librpm is going to trap various UNIX signals including SIGINT and SIGTERM +// which will prevent Beats from shutting down correctly. +// +// This disables that behavior. We should be very dilligent in +// cleaning up in our use of librpm. +// +// More recent versions of librpm have a new function rpmsqSetInterruptSafety() +// to do this, see below. +// +// See also: +// - librpm traps signals and calls exit(1) to terminate the whole process incl. our Go code: https://github.com/rpm-software-management/rpm/blob/rpm-4.11.3-release/lib/rpmdb.c#L640 +// - has caused problems for gdb before, calling rpmsqEnable(_, NULL) is the workaround they also use: https://bugzilla.redhat.com/show_bug.cgi?id=643031 +// - the new rpmsqSetInterruptSafety(), unfortunately only available in librpm>=4.14.0 (CentOS 7 has 4.11.3): https://github.com/rpm-software-management/rpm/commit/56f49d7f5af7c1c8a3eb478431356195adbfdd25 +void +my_disableLibrpmSignalTraps(void *f) { + int (*rpmsqEnable)(int, rpmsqAction_t); + rpmsqEnable = (int (*)(int, rpmsqAction_t))f; + + // Disable all traps + rpmsqEnable(-SIGHUP, NULL); + rpmsqEnable(-SIGINT, NULL); + rpmsqEnable(-SIGTERM, NULL); + rpmsqEnable(-SIGQUIT, NULL); + rpmsqEnable(-SIGPIPE, NULL); +} + +void +my_rpmsqSetInterruptSafety(void *f, int on) { + void (*rpmsqSetInterruptSafety)(int); + rpmsqSetInterruptSafety = (void (*)(int))f; + + rpmsqSetInterruptSafety(on); +} +*/ import "C" // Constants in sync with /usr/include/rpm/rpmtag.h @@ -117,15 +155,17 @@ const ( ) type cFunctions struct { - rpmtsCreate unsafe.Pointer - rpmReadConfigFiles unsafe.Pointer - rpmtsInitIterator unsafe.Pointer - rpmdbNextIterator unsafe.Pointer - headerLink unsafe.Pointer - headerGetEntry unsafe.Pointer - headerFree unsafe.Pointer - rpmdbFreeIterator unsafe.Pointer - rpmtsFree unsafe.Pointer + rpmtsCreate unsafe.Pointer + rpmReadConfigFiles unsafe.Pointer + rpmtsInitIterator unsafe.Pointer + rpmdbNextIterator unsafe.Pointer + headerLink unsafe.Pointer + headerGetEntry unsafe.Pointer + headerFree unsafe.Pointer + rpmdbFreeIterator unsafe.Pointer + rpmtsFree unsafe.Pointer + rpmsqEnable unsafe.Pointer + rpmsqSetInterruptSafety unsafe.Pointer } var cFun *cFunctions @@ -186,10 +226,31 @@ func dlopenCFunctions() (*cFunctions, error) { return nil, err } + // Only available in librpm>=4.13.0 + cFun.rpmsqSetInterruptSafety, err = librpm.GetSymbolPointer("rpmsqSetInterruptSafety") + if err != nil { + var err2 error + // Only available in librpm<4.14.0 + cFun.rpmsqEnable, err2 = librpm.GetSymbolPointer("rpmsqEnable") + if err2 != nil { + var errs multierror.Errors + errs = append(errs, err, err2) + return nil, errs.Err() + } + } + return &cFun, nil } func listRPMPackages() ([]*Package, error) { + // In newer versions, librpm is using the thread-local variable + // `disableInterruptSafety` in rpmio/rpmsq.c to disable signal + // traps. To make sure our settings remain in effect throughout + // our function calls we have to lock the OS thread here, since + // Golang can otherwise use any thread it likes for each C.* call. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if cFun == nil { var err error cFun, err = dlopenCFunctions() @@ -198,6 +259,10 @@ func listRPMPackages() ([]*Package, error) { } } + if cFun.rpmsqSetInterruptSafety != nil { + C.my_rpmsqSetInterruptSafety(cFun.rpmsqSetInterruptSafety, 0) + } + rpmts := C.my_rpmtsCreate(cFun.rpmtsCreate) if rpmts == nil { return nil, fmt.Errorf("Failed to get rpmts") @@ -212,6 +277,9 @@ func listRPMPackages() ([]*Package, error) { if mi == nil { return nil, fmt.Errorf("Failed to get match iterator") } + if cFun.rpmsqEnable != nil { + C.my_disableLibrpmSignalTraps(cFun.rpmsqEnable) + } defer C.my_rpmdbFreeIterator(cFun.rpmdbFreeIterator, mi) var packages []*Package diff --git a/x-pack/auditbeat/tests/system/test_metricsets.py b/x-pack/auditbeat/tests/system/test_metricsets.py index 965f7ee77c7..47b54b2f49d 100644 --- a/x-pack/auditbeat/tests/system/test_metricsets.py +++ b/x-pack/auditbeat/tests/system/test_metricsets.py @@ -41,7 +41,6 @@ def test_metricset_login(self): # Metricset is experimental and that generates a warning, TODO: remove later self.check_metricset("system", "login", COMMON_FIELDS + fields, config, warnings_allowed=True) - @unittest.skip("Flaky test, see https://github.com/elastic/beats/issues/10633") @unittest.skipIf(sys.platform == "win32", "Not implemented for Windows") @unittest.skipIf(sys.platform == "linux2" and not (os.path.isdir("/var/lib/dpkg") or os.path.isdir("/var/lib/rpm")), "Only implemented for dpkg and rpm")