From 528a51639033cc0fe3ce10973217d9483dfb6bcd Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 4 Jun 2023 18:48:34 -0500 Subject: [PATCH] Add more sandbox capabilities. Add more granularity to ffi sandbox capabilities - distinguish between using FFI functions, creating FFI functions, and creating executable memory. --- README.md | 31 +++++++++++++------------------ src/core/corelib.c | 6 ++++++ src/core/ffi.c | 20 ++++++++++---------- src/include/janet.h | 5 ++++- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index def0246e..8edabc7b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Janet logo -**Janet** is a dynamic language and bytecode interpreter for system scripting, expressive automation, and +**Janet** is a programming language for system scripting, expressive automation, and extending programs written in C or C++ with user scripting capabilities. There is a REPL for trying out the language, as well as the ability @@ -105,34 +105,31 @@ See the examples directory for all provided example programs. ## Use Cases Janet makes a good system scripting language, or a language to embed in other programs. -It's like Lua and Guile in that regard. It has more built-in functionality and a richer core language than +It's like Lua and GNU Guile in that regard. It has more built-in functionality and a richer core language than Lua, but smaller than GNU Guile or Python. However, it is much easier to embed and port than Python or Guile. -## Features +Some people use janet for sysadmin scripting, web development, or small video games. + +## Language Features * 600+ functions and macros in the core library * Built-in socket networking, threading, subprocesses, and file system functions. * Parsing Expression Grammars (PEG) engine as a more robust Regex alternative -* Macros +* Macros and compile-time computation * Per-thread event loop for efficient IO (epoll/IOCP/kqueue) -* Built-in C FFI lets you load existing binaries and run them. +* First-class green threads (continuations) as well as OS threads * Erlang-style supervision trees that integrate with the event loop -* Configurable at build time - turn features on or off for a smaller or more featureful build * First-class closures * Garbage collection -* First-class green threads (continuations) +* Distributed as janet.c and janet.h for embedding into a larger program. * Python-style generators (implemented as a plain macro) * Mutable and immutable arrays (array/tuple) * Mutable and immutable hashtables (table/struct) * Mutable and immutable strings (buffer/string) -* Multithreading -* Bytecode interpreter with an assembly interface, as well as bytecode verification -* Tail-call optimization -* Interface with C via abstract types and C functions -* Dynamically load C libraries -* REPL -* Embedding Janet in other programs -* Interactive environment with detailed stack traces +* Tail recursion +* Interface with C functions and dynamically load plugins ("natives"). +* Built-in C FFI for when the native bindings are too much work +* REPL development with debugger and inspectable runtime ## Documentation @@ -329,9 +326,7 @@ Gitter provides Matrix and IRC bridges as well. ### How fast is it? -Medium speed. - -In all seriousness, it is about the same speed as most interpreted languages without a JIT compiler. Tight, critical +It is about the same speed as most interpreted languages without a JIT compiler. Tight, critical loops should probably be written in C or C++ . Programs tend to be a bit faster than they would be in a language like Python due to the discouragement of slow Object-Oriented abstraction with lots of hash-table lookups, and making late-binding explicit. All values are boxed in an 8-byte diff --git a/src/core/corelib.c b/src/core/corelib.c index 0d72c118..741425a0 100644 --- a/src/core/corelib.c +++ b/src/core/corelib.c @@ -677,6 +677,9 @@ static const SandboxOption sandbox_options[] = { {"all", JANET_SANDBOX_ALL}, {"env", JANET_SANDBOX_ENV}, {"ffi", JANET_SANDBOX_FFI}, + {"ffi-define", JANET_SANDBOX_FFI_DEFINE}, + {"ffi-jit", JANET_SANDBOX_FFI_JIT}, + {"ffi-use", JANET_SANDBOX_FFI_USE}, {"fs", JANET_SANDBOX_FS}, {"fs-read", JANET_SANDBOX_FS_READ}, {"fs-temp", JANET_SANDBOX_FS_TEMP}, @@ -698,6 +701,9 @@ JANET_CORE_FN(janet_core_sandbox, "* :all - disallow all (except IO to stdout, stderr, and stdin)\n" "* :env - disallow reading and write env variables\n" "* :ffi - disallow FFI (recommended if disabling anything else)\n" + "* :ffi-define - disallow loading new FFI modules and binding new functions\n" + "* :ffi-jit - disallow calling `ffi/jitfn`\n" + "* :ffi-use - disallow using any previously bound FFI functions and memory-unsafe functions.\n" "* :fs - disallow access to the file system\n" "* :fs-read - disallow read access to the file system\n" "* :fs-temp - disallow creating temporary files\n" diff --git a/src/core/ffi.c b/src/core/ffi.c index ffd7301e..c61cc3da 100644 --- a/src/core/ffi.c +++ b/src/core/ffi.c @@ -1303,7 +1303,7 @@ JANET_CORE_FN(cfun_ffi_jitfn, "(ffi/jitfn bytes)", "Create an abstract type that can be used as the pointer argument to `ffi/call`. The content " "of `bytes` is architecture specific machine code that will be copied into executable memory.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_JIT); janet_fixarity(argc, 1); JanetByteView bytes = janet_getbytes(argv, 0); @@ -1356,7 +1356,7 @@ JANET_CORE_FN(cfun_ffi_call, "(ffi/call pointer signature & args)", "Call a raw pointer as a function pointer. The function signature specifies " "how Janet values in `args` are converted to native machine types.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_USE); janet_arity(argc, 2, -1); void *function_pointer = janet_ffi_get_callable_pointer(argv, 0); JanetFFISignature *signature = janet_getabstract(argv, 1, &janet_signature_type); @@ -1381,7 +1381,7 @@ JANET_CORE_FN(cfun_ffi_buffer_write, "Append a native type to a buffer such as it would appear in memory. This can be used " "to pass pointers to structs in the ffi, or send C/C++/native structs over the network " "or to files. Returns a modifed buffer or a new buffer if one is not supplied.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_USE); janet_arity(argc, 2, 4); JanetFFIType type = decode_ffi_type(argv[0]); uint32_t el_size = (uint32_t) type_size(type); @@ -1404,7 +1404,7 @@ JANET_CORE_FN(cfun_ffi_buffer_read, "Parse a native struct out of a buffer and convert it to normal Janet data structures. " "This function is the inverse of `ffi/write`. `bytes` can also be a raw pointer, although " "this is unsafe.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_USE); janet_arity(argc, 2, 3); JanetFFIType type = decode_ffi_type(argv[0]); size_t offset = (size_t) janet_optnat(argv, argc, 2, 0); @@ -1451,7 +1451,7 @@ JANET_CORE_FN(janet_core_raw_native, " or run any code from it. This is different than `native`, which will " "run initialization code to get a module table. If `path` is nil, opens the current running binary. " "Returns a `core/native`.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_DEFINE); janet_arity(argc, 0, 1); const char *path = janet_optcstring(argv, argc, 0, NULL); Clib lib = load_clib(path); @@ -1467,7 +1467,7 @@ JANET_CORE_FN(janet_core_native_lookup, "(ffi/lookup native symbol-name)", "Lookup a symbol from a native object. All symbol lookups will return a raw pointer " "if the symbol is found, else nil.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_DEFINE); janet_fixarity(argc, 2); JanetAbstractNative *anative = janet_getabstract(argv, 0, &janet_native_type); const char *sym = janet_getcstring(argv, 1); @@ -1481,7 +1481,7 @@ JANET_CORE_FN(janet_core_native_close, "(ffi/close native)", "Free a native object. Dereferencing pointers to symbols in the object will have undefined " "behavior after freeing.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_DEFINE); janet_fixarity(argc, 1); JanetAbstractNative *anative = janet_getabstract(argv, 0, &janet_native_type); if (anative->closed) janet_panic("native object already closed"); @@ -1494,7 +1494,7 @@ JANET_CORE_FN(janet_core_native_close, JANET_CORE_FN(cfun_ffi_malloc, "(ffi/malloc size)", "Allocates memory directly using the janet memory allocator. Memory allocated in this way must be freed manually! Returns a raw pointer, or nil if size = 0.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_USE); janet_fixarity(argc, 1); size_t size = janet_getsize(argv, 0); if (size == 0) return janet_wrap_nil(); @@ -1504,7 +1504,7 @@ JANET_CORE_FN(cfun_ffi_malloc, JANET_CORE_FN(cfun_ffi_free, "(ffi/free pointer)", "Free memory allocated with `ffi/malloc`. Returns nil.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_USE); janet_fixarity(argc, 1); if (janet_checktype(argv[0], JANET_NIL)) return janet_wrap_nil(); void *pointer = janet_getpointer(argv, 0); @@ -1519,7 +1519,7 @@ JANET_CORE_FN(cfun_ffi_pointer_buffer, "to be manipulated with buffer functions. Attempts to resize or extend the buffer " "beyond its initial capacity will raise an error. As with many FFI functions, this is memory " "unsafe and can potentially allow out of bounds memory access. Returns a new buffer.") { - janet_sandbox_assert(JANET_SANDBOX_FFI); + janet_sandbox_assert(JANET_SANDBOX_FFI_USE); janet_arity(argc, 2, 4); void *pointer = janet_getpointer(argv, 0); int32_t capacity = janet_getnat(argv, 1); diff --git a/src/include/janet.h b/src/include/janet.h index 1064380b..40f1a0fe 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1809,13 +1809,16 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr #define JANET_SANDBOX_SUBPROCESS 2 #define JANET_SANDBOX_NET_CONNECT 4 #define JANET_SANDBOX_NET_LISTEN 8 -#define JANET_SANDBOX_FFI 16 +#define JANET_SANDBOX_FFI_DEFINE 16 #define JANET_SANDBOX_FS_WRITE 32 #define JANET_SANDBOX_FS_READ 64 #define JANET_SANDBOX_HRTIME 128 #define JANET_SANDBOX_ENV 256 #define JANET_SANDBOX_DYNAMIC_MODULES 512 #define JANET_SANDBOX_FS_TEMP 1024 +#define JANET_SANDBOX_FFI_USE 2048 +#define JANET_SANDBOX_FFI_JIT 4096 +#define JANET_SANDBOX_FFI (JANET_SANDBOX_FFI_DEFINE | JANET_SANDBOX_FFI_USE | JANET_SANDBOX_FFI_JIT) #define JANET_SANDBOX_FS (JANET_SANDBOX_FS_WRITE | JANET_SANDBOX_FS_READ | JANET_SANDBOX_FS_TEMP) #define JANET_SANDBOX_NET (JANET_SANDBOX_NET_CONNECT | JANET_SANDBOX_NET_LISTEN) #define JANET_SANDBOX_ALL (UINT32_MAX)