From 00c89373b87b4bb503699675ade70febb677b810 Mon Sep 17 00:00:00 2001 From: Cocoa Date: Thu, 22 Oct 2026 22:55:16 +0900 Subject: [PATCH] fix: reject truncated native file paths --- Makefile | 2 +- Makefile.win | 2 +- c_src/stb_image_nif.c | 9 +++++++-- mix.exs | 3 ++- test/stb_image_path_validation_test.exs | 15 +++++++++++++++ 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 test/stb_image_path_validation_test.exs diff --git a/Makefile b/Makefile index 80f1a64..7eb0c89 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,6 @@ endif build: $(STB_IMAGE_NIF_SO) @ echo > /dev/null -$(STB_IMAGE_NIF_SO): +$(STB_IMAGE_NIF_SO): $(C_SRC)/stb_image_nif.c $(C_SRC)/nif_utils.h $(wildcard $(STB_INCLUDE_DIR)/*.h) @ mkdir -p $(PRIV_DIR) $(CC) $(CPPFLAGS) $(C_SRC)/stb_image_nif.c -o $(STB_IMAGE_NIF_SO) diff --git a/Makefile.win b/Makefile.win index 5ae3a4e..caa4b74 100644 --- a/Makefile.win +++ b/Makefile.win @@ -9,7 +9,7 @@ CPPFLAGS = /O2 /EHsc /I"$(ERTS_INCLUDE_DIR)" /I"$(STB_INCLUDE_DIR)" build: $(STB_IMAGE_NIF_SO) -$(STB_IMAGE_NIF_SO): +$(STB_IMAGE_NIF_SO): $(C_SRC)/stb_image_nif.c $(C_SRC)/nif_utils.h $(STB_INCLUDE_DIR)/stb_image.h $(STB_INCLUDE_DIR)/stb_image_write.h $(STB_INCLUDE_DIR)/stb_image_resize2.h @ if not exist "$(PRIV_DIR)" mkdir "$(PRIV_DIR)" $(CC) $(CPPFLAGS) /LD /MD /Fe$@ $(C_SRC)/stb_image_nif.c diff --git a/c_src/stb_image_nif.c b/c_src/stb_image_nif.c index d7b57c4..16d9326 100644 --- a/c_src/stb_image_nif.c +++ b/c_src/stb_image_nif.c @@ -15,6 +15,7 @@ #include #include #include +#include #define MAX_EXTNAME_LENGTH 4 @@ -56,7 +57,8 @@ static ERL_NIF_TERM read_file(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[ ERL_NIF_TERM ret; - if (!enif_inspect_binary(env, argv[0], &path)) { + if (!enif_inspect_binary(env, argv[0], &path) || path.size == SIZE_MAX || + (path.size > 0 && memchr(path.data, '\0', path.size) != NULL)) { return error(env, "invalid path"); } if(!enif_get_int(env, argv[1], &desired_channels)) { @@ -64,6 +66,7 @@ static ERL_NIF_TERM read_file(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[ } c_path = enif_alloc(path.size + 1); + if (c_path == NULL) return error(env, "out of memory"); memcpy(c_path, path.data, path.size); c_path[path.size] = '\0'; @@ -199,7 +202,8 @@ static ERL_NIF_TERM write_file(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv ErlNifBinary result; int w, h, comp; - if (!enif_inspect_binary(env, argv[0], &path)) { + if (!enif_inspect_binary(env, argv[0], &path) || path.size == SIZE_MAX || + (path.size > 0 && memchr(path.data, '\0', path.size) != NULL)) { return error(env, "invalid path"); } if (!enif_get_atom(env, argv[1], format, sizeof(format), ERL_NIF_LATIN1)) { @@ -219,6 +223,7 @@ static ERL_NIF_TERM write_file(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv } c_path = enif_alloc(path.size + 1); + if (c_path == NULL) return error(env, "out of memory"); memcpy(c_path, path.data, path.size); c_path[path.size] = '\0'; diff --git a/mix.exs b/mix.exs index aa2de21..ad27653 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule StbImage.MixProject do use Mix.Project @app :stb_image - @version "1.0.0" + @version "1.0.1-dev" @github_url "https://github.com/elixir-nx/stb_image" def project do @@ -16,6 +16,7 @@ defmodule StbImage.MixProject do docs: docs(), package: package(), compilers: [:elixir_make] ++ Mix.compilers(), + make_force_build: Mix.env() == :test, make_precompiler: {:nif, CCPrecompiler}, make_precompiler_url: "#{@github_url}/releases/download/v#{@version}/@{artefact_filename}", make_precompiler_filename: "stb_image_nif", diff --git a/test/stb_image_path_validation_test.exs b/test/stb_image_path_validation_test.exs new file mode 100644 index 0000000..5894929 --- /dev/null +++ b/test/stb_image_path_validation_test.exs @@ -0,0 +1,15 @@ +defmodule StbImagePathValidationTest do + use ExUnit.Case, async: true + + alias StbImage.Nif + + @tag :tmp_dir + test "paths containing null bytes are rejected without truncation", %{tmp_dir: tmp_dir} do + source = Path.join(__DIR__, "test.png") + destination = Path.join(tmp_dir, "image.png") + + assert {:error, _} = Nif.read_file(source <> <<0>> <> "suffix", 0) + assert {:error, _} = Nif.write_file(destination <> <<0>> <> "suffix", :png, <<0>>, 1, 1, 1) + refute File.exists?(destination) + end +end