Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into feature/dra-enhance…
Browse files Browse the repository at this point in the history
…ments

* upstream/main:
  winlogbeat: fix event handling for Windows 2022 (elastic#30942)
  [Release] add-backport-next (elastic#31056)
  • Loading branch information
v1v committed Mar 31, 2022
2 parents 8c2d88b + 34bdc3d commit 4167115
Show file tree
Hide file tree
Showing 32 changed files with 398 additions and 176 deletions.
13 changes: 13 additions & 0 deletions .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,16 @@ pull_request_rules:
labels:
- "backport"
title: "[{{ destination_branch }}](backport #{{ number }}) {{ title }}"
- name: backport patches to 8.2 branch
conditions:
- merged
- label=backport-v8.2.0
actions:
backport:
assignees:
- "{{ author }}"
branches:
- "8.2"
labels:
- "backport"
title: "[{{ destination_branch }}](backport #{{ number }}) {{ title }}"
2 changes: 2 additions & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...main[Check the HEAD dif

*Winlogbeat*

- Fix evtx parsing failures. {issue}30621[30621] {pull}30942[30942]


*Functionbeat*

Expand Down
5 changes: 5 additions & 0 deletions winlogbeat/Jenkinsfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ stages:
crosscompile:
make: "make -C winlogbeat crosscompile"
stage: mandatory
windows-2022:
mage: "mage build unitTest"
platforms: ## override default labels in this specific stage.
- "windows-2022"
stage: mandatory
windows-2019:
mage: "mage build unitTest"
platforms: ## override default labels in this specific stage.
Expand Down
2 changes: 1 addition & 1 deletion winlogbeat/beater/winlogbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (eb *Winlogbeat) init(b *beat.Beat) error {
if err != nil {
return fmt.Errorf("failed to create new event log: %w", err)
}
eb.log.Debugw("Initialized EventLog", "id", eventLog.Name())
eb.log.Debugf("initialized WinEventLog[%s]", eventLog.Name())

logger, err := newEventLogger(b.Info, eventLog, config, eb.log)
if err != nil {
Expand Down
47 changes: 29 additions & 18 deletions winlogbeat/eventlog/wineventlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ const (
eventLoggingAPIName = "eventlogging"
)

func init() {
// Register wineventlog API if it is available.
available, _ := win.IsAvailable()
if available {
Register(winEventLogAPIName, 0, newWinEventLog, win.Channels)
Register(eventLoggingAPIName, 1, newEventLogging, win.Channels)
}
}

type winEventLogConfig struct {
ConfigCommon `config:",inline"`
BatchReadSize int `config:"batch_read_size"` // Maximum number of events that Read will return.
Expand Down Expand Up @@ -160,6 +169,7 @@ type winEventLog struct {
lastRead checkpoint.EventLogState // Record number of the last read event.

render func(event win.EvtHandle, out io.Writer) error // Function for rendering the event to XML.
message func(event win.EvtHandle) (string, error) // Message fallback function.
renderBuf []byte // Buffer used for rendering event.
outputBuf *sys.ByteBuffer // Buffer for receiving XML
cache *messageFilesCache // Cached mapping of source name to event message file handles.
Expand Down Expand Up @@ -198,7 +208,7 @@ func (l *winEventLog) openChannel(bookmark win.EvtHandle) error {
if err != nil {
return err
}
defer func() { _ = windows.CloseHandle(signalEvent) }()
defer windows.CloseHandle(signalEvent) //nolint:errcheck // This is just a resource release.

var flags win.EvtSubscribeFlag
if bookmark > 0 {
Expand Down Expand Up @@ -286,13 +296,12 @@ func (l *winEventLog) Read() ([]Record, error) {
}()
detailf("%s EventHandles returned %d handles", l.logPrefix, len(handles))

//nolint: prealloc // some handles can be skipped, the final size is unknown
var records []Record
var records []Record //nolint:prealloc // This linter gives bad advice and does not take into account conditionals in loops.
for _, h := range handles {
l.outputBuf.Reset()
err := l.render(h, l.outputBuf)
var bufErr sys.InsufficientBufferError
if ok := errors.As(err, &bufErr); ok {
if errors.As(err, &bufErr) {
detailf("%s Increasing render buffer size to %d", l.logPrefix,
bufErr.RequiredSize)
l.renderBuf = make([]byte, bufErr.RequiredSize)
Expand All @@ -314,6 +323,12 @@ func (l *winEventLog) Read() ([]Record, error) {
if r.Offset.Bookmark, err = l.createBookmarkFromEvent(h); err != nil {
logp.Warn("%s failed creating bookmark: %v", l.logPrefix, err)
}
if r.Message == "" {
r.Message, err = l.message(h)
if err != nil {
logp.Err("%s error salvaging message: %v", l.logPrefix, err)
}
}
records = append(records, r)
l.lastRead = r.Offset
}
Expand All @@ -329,20 +344,20 @@ func (l *winEventLog) Close() error {

func (l *winEventLog) eventHandles(maxRead int) ([]win.EvtHandle, int, error) {
handles, err := win.EventHandles(l.subscription, maxRead)
switch {
case err == nil:
switch err { //nolint:errorlint // This is an errno or nil.
case nil:
if l.maxRead > maxRead {
debugf("%s Recovered from RPC_S_INVALID_BOUND error (errno 1734) "+
"by decreasing batch_read_size to %v", l.logPrefix, maxRead)
}
return handles, maxRead, nil
case errors.Is(err, win.ERROR_NO_MORE_ITEMS):
case win.ERROR_NO_MORE_ITEMS:
detailf("%s No more events", l.logPrefix)
if l.config.NoMoreEvents == Stop {
return nil, maxRead, io.EOF
}
return nil, maxRead, nil
case errors.Is(err, win.RPC_S_INVALID_BOUND):
case win.RPC_S_INVALID_BOUND:
incrementMetric(readErrors, err)
if err := l.Close(); err != nil {
return nil, 0, fmt.Errorf("failed to recover from RPC_S_INVALID_BOUND: %w", err)
Expand Down Expand Up @@ -381,13 +396,15 @@ func (l *winEventLog) buildRecordFromXML(x []byte, recoveredErr error) Record {
e.RenderErr = append(e.RenderErr, recoveredErr.Error())
}

// Get basic string values for raw fields.
winevent.EnrichRawValuesWithNames(nil, &e)
if e.Level == "" {
// Fallback on LevelRaw if the Level is not set in the RenderingInfo.
e.Level = win.EventLevel(e.LevelRaw).String()
}

if logp.IsDebug(detailSelector) {
detailf("%s XML=%s Event=%+v", l.logPrefix, string(x), e)
detailf("%s XML=%s Event=%+v", l.logPrefix, x, e)
}

r := Record{
Expand Down Expand Up @@ -489,6 +506,9 @@ func newWinEventLog(options *common.Config) (EventLog, error) {
return win.RenderEvent(event, c.EventLanguage, l.renderBuf, l.cache.get, out)
}
}
l.message = func(event win.EvtHandle) (string, error) {
return win.Message(event, l.renderBuf, l.cache.get)
}

return l, nil
}
Expand All @@ -503,12 +523,3 @@ func (l *winEventLog) createBookmarkFromEvent(evtHandle win.EvtHandle) (string,
win.Close(bmHandle)
return string(l.outputBuf.Bytes()), err
}

func init() {
// Register wineventlog API if it is available.
available, _ := win.IsAvailable()
if available {
Register(winEventLogAPIName, 0, newWinEventLog, win.Channels)
Register(eventLoggingAPIName, 1, newEventLogging, win.Channels)
}
}
28 changes: 13 additions & 15 deletions winlogbeat/eventlog/wineventlog_experimental.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
package eventlog

import (
"errors"
"fmt"
"io"
"os"
Expand All @@ -43,6 +42,14 @@ const (
winEventLogExpAPIName = "wineventlog-experimental"
)

func init() {
// Register wineventlog API if it is available.
available, _ := win.IsAvailable()
if available {
Register(winEventLogExpAPIName, 10, newWinEventLogExp, win.Channels)
}
}

// winEventLogExp implements the EventLog interface for reading from the Windows
// Event Log API.
type winEventLogExp struct {
Expand Down Expand Up @@ -100,7 +107,7 @@ func (l *winEventLogExp) openChannel(bookmark win.Bookmark) (win.EvtHandle, erro
if err != nil {
return win.NilHandle, err
}
defer func() { _ = windows.CloseHandle(signalEvent) }()
defer windows.CloseHandle(signalEvent) //nolint:errcheck // This is just a resource release.

var flags win.EvtSubscribeFlag
if bookmark > 0 {
Expand All @@ -120,11 +127,10 @@ func (l *winEventLogExp) openChannel(bookmark win.Bookmark) (win.EvtHandle, erro
win.EvtHandle(bookmark), // Bookmark - for resuming from a specific event
flags)

switch {
case err == nil:
switch err { //nolint:errorlint // This is an errno or nil.
case nil:
return h, nil
case errors.Is(err, win.ERROR_NOT_FOUND), errors.Is(err, win.ERROR_EVT_QUERY_RESULT_STALE),
errors.Is(err, win.ERROR_EVT_QUERY_RESULT_INVALID_POSITION):
case win.ERROR_NOT_FOUND, win.ERROR_EVT_QUERY_RESULT_STALE, win.ERROR_EVT_QUERY_RESULT_INVALID_POSITION:
// The bookmarked event was not found, we retry the subscription from the start.
incrementMetric(readErrors, err)
return win.Subscribe(0, signalEvent, "", l.query, 0, win.EvtSubscribeStartAtOldestRecord)
Expand Down Expand Up @@ -213,7 +219,7 @@ func (l *winEventLogExp) processHandle(h win.EvtHandle) (*Record, error) {
evt.RenderErr = append(evt.RenderErr, err.Error())
}

//nolint: godox // keep to have a record of feature disparity between non-experimental vs experimental
//nolint:godox // Bad linter! Keep to have a record of feature disparity between non-experimental vs experimental.
// TODO: Need to add XML when configured.

r := &Record{
Expand Down Expand Up @@ -321,11 +327,3 @@ func newWinEventLogExp(options *common.Config) (EventLog, error) {

return l, nil
}

func init() {
// Register wineventlog API if it is available.
available, _ := win.IsAvailable()
if available {
Register(winEventLogExpAPIName, 10, newWinEventLogExp, win.Channels)
}
}
23 changes: 20 additions & 3 deletions winlogbeat/eventlog/wineventlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

"github.com/andrewkroh/sys/windows/svc/eventlog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elastic/beats/v7/libbeat/common"
"github.com/elastic/beats/v7/winlogbeat/checkpoint"
Expand Down Expand Up @@ -182,13 +183,27 @@ func testWindowsEventLog(t *testing.T, api string) {
const messageSize = 256 // Originally 31800, such a large value resulted in an empty eventlog under Win10.
const totalEvents = 1000
for i := 0; i < totalEvents; i++ {
safeWriteEvent(t, writer, eventlog.Info, uint32(i%1000), []string{strconv.Itoa(i) + " " + randomSentence(messageSize)})
safeWriteEvent(t, writer, eventlog.Info, uint32(i%1000)+1, []string{strconv.Itoa(i) + " " + randomSentence(messageSize)})
}

openLog := func(t testing.TB, config map[string]interface{}) EventLog {
return openLog(t, api, nil, config)
}

t.Run("has_message", func(t *testing.T) {
log := openLog(t, map[string]interface{}{"name": providerName, "batch_read_size": 1})
defer log.Close()

for i := 0; i < 10; i++ {
records, err := log.Read()
require.NotEmpty(t, records)
require.NoError(t, err)

r := records[0]
require.NotEmpty(t, r.Message, "message field is empty: errors:%v\nrecord:%#v", r.Event.RenderErr, r)
}
})

// Test reading from an event log using a custom XML query.
t.Run("custom_xml_query", func(t *testing.T) {
cfg := map[string]interface{}{
Expand Down Expand Up @@ -302,16 +317,18 @@ func createLog(t testing.TB, messageFiles ...string) (log *eventlog.Log, tearDow
}

if existed {
wineventlog.EvtClearLog(wineventlog.NilHandle, name, "")
wineventlog.EvtClearLog(wineventlog.NilHandle, name, "") //nolint:errcheck // This is just a resource release.
}

log, err = eventlog.Open(source)
//nolint:errcheck // This is just a resource release.
if err != nil {
eventlog.RemoveSource(name, source)
eventlog.RemoveProvider(name)
t.Fatal(err)
}

//nolint:errcheck // This is just a resource release.
tearDown = func() {
log.Close()
wineventlog.EvtClearLog(wineventlog.NilHandle, name, "")
Expand All @@ -338,7 +355,7 @@ func safeWriteEvent(t testing.TB, log *eventlog.Log, etype uint16, eid uint32, m

// setLogSize set the maximum number of bytes that an event log can hold.
func setLogSize(t testing.TB, provider string, sizeBytes int) {
output, err := exec.Command("wevtutil.exe", "sl", "/ms:"+strconv.Itoa(sizeBytes), provider).CombinedOutput()
output, err := exec.Command("wevtutil.exe", "sl", "/ms:"+strconv.Itoa(sizeBytes), provider).CombinedOutput() //nolint:gosec // No possibility of command injection.
if err != nil {
t.Fatal("Failed to set log size", err, string(output))
}
Expand Down
30 changes: 13 additions & 17 deletions winlogbeat/sys/winevent/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ var (
const (
keywordAuditFailure = 0x10000000000000
keywordAuditSuccess = 0x20000000000000

// keywordClassic indicates the log was published with the "classic" event
// logging API.
// https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.eventing.reader.standardeventkeywords?view=netframework-4.8
keywordClassic = 0x80000000000000
)

// UnmarshalXML unmarshals the given XML into a new Event.
Expand All @@ -67,7 +62,7 @@ type Event struct {
Version Version `xml:"System>Version"`
LevelRaw uint8 `xml:"System>Level"`
TaskRaw uint16 `xml:"System>Task"`
OpcodeRaw uint8 `xml:"System>Opcode"`
OpcodeRaw *uint8 `xml:"System>Opcode,omitempty"`
KeywordsRaw HexInt64 `xml:"System>Keywords"`
TimeCreated TimeCreated `xml:"System>TimeCreated"`
RecordID uint64 `xml:"System>EventRecordID"`
Expand Down Expand Up @@ -258,7 +253,10 @@ func (u *UserData) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {

u.Name = se.Name
u.Pairs = in.Pairs
d.Skip()
err = d.Skip()
if err != nil {
return err
}
break
}
}
Expand Down Expand Up @@ -309,8 +307,7 @@ func (v *Version) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {

version, err := strconv.ParseUint(s, 10, 8)
if err != nil {
// Ignore invalid version values.
return nil
return nil //nolint:nilerr // Ignore invalid version values.
}

*v = Version(version)
Expand Down Expand Up @@ -341,20 +338,19 @@ func (v *HexInt64) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
func EnrichRawValuesWithNames(publisherMeta *WinMeta, event *Event) {
// Keywords. Each bit in the value can represent a keyword.
rawKeyword := int64(event.KeywordsRaw)
isClassic := keywordClassic&rawKeyword > 0

if len(event.Keywords) == 0 {
for mask, keyword := range defaultWinMeta.Keywords {
if rawKeyword&mask > 0 {
if rawKeyword&mask != 0 {
event.Keywords = append(event.Keywords, keyword)
rawKeyword -= mask
rawKeyword &^= mask
}
}
if publisherMeta != nil {
for mask, keyword := range publisherMeta.Keywords {
if rawKeyword&mask > 0 {
if rawKeyword&mask != 0 {
event.Keywords = append(event.Keywords, keyword)
rawKeyword -= mask
rawKeyword &^= mask
}
}
}
Expand All @@ -363,10 +359,10 @@ func EnrichRawValuesWithNames(publisherMeta *WinMeta, event *Event) {
var found bool
if event.Opcode == "" {
// Opcode (search in defaultWinMeta first).
if !isClassic {
event.Opcode, found = defaultWinMeta.Opcodes[event.OpcodeRaw]
if event.OpcodeRaw != nil {
event.Opcode, found = defaultWinMeta.Opcodes[*event.OpcodeRaw]
if !found && publisherMeta != nil {
event.Opcode = publisherMeta.Opcodes[event.OpcodeRaw]
event.Opcode = publisherMeta.Opcodes[*event.OpcodeRaw]
}
}
}
Expand Down
Loading

0 comments on commit 4167115

Please sign in to comment.