diff --git a/compiler.go b/compiler.go index 824d258..ccbbc32 100644 --- a/compiler.go +++ b/compiler.go @@ -10,6 +10,11 @@ package yara #include #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*); @@ -17,6 +22,7 @@ void freeCallback(char*, void*); import "C" import ( "errors" + "fmt" "os" "reflect" "runtime" @@ -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. diff --git a/compiler_test.go b/compiler_test.go index c520f07..fc23319 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -6,7 +6,9 @@ package yara -import "testing" +import ( + "testing" +) func TestCompiler(t *testing.T) { c, _ := NewCompiler() @@ -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 " + 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 { diff --git a/error.go b/error.go index e178327..c5ed9f5 100644 --- a/error.go +++ b/error.go @@ -8,25 +8,94 @@ package yara // #include 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", diff --git a/scanner.go b/scanner.go index f4b3f5e..f9f06ef 100644 --- a/scanner.go +++ b/scanner.go @@ -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 @@ -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)))) @@ -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, )) @@ -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), )) @@ -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), )) @@ -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, ))