From 80d19917c7af63a66c35b753d9f1f34426012459 Mon Sep 17 00:00:00 2001 From: Tom Kedem <6370957+vToMy@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:41:01 +0300 Subject: [PATCH] Add disableIncludes() support to YaraCompiler Adds the ability to disable YARA include directives during rule compilation by calling yr_compiler_set_include_callback with a NULL callback. This is a security-relevant feature that prevents rules from using include directives to read arbitrary files from disk. Changes span all layers: - YaraCompiler interface: new disableIncludes() method - Embedded mode: JNI binding to yr_compiler_set_include_callback - External mode: stub with warning log (not supported in CLI mode) - Native wrapper: yara_compiler_set_null_include_callback() C function - Test: verifies compilation fails when includes are disabled Co-Authored-By: Claude Opus 4.6 (1M context) --- .../github/subreption/yara/YaraCompiler.java | 5 ++ .../yara/embedded/YaraCompilerImpl.java | 11 ++++ .../subreption/yara/embedded/YaraLibrary.java | 9 ++++ .../yara/external/YaraCompilerImpl.java | 5 ++ src/main/native-package/src/yara-wrapper.h | 8 +++ .../yara/embedded/YaraCompilerImplTest.java | 52 +++++++++++++++++++ 6 files changed, 90 insertions(+) diff --git a/src/main/java/com/github/subreption/yara/YaraCompiler.java b/src/main/java/com/github/subreption/yara/YaraCompiler.java index 45aa918..0c02ae7 100644 --- a/src/main/java/com/github/subreption/yara/YaraCompiler.java +++ b/src/main/java/com/github/subreption/yara/YaraCompiler.java @@ -28,6 +28,11 @@ public interface YaraCompiler extends AutoCloseable { */ void setCallback(YaraCompilationCallback cbk); + /** + * Instruct the compiler not to allow includes in files + */ + void disableIncludes(); + /** * Add rules content * diff --git a/src/main/java/com/github/subreption/yara/embedded/YaraCompilerImpl.java b/src/main/java/com/github/subreption/yara/embedded/YaraCompilerImpl.java index e926203..d18a357 100644 --- a/src/main/java/com/github/subreption/yara/embedded/YaraCompilerImpl.java +++ b/src/main/java/com/github/subreption/yara/embedded/YaraCompilerImpl.java @@ -96,6 +96,17 @@ public void setCallback(YaraCompilationCallback cbk) { library.compilerSetCallback(peer, callBackAddress, 0); } + /** + * Instructs the compiler to disallow the usage of includes in rules files. + * Compilation will fail if include is present. + * Internally it sets includes handling callback to null. + * https://yara.readthedocs.io/en/stable/capi.html#c.yr_compiler_set_include_callback + */ + @Override + public void disableIncludes() { + library.compilerDisableIncludes(peer, 0); + } + /** * Release compiler instance * @throws Exception diff --git a/src/main/java/com/github/subreption/yara/embedded/YaraLibrary.java b/src/main/java/com/github/subreption/yara/embedded/YaraLibrary.java index 194bc06..e84475f 100644 --- a/src/main/java/com/github/subreption/yara/embedded/YaraLibrary.java +++ b/src/main/java/com/github/subreption/yara/embedded/YaraLibrary.java @@ -80,6 +80,15 @@ public void compilerSetCallback(long compiler, long callback, long data) { yr_compiler_set_callback(compiler, callback, data); } + private final native void yara_compiler_set_null_include_callback( + @JniArg(cast = "YR_COMPILER*") long compiler, + @JniArg(cast = "void *") long data + ); + public void compilerDisableIncludes(long compiler, long data) { + Preconditions.checkState(library != null); + yara_compiler_set_null_include_callback(compiler, data); + } + private final native int yr_compiler_add_string( @JniArg(cast = "YR_COMPILER *") long compiler, String rules, diff --git a/src/main/java/com/github/subreption/yara/external/YaraCompilerImpl.java b/src/main/java/com/github/subreption/yara/external/YaraCompilerImpl.java index 7a39abe..5fd9fc4 100644 --- a/src/main/java/com/github/subreption/yara/external/YaraCompilerImpl.java +++ b/src/main/java/com/github/subreption/yara/external/YaraCompilerImpl.java @@ -67,6 +67,11 @@ public void setCallback(YaraCompilationCallback cbk) { this.callback = cbk; } + @Override + public void disableIncludes() { + logger.warn("Disabling includes is not supported in external mode"); + } + @Override public void addRulesContent(String content, String namespace) { Boolean deleteImmediately = false; diff --git a/src/main/native-package/src/yara-wrapper.h b/src/main/native-package/src/yara-wrapper.h index bfa3f43..d2b48c9 100644 --- a/src/main/native-package/src/yara-wrapper.h +++ b/src/main/native-package/src/yara-wrapper.h @@ -220,6 +220,14 @@ yara_compiler_add_file(JNIEnv *env, void *compiler, const char *path, const char return ret; } +// Disable includes by passing NULL include callback +// https://yara.readthedocs.io/en/stable/capi.html#c.yr_compiler_set_include_callback +static void +yara_compiler_set_null_include_callback(void *compiler, void* user_data) { + + yr_compiler_set_include_callback((YR_COMPILER*)compiler, NULL, NULL, user_data); +} + /* * Module functions */ diff --git a/src/test/java/com/github/subreption/yara/embedded/YaraCompilerImplTest.java b/src/test/java/com/github/subreption/yara/embedded/YaraCompilerImplTest.java index 6ccdc96..95fb7a8 100644 --- a/src/test/java/com/github/subreption/yara/embedded/YaraCompilerImplTest.java +++ b/src/test/java/com/github/subreption/yara/embedded/YaraCompilerImplTest.java @@ -19,6 +19,7 @@ import java.io.File; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -27,6 +28,7 @@ import org.junit.jupiter.api.AfterEach; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -163,6 +165,56 @@ public void onError(ErrorLevel errorLevel, String fileName, long lineNumber, Str assertTrue(called.get()); } + private static final String YARA_RULE_INCLUDE = "include \"${filePathToInclude}\"\n" + + "rule HiThere\n" + + "{\n" + + " strings:\n" + + " $a = \"Hi there\"\n" + + " condition:\n" + + " $a\n" + + "}\n"; + + @Test + public void testAddRulesContentFailsOnIncludesDisabled() throws Exception { + Path tempFile = Files.createTempFile("yara-rule-test", ".yara"); + try { + Files.write(tempFile, YARA_RULE_HELLO.getBytes(StandardCharsets.UTF_8)); + + final AtomicBoolean called = new AtomicBoolean(); + YaraCompilationCallback callback; + + // Don't disable includes - compilation should pass + callback = (errorLevel, fileName, lineNumber, message) -> { + called.set(true); + logger.debug(String.format("Compilation failed in %s at %d: %s", fileName, lineNumber, message)); + }; + try (YaraCompiler compiler = yara.createCompiler()) { + compiler.setCallback(callback); + compiler.addRulesContent(YARA_RULE_INCLUDE.replace("${filePathToInclude}", tempFile.toAbsolutePath().toString()), null); + } catch (YaraException e) { + fail("Exception not expected: " + e); + } + assertFalse(called.get()); + + // Disable includes - compilation should fail + callback = (errorLevel, fileName, lineNumber, message) -> { + called.set(true); + assertEquals(1, lineNumber); + assertEquals("includes are disabled", message); + }; + try (YaraCompiler compiler = yara.createCompiler()) { + compiler.setCallback(callback); + compiler.disableIncludes(); + compiler.addRulesContent(YARA_RULE_INCLUDE.replace("${filePathToInclude}", tempFile.toAbsolutePath().toString()), null); + fail(); + } catch (YaraException e) { + } + assertTrue(called.get()); + } finally { + Files.deleteIfExists(tempFile); + } + } + @Test public void testAddRulePackageSucceeds() throws Exception { YaraCompilationCallback callback = new YaraCompilationCallback() {