mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
Currently, the Yara test case ("yara/test-yara-scan.sh") fails, with the
following obscure error message:
> ><fs> yara-scan /text.txt
> libguestfs: error: deserialise_yara_detection_list:
Namely, the Yara rule match list serialization / de-serialization, between
the daemon and the library, is broken. It is caused by the following
incompatible pointer passing (i.e., undefined behavior), in function
do_internal_yara_scan(), file "daemon/yara.c":
> r = yr_rules_scan_fd (rules, fd, 0, yara_rules_callback, (void *) path, 0);
^^^^^^^^^^^^^^^^^^^
The prototype of yara_rules_callback() is:
> static int
> yara_rules_callback (int code, void *message, void *data)
however, in Yara commit 2b121b166d25 ("Track string matches using
YR_SCAN_CONTEXT.", 2020-02-27), which was included in the upstream v4.0.0
release, the rules callback prototype was changed as follows:
> diff --git a/libyara/include/yara/types.h b/libyara/include/yara/types.h
> index cad095cd70c2..f415033c4aa6 100644
> --- a/libyara/include/yara/types.h
> +++ b/libyara/include/yara/types.h
> @@ -661,6 +644,7 @@ struct YR_MEMORY_BLOCK_ITERATOR
>
>
> typedef int (*YR_CALLBACK_FUNC)(
> + YR_SCAN_CONTEXT* context,
> int message,
> void* message_data,
> void* user_data);
Therefore, the yara_rules_callback() function is entered with a mismatched
parameter list in the daemon, and so it passes garbage to
send_detection_info(), for serializing the match list.
This incompatible change was in fact documented by the Yara project:
https://github.com/VirusTotal/yara/wiki/Backward-incompatible-changes-in-YARA-4.0-API#scanning-callback
Gcc too warns about the incompatible pointer type, under
"-Wincompatible-pointer-types". However, libguestfs is built without
"-Werror" by default, so the warning is easy to miss, and the bug only
manifests at runtime.
(The same problem exists for yr_compiler_set_callback() /
compile_error_callback():
https://github.com/VirusTotal/yara/wiki/Backward-incompatible-changes-in-YARA-4.0-API#compiler-callback
except that this instance of the problem is not triggered by the test
case, as the rule list always compiles.)
Rather than simply fixing the parameter lists, consider the following
approach.
If Yara's YR_CALLBACK_FUNC and YR_COMPILER_CALLBACK_FUNC typedefs were not
for *pointer* types but actual *function* prototypes, then we could use
them directly in the declarations of our callback functions. Then any
future changes in the param lists would force a "conflicting types"
*compilation error* (not a warning). Illustration:
/* this is *not* a pointer type */
typedef int HELLO_FUNC (void);
/* function declarations */
static HELLO_FUNC my_hello_good;
static HELLO_FUNC my_hello_bad;
/* function definition, with explicit parameter list */
static int my_hello_good (void) { return 1; }
/* function definition with wrong param list -> compilation error */
static int my_hello_bad (int i) { return i; }
Unfortunately, given that the Yara-provided typedefs are already pointers,
we can't do this, in standard C anyway. Type derivation only allows for
array, structure, union, function, and pointer type derivation; it does
not allow "undoing" previous derivations.
However, using gcc's "typeof" keyword, the idea is possible. Given
YR_CALLBACK_FUNC, the expression
(YR_CALLBACK_FUNC)NULL
is a well-defined null pointer, and the function designator expression
*(YR_CALLBACK_FUNC)NULL
has the desired function type.
Of course, evaluating this expression would be undefined behavior, but in
the GCC extension expression
typeof (*(YR_CALLBACK_FUNC)NULL)
the operand of the "typeof" operator is never evaluated, as it does not
have a variably modified type. We can therefore use this "typeof" in the
same role as HELLO_FUNC had in the above example.
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Message-Id: <20211011223627.20856-4-lersek@redhat.com>
Acked-by: Richard W.M. Jones <rjones@redhat.com>
[lersek@redhat.com: clean up whitespace in "YR_RULE *rule"]
328 lines
7.4 KiB
C
328 lines
7.4 KiB
C
/* libguestfs - the guestfsd daemon
|
|
* Copyright (C) 2016 Red Hat Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdbool.h>
|
|
#include <rpc/types.h>
|
|
#include <rpc/xdr.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "daemon.h"
|
|
#include "actions.h"
|
|
#include "optgroups.h"
|
|
#include "guestfs_protocol.h"
|
|
|
|
#ifdef HAVE_YARA
|
|
|
|
#include <yara.h>
|
|
|
|
#ifdef HAVE_ATTRIBUTE_CLEANUP
|
|
#define CLEANUP_DESTROY_YARA_COMPILER \
|
|
__attribute__((cleanup(cleanup_destroy_yara_compiler)))
|
|
#else
|
|
#define CLEANUP_DESTROY_YARA_COMPILER
|
|
#endif
|
|
|
|
struct write_callback_data {
|
|
int fd;
|
|
uint64_t written;
|
|
};
|
|
|
|
/* Yara compiled rules. */
|
|
static YR_RULES *rules = NULL;
|
|
static bool initialized = false;
|
|
|
|
static int compile_rules_file (const char *);
|
|
static void cleanup_destroy_yara_compiler (void *ptr);
|
|
static int send_detection_info (const char *, YR_RULE *);
|
|
|
|
/* Typedefs that effectively strip the pointer derivation from Yara's
|
|
* YR_CALLBACK_FUNC and YR_COMPILER_CALLBACK_FUNC types, using GCC's "typeof"
|
|
* extension.
|
|
*/
|
|
typedef typeof (*(YR_CALLBACK_FUNC)NULL) guestfs_yr_callback;
|
|
typedef typeof (*(YR_COMPILER_CALLBACK_FUNC)NULL) guestfs_yr_compiler_callback;
|
|
|
|
/* Declarations of our callback functions expressed in terms of Yara's
|
|
* typedefs. Note: these are *function declarations*.
|
|
*/
|
|
static guestfs_yr_callback yara_rules_callback;
|
|
static guestfs_yr_compiler_callback compile_error_callback;
|
|
|
|
/* Has one FileIn parameter.
|
|
* Takes optional arguments, consult optargs_bitmask.
|
|
*/
|
|
int
|
|
do_yara_load (void)
|
|
{
|
|
int r;
|
|
CLEANUP_CLOSE int fd = -1;
|
|
char tmpfile[] = "/tmp/yaraXXXXXX";
|
|
|
|
fd = mkstemp (tmpfile);
|
|
if (fd == -1) {
|
|
reply_with_perror ("mkstemp");
|
|
return -1;
|
|
}
|
|
|
|
r = upload_to_fd (fd, tmpfile);
|
|
if (r == -1) {
|
|
unlink (tmpfile);
|
|
return -1;
|
|
}
|
|
|
|
/* Initialize yara only once. */
|
|
if (!initialized) {
|
|
r = yr_initialize ();
|
|
if (r != ERROR_SUCCESS) {
|
|
reply_with_error ("failed initializing yara");
|
|
unlink (tmpfile);
|
|
return -1;
|
|
}
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
/* Destroy previously loaded rules. */
|
|
if (rules != NULL) {
|
|
yr_rules_destroy (rules);
|
|
rules = NULL;
|
|
}
|
|
|
|
/* Try to load the rules as compiled.
|
|
* If their are in source code format, compile them first.
|
|
*/
|
|
r = yr_rules_load (tmpfile, &rules);
|
|
if (r == ERROR_INVALID_FILE)
|
|
r = compile_rules_file (tmpfile);
|
|
|
|
unlink (tmpfile);
|
|
|
|
return (r == ERROR_SUCCESS) ? 0 : -1;
|
|
}
|
|
|
|
int
|
|
do_yara_destroy (void)
|
|
{
|
|
if (rules == NULL) {
|
|
reply_with_error ("no yara rules loaded");
|
|
return -1;
|
|
}
|
|
|
|
yr_rules_destroy (rules);
|
|
rules = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Has one FileOut parameter. */
|
|
int
|
|
do_internal_yara_scan (const char *path)
|
|
{
|
|
int r;
|
|
CLEANUP_CLOSE int fd = -1;
|
|
|
|
if (rules == NULL) {
|
|
reply_with_error ("no yara rules loaded");
|
|
return -1;
|
|
}
|
|
|
|
CHROOT_IN;
|
|
fd = open (path, O_RDONLY|O_CLOEXEC);
|
|
CHROOT_OUT;
|
|
|
|
if (fd == -1) {
|
|
reply_with_perror ("%s", path);
|
|
return -1;
|
|
}
|
|
|
|
reply (NULL, NULL); /* Reply message. */
|
|
|
|
r = yr_rules_scan_fd (rules, fd, 0, yara_rules_callback, (void *) path, 0);
|
|
if (r == ERROR_SUCCESS)
|
|
r = send_file_end (0); /* File transfer end. */
|
|
else
|
|
send_file_end (1); /* Cancel file transfer. */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Compile source code rules and load them.
|
|
* Return ERROR_SUCCESS on success, Yara error code type on error.
|
|
*/
|
|
static int
|
|
compile_rules_file (const char *rules_path)
|
|
{
|
|
int ret;
|
|
CLEANUP_FCLOSE FILE *rule_file = NULL;
|
|
CLEANUP_DESTROY_YARA_COMPILER YR_COMPILER *compiler = NULL;
|
|
|
|
ret = yr_compiler_create (&compiler);
|
|
if (ret != ERROR_SUCCESS) {
|
|
reply_with_error ("yr_compiler_create");
|
|
return ret;
|
|
}
|
|
|
|
yr_compiler_set_callback (compiler, compile_error_callback, NULL);
|
|
|
|
rule_file = fopen (rules_path, "r");
|
|
if (rule_file == NULL) {
|
|
reply_with_error ("unable to open rules file");
|
|
ret = ERROR_COULD_NOT_OPEN_FILE;
|
|
goto err;
|
|
}
|
|
|
|
ret = yr_compiler_add_file (compiler, rule_file, NULL, NULL);
|
|
if (ret > 0) {
|
|
reply_with_error ("found %d errors when compiling the rules", ret);
|
|
goto err;
|
|
}
|
|
|
|
ret = yr_compiler_get_rules (compiler, &rules);
|
|
if (ret == ERROR_INSUFICIENT_MEMORY) {
|
|
errno = ENOMEM;
|
|
reply_with_perror ("yr_compiler_get_rules");
|
|
}
|
|
|
|
err:
|
|
#ifndef HAVE_ATTRIBUTE_CLEANUP
|
|
yr_compiler_destroy (compiler);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Yara compilation error callback.
|
|
* Reports back the compilation error message.
|
|
* Prints compilation warnings if verbose.
|
|
*/
|
|
static void
|
|
compile_error_callback (int level, const char *name, int line,
|
|
const YR_RULE *rule, const char *message, void *data)
|
|
{
|
|
if (level == YARA_ERROR_LEVEL_ERROR)
|
|
fprintf (stderr, "Yara error (line %d): %s\n", line, message);
|
|
else if (verbose)
|
|
fprintf (stderr, "Yara warning (line %d): %s\n", line, message);
|
|
}
|
|
|
|
/* Yara scan callback, called by yr_rules_scan_file.
|
|
* Return 0 on success, -1 on error.
|
|
*/
|
|
static int
|
|
yara_rules_callback (YR_SCAN_CONTEXT *context, int code, void *message,
|
|
void *data)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (code == CALLBACK_MSG_RULE_MATCHING)
|
|
ret = send_detection_info ((const char *)data, (YR_RULE *) message);
|
|
|
|
return (ret == 0) ? CALLBACK_CONTINUE : CALLBACK_ERROR;
|
|
}
|
|
|
|
/* Serialize file path and rule name and send it out.
|
|
* Return 0 on success, -1 on error.
|
|
*/
|
|
static int
|
|
send_detection_info (const char *name, YR_RULE *rule)
|
|
{
|
|
XDR xdr;
|
|
int r;
|
|
size_t len;
|
|
CLEANUP_FREE char *buf = NULL;
|
|
struct guestfs_int_yara_detection detection;
|
|
|
|
detection.yara_name = (char *) name;
|
|
detection.yara_rule = (char *) rule->identifier;
|
|
|
|
/* Serialize detection struct. */
|
|
buf = malloc (GUESTFS_MAX_CHUNK_SIZE);
|
|
if (buf == NULL) {
|
|
perror ("malloc");
|
|
return -1;
|
|
}
|
|
|
|
xdrmem_create (&xdr, buf, GUESTFS_MAX_CHUNK_SIZE, XDR_ENCODE);
|
|
|
|
r = xdr_guestfs_int_yara_detection (&xdr, &detection);
|
|
if (r == 0) {
|
|
perror ("xdr_guestfs_int_yara_detection");
|
|
return -1;
|
|
}
|
|
|
|
len = xdr_getpos (&xdr);
|
|
|
|
xdr_destroy (&xdr);
|
|
|
|
/* Send serialised yara_detection out. */
|
|
return send_file_write (buf, len);
|
|
}
|
|
|
|
/* Clean up yara handle on daemon exit. */
|
|
void yara_finalize (void) __attribute__((destructor));
|
|
|
|
void
|
|
yara_finalize (void)
|
|
{
|
|
int r;
|
|
|
|
if (!initialized)
|
|
return;
|
|
|
|
if (rules != NULL) {
|
|
yr_rules_destroy (rules);
|
|
rules = NULL;
|
|
}
|
|
|
|
r = yr_finalize ();
|
|
if (r != ERROR_SUCCESS)
|
|
perror ("yr_finalize");
|
|
|
|
initialized = false;
|
|
}
|
|
|
|
static void
|
|
cleanup_destroy_yara_compiler (void *ptr)
|
|
{
|
|
YR_COMPILER *compiler = * (YR_COMPILER **) ptr;
|
|
|
|
if (compiler != NULL)
|
|
yr_compiler_destroy (compiler);
|
|
}
|
|
|
|
int
|
|
optgroup_libyara_available (void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
#else /* !HAVE_YARA */
|
|
|
|
OPTGROUP_LIBYARA_NOT_AVAILABLE
|
|
|
|
#endif /* !HAVE_YARA */
|