mirror of
https://github.com/discourse/discourse.git
synced 2026-08-04 10:39:43 +08:00
- Update all imagemagick calls to go through a new `::Imagemagick` wrapper, which wraps the command in `Discourse::SafeExec` - Patch image_optim to force its calls through `SafeExec` This provides robust defense-in-depth against vulnerabilities in image processing binaries. Landlock is supported on Linux Kernel 5.13 and above.
62 lines
2.3 KiB
Ruby
Vendored
62 lines
2.3 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
|
|
|
|
def self.asset_read_paths
|
|
@asset_read_paths ||= [Rails.root.join("vendor").to_s, ENV["MAGICK_CONFIGURE_PATH"]].compact
|
|
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
|