Skip to content

Commit

Permalink
Enhance the Error type to provide more information about the error.
Browse files Browse the repository at this point in the history
This introduces the following changes.:

* The `Error` code now has a `Code` field that exposes the YARA API error code to Go users, and also exposes the error codes as constants that can be used in Go code (example: `err.Code == yara.ERROR_TIMEOUT`). Sometimes is useful to get a numeric error code instead of a text string.

* When `Error` is converted to a string, it includes the name of the rule causing the error. For example, instead of getting an error like `syntax error, unexpected identifier, expecting <condition>`, you get `rule \"foo\": syntax error, unexpected identifier, expecting <condition>`.
  • Loading branch information
plusvic authored and hillu committed Nov 8, 2023
1 parent 11d1088 commit fdb067a
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 15 deletions.
16 changes: 15 additions & 1 deletion compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ package yara
#include <yara.h>
#include "compat.h"
// rule_identifier is a union accessor function.
static const char* rule_identifier(YR_RULE* r) {
return r->identifier;
}
void compilerCallback(int, char*, int, YR_RULE*, char*, void*);
char* includeCallback(char*, char*, char*, void*);
void freeCallback(char*, void*);
*/
import "C"
import (
"errors"
"fmt"
"os"
"reflect"
"runtime"
Expand All @@ -26,10 +32,18 @@ import (
//export compilerCallback
func compilerCallback(errorLevel C.int, filename *C.char, linenumber C.int, rule *C.YR_RULE, message *C.char, userData unsafe.Pointer) {
c := cgoHandle(*(*uintptr)(userData)).Value().(*Compiler)
var text string
if rule != nil {
text = fmt.Sprintf("rule \"%s\": %s",
C.GoString(C.rule_identifier(rule)),
C.GoString(message))
} else {
text = C.GoString(message)
}
msg := CompilerMessage{
Filename: C.GoString(filename),
Line: int(linenumber),
Text: C.GoString(message),
Text: text,
}
if rule != nil {
// Rule object implicitly relies on the compiler object and is destroyed when the compiler is destroyed.
Expand Down
16 changes: 15 additions & 1 deletion compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

package yara

import "testing"
import (
"testing"
)

func TestCompiler(t *testing.T) {
c, _ := NewCompiler()
Expand Down Expand Up @@ -43,6 +45,18 @@ func TestWarnings(t *testing.T) {
t.Logf("Recorded Errors=%#v, Warnings=%#v", c.Errors, c.Warnings)
}

func TestErrors(t *testing.T) {
c, _ := NewCompiler()
c.AddString("rule foo { bar }", "")
if len(c.Errors) == 0 {
t.Error("did not recognize error")
}
expectedError := "rule \"foo\": syntax error, unexpected identifier, expecting <condition>"
if c.Errors[0].Text != expectedError {
t.Errorf("expected error: %#v, got %#v", expectedError, c.Errors[0].Text)
}
}

func setupCompiler(t *testing.T) *Compiler {
c, err := NewCompiler()
if err != nil {
Expand Down
85 changes: 77 additions & 8 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,94 @@ package yara

// #include <yara.h>
import "C"
import "strconv"
import (
"fmt"
)

// Error encapsulates the C API error codes.
type Error int
type Error struct {
// YARA error code.
Code int
// Namespace in which the error occurred, if applicable. It can be empty.
Namespace string
// Rule in which the error occurred, if applicable. It can be empty.
RuleIdentifier string
// String in which the error occurred, if applicable. It can be empty.
StringIdentifier string
}

func (e Error) Error() string {
if str, ok := errorStrings[int(e)]; ok {
func (e Error) Error() (errorString string) {
if e.Namespace != "" && e.RuleIdentifier != "" {
errorString = fmt.Sprintf("%s caused by rule \"%s:%s\"",
errorCodeToString(e.Code), e.Namespace, e.RuleIdentifier)
if e.StringIdentifier != "" {
errorString += fmt.Sprintf(" string %s", e.StringIdentifier)
}
} else {
errorString = errorCodeToString(e.Code)
}
return errorString
}

func errorCodeToString(errorCode int) string {
if str, ok := errorStrings[errorCode]; ok {
return str
}
return "unknown YARA error " + strconv.Itoa(int(e))
return fmt.Sprintf("unknown error %d", errorCode)
}

func newError(code C.int) error {
if code != 0 {
return Error(code)
if code == C.ERROR_SUCCESS {
return nil
}
return nil
return Error{Code: int(code)}
}

const (
ERROR_SUCCESS = C.ERROR_SUCCESS
ERROR_INSUFFICIENT_MEMORY = C.ERROR_INSUFFICIENT_MEMORY
ERROR_COULD_NOT_ATTACH_TO_PROCESS = C.ERROR_COULD_NOT_ATTACH_TO_PROCESS
ERROR_COULD_NOT_OPEN_FILE = C.ERROR_COULD_NOT_OPEN_FILE
ERROR_COULD_NOT_MAP_FILE = C.ERROR_COULD_NOT_MAP_FILE
ERROR_INVALID_FILE = C.ERROR_INVALID_FILE
ERROR_CORRUPT_FILE = C.ERROR_CORRUPT_FILE
ERROR_UNSUPPORTED_FILE_VERSION = C.ERROR_UNSUPPORTED_FILE_VERSION
ERROR_INVALID_REGULAR_EXPRESSION = C.ERROR_INVALID_REGULAR_EXPRESSION
ERROR_INVALID_HEX_STRING = C.ERROR_INVALID_HEX_STRING
ERROR_SYNTAX_ERROR = C.ERROR_SYNTAX_ERROR
ERROR_LOOP_NESTING_LIMIT_EXCEEDED = C.ERROR_LOOP_NESTING_LIMIT_EXCEEDED
ERROR_DUPLICATED_LOOP_IDENTIFIER = C.ERROR_DUPLICATED_LOOP_IDENTIFIER
ERROR_DUPLICATED_IDENTIFIER = C.ERROR_DUPLICATED_IDENTIFIER
ERROR_DUPLICATED_TAG_IDENTIFIER = C.ERROR_DUPLICATED_TAG_IDENTIFIER
ERROR_DUPLICATED_META_IDENTIFIER = C.ERROR_DUPLICATED_META_IDENTIFIER
ERROR_DUPLICATED_STRING_IDENTIFIER = C.ERROR_DUPLICATED_STRING_IDENTIFIER
ERROR_UNREFERENCED_STRING = C.ERROR_UNREFERENCED_STRING
ERROR_UNDEFINED_STRING = C.ERROR_UNDEFINED_STRING
ERROR_UNDEFINED_IDENTIFIER = C.ERROR_UNDEFINED_IDENTIFIER
ERROR_MISPLACED_ANONYMOUS_STRING = C.ERROR_MISPLACED_ANONYMOUS_STRING
ERROR_INCLUDES_CIRCULAR_REFERENCE = C.ERROR_INCLUDES_CIRCULAR_REFERENCE
ERROR_INCLUDE_DEPTH_EXCEEDED = C.ERROR_INCLUDE_DEPTH_EXCEEDED
ERROR_WRONG_TYPE = C.ERROR_WRONG_TYPE
ERROR_EXEC_STACK_OVERFLOW = C.ERROR_EXEC_STACK_OVERFLOW
ERROR_SCAN_TIMEOUT = C.ERROR_SCAN_TIMEOUT
ERROR_TOO_MANY_SCAN_THREADS = C.ERROR_TOO_MANY_SCAN_THREADS
ERROR_CALLBACK_ERROR = C.ERROR_CALLBACK_ERROR
ERROR_INVALID_ARGUMENT = C.ERROR_INVALID_ARGUMENT
ERROR_TOO_MANY_MATCHES = C.ERROR_TOO_MANY_MATCHES
ERROR_INTERNAL_FATAL_ERROR = C.ERROR_INTERNAL_FATAL_ERROR
ERROR_NESTED_FOR_OF_LOOP = C.ERROR_NESTED_FOR_OF_LOOP
ERROR_INVALID_FIELD_NAME = C.ERROR_INVALID_FIELD_NAME
ERROR_UNKNOWN_MODULE = C.ERROR_UNKNOWN_MODULE
ERROR_NOT_A_STRUCTURE = C.ERROR_NOT_A_STRUCTURE
ERROR_NOT_INDEXABLE = C.ERROR_NOT_INDEXABLE
ERROR_NOT_A_FUNCTION = C.ERROR_NOT_A_FUNCTION
ERROR_INVALID_FORMAT = C.ERROR_INVALID_FORMAT
ERROR_TOO_MANY_ARGUMENTS = C.ERROR_TOO_MANY_ARGUMENTS
ERROR_WRONG_ARGUMENTS = C.ERROR_WRONG_ARGUMENTS
ERROR_WRONG_RETURN_TYPE = C.ERROR_WRONG_RETURN_TYPE
ERROR_DUPLICATED_STRUCTURE_MEMBER = C.ERROR_DUPLICATED_STRUCTURE_MEMBER
)

// FIXME: This should be generated from yara/error.h
var errorStrings = map[int]string{
C.ERROR_INSUFICIENT_MEMORY: "insufficient memory",
Expand Down
27 changes: 22 additions & 5 deletions scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ type Scanner struct {
userData *cgoHandle
}

// Creates a new error that includes information a about the rule
// causing the error.
func (s *Scanner) newScanError(code C.int) error {
if code == C.ERROR_SUCCESS {
return nil
}
err := Error{Code: int(code)}
if rule := s.GetLastErrorRule(); rule != nil {
err.RuleIdentifier = rule.Identifier()
err.Namespace = rule.Namespace()
}
if str := s.GetLastErrorString(); str != nil {
err.StringIdentifier = str.Identifier()
}
return err
}

// NewScanner creates a YARA scanner.
func NewScanner(r *Rules) (*Scanner, error) {
var yrScanner *C.YR_SCANNER
Expand Down Expand Up @@ -153,7 +170,7 @@ func (s *Scanner) ScanMem(buf []byte) (err error) {
}
s.putCallbackData()
C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback))
err = newError(C.yr_scanner_scan_mem(
err = s.newScanError(C.yr_scanner_scan_mem(
s.cptr,
ptr,
C.size_t(len(buf))))
Expand All @@ -176,7 +193,7 @@ func (s *Scanner) ScanFile(filename string) (err error) {
defer C.free(unsafe.Pointer(cfilename))
s.putCallbackData()
C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback))
err = newError(C.yr_scanner_scan_file(
err = s.newScanError(C.yr_scanner_scan_file(
s.cptr,
cfilename,
))
Expand All @@ -191,7 +208,7 @@ func (s *Scanner) ScanFile(filename string) (err error) {
func (s *Scanner) ScanFileDescriptor(fd uintptr) (err error) {
s.putCallbackData()
C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback))
err = newError(C._yr_scanner_scan_fd(
err = s.newScanError(C._yr_scanner_scan_fd(
s.cptr,
C.int(fd),
))
Expand All @@ -206,7 +223,7 @@ func (s *Scanner) ScanFileDescriptor(fd uintptr) (err error) {
func (s *Scanner) ScanProc(pid int) (err error) {
s.putCallbackData()
C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback))
err = newError(C.yr_scanner_scan_proc(
err = s.newScanError(C.yr_scanner_scan_proc(
s.cptr,
C.int(pid),
))
Expand All @@ -226,7 +243,7 @@ func (s *Scanner) ScanMemBlocks(mbi MemoryBlockIterator) (err error) {
defer ((*cgoHandle)(cmbi.context)).Delete()
s.putCallbackData()
C.yr_scanner_set_flags(s.cptr, s.flags.withReportFlags(s.Callback)|C.SCAN_FLAGS_NO_TRYCATCH)
err = newError(C.yr_scanner_scan_mem_blocks(
err = s.newScanError(C.yr_scanner_scan_mem_blocks(
s.cptr,
cmbi,
))
Expand Down

0 comments on commit fdb067a

Please sign in to comment.