0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-11 02:59:07 +08:00
discourse/lib/image_magick.rb
discoursebot 1a90a5ab5e
DEV: Remove /etc from default SafeExec paths [backport 2026.7] (#42467)
Backport of #42466 to release/2026.7.

---

`/etc` can sometimes include sensitive information. Better to list
specific files/directories for each use-case.

Co-authored-by: David Taylor <david@taylorhq.com>
2026-08-10 13:48:03 +01:00

67 lines
2.4 KiB
Ruby
Vendored

# frozen_string_literal: true
require "tmpdir"
# Runs ImageMagick under the Landlock sandbox (via Discourse::SafeExec) so a
# decoder bug parsing an untrusted upload is confined to an explicit filesystem
# allowlist with no network, not the full rights of the calling process.
module ImageMagick
# memory_bytes sits above the policy.xml ceiling (memory 1GiB + map 2GiB) so a
# legitimate ≤128MP decode is not killed; MALLOC_ARENA_MAX bounds per-thread
# arenas that would otherwise inflate address space against it. cpu_seconds is
# a runaway backstop above the wall-clock timeout.
RLIMITS = {
cpu_seconds: 300,
memory_bytes: 4 * 1024 * 1024 * 1024,
file_size_bytes: 10 * 1024 * 1024 * 1024,
open_files: 1024,
}.freeze
DEFAULT_TIMEOUT = 30
FONTCONFIG_READ_PATHS = %w[/etc/fonts].freeze
def self.asset_read_paths
@asset_read_paths ||=
Discourse::SafeExec.existing_paths(
[Rails.root.join("vendor").to_s, ENV["MAGICK_CONFIGURE_PATH"], *FONTCONFIG_READ_PATHS],
)
end
def self.magick(*args, read: [], write: [], timeout: nil, nice: nil, failure_message: "")
command = ["magick", *args]
command = ["nice", "-n", nice.to_s, *command] if nice
run(*command, read:, write:, timeout:, failure_message:)
end
def self.identify(*args, read: [], write: [], timeout: nil, failure_message: "")
run("identify", *args, read:, write:, timeout:, failure_message:)
end
def self.run(*command, read:, write:, timeout:, failure_message:)
# A private scratch dir keeps ImageMagick's disk-backed pixel cache inside
# the write allowlist, so large images that spill to disk still succeed.
Dir.mktmpdir("discourse-imagemagick-") do |scratch|
Discourse::SafeExec.capture(
*command,
env: {
**ENV.slice("PATH", "MAGICK_CONFIGURE_PATH", "LANG", "LC_ALL"),
"MAGICK_TEMPORARY_PATH" => scratch,
"TMPDIR" => scratch,
"HOME" => scratch,
"XDG_CACHE_HOME" => scratch,
"MALLOC_ARENA_MAX" => "2",
},
unsetenv_others: true,
read: [*Discourse::SafeExec.default_read_paths, *asset_read_paths, *read],
write: [scratch, *write],
execute: Discourse::SafeExec.default_execute_paths,
timeout: timeout || DEFAULT_TIMEOUT,
rlimits: RLIMITS,
failure_message:,
seccomp_deny_network: true,
)
end
end
private_class_method :run
end