From 00d536d9e2c6c76d88724e5c75cc82a857519c65 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 30 Jan 2025 15:50:50 +0100 Subject: [PATCH] backports: Copy mkdir_p TOCTOU fix from elixir PR 14242 See: https://github.com/elixir-lang/elixir/pull/14242 --- changelog.d/toctou-mkdir.fix | 1 + lib/pleroma/backports.ex | 72 ++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 changelog.d/toctou-mkdir.fix create mode 100644 lib/pleroma/backports.ex diff --git a/changelog.d/toctou-mkdir.fix b/changelog.d/toctou-mkdir.fix new file mode 100644 index 000000000..b070db1a0 --- /dev/null +++ b/changelog.d/toctou-mkdir.fix @@ -0,0 +1 @@ +Backport [Elixir PR 14242](https://github.com/elixir-lang/elixir/pull/14242) fixing racy mkdir and lack of error handling of parent directory creation \ No newline at end of file diff --git a/lib/pleroma/backports.ex b/lib/pleroma/backports.ex new file mode 100644 index 000000000..68cb7b990 --- /dev/null +++ b/lib/pleroma/backports.ex @@ -0,0 +1,72 @@ +# Copyright 2012 Plataformatec +# Copyright 2021 The Elixir Team +# SPDX-License-Identifier: Apache-2.0 + +defmodule Pleroma.Backports do + import File, only: [dir?: 1] + + # + # To be removed when we require Elixir 1.19 + @doc """ + Tries to create the directory `path`. + + Missing parent directories are created. Returns `:ok` if successful, or + `{:error, reason}` if an error occurs. + + Typical error reasons are: + + * `:eacces` - missing search or write permissions for the parent + directories of `path` + * `:enospc` - there is no space left on the device + * `:enotdir` - a component of `path` is not a directory + + """ + @spec mkdir_p(Path.t()) :: :ok | {:error, File.posix() | :badarg} + def mkdir_p(path) do + do_mkdir_p(IO.chardata_to_string(path)) + end + + defp do_mkdir_p("/") do + :ok + end + + defp do_mkdir_p(path) do + parent = Path.dirname(path) + + if parent == path do + :ok + else + case do_mkdir_p(parent) do + :ok -> + case :file.make_dir(path) do + {:error, :eexist} -> + if dir?(path), do: :ok, else: {:error, :enotdir} + + other -> + other + end + + e -> + e + end + end + end + + @doc """ + Same as `mkdir_p/1`, but raises a `File.Error` exception in case of failure. + Otherwise `:ok`. + """ + @spec mkdir_p!(Path.t()) :: :ok + def mkdir_p!(path) do + case mkdir_p(path) do + :ok -> + :ok + + {:error, reason} -> + raise File.Error, + reason: reason, + action: "make directory (with -p)", + path: IO.chardata_to_string(path) + end + end +end