2020-08-18 19:51:02 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module ImportExport
|
|
|
|
class DecompressedArchiveSizeValidator
|
|
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
|
|
|
|
DEFAULT_MAX_BYTES = 10.gigabytes.freeze
|
2021-12-11 22:18:48 +05:30
|
|
|
TIMEOUT_LIMIT = 210.seconds
|
2020-08-18 19:51:02 +05:30
|
|
|
|
2022-07-01 11:34:44 +05:30
|
|
|
ServiceError = Class.new(StandardError)
|
|
|
|
|
2020-08-18 19:51:02 +05:30
|
|
|
def initialize(archive_path:, max_bytes: self.class.max_bytes)
|
|
|
|
@archive_path = archive_path
|
|
|
|
@max_bytes = max_bytes
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid?
|
|
|
|
strong_memoize(:valid) do
|
|
|
|
validate
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.max_bytes
|
|
|
|
DEFAULT_MAX_BYTES
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def validate
|
2022-08-27 11:52:29 +05:30
|
|
|
pgrps = nil
|
2021-03-11 19:13:27 +05:30
|
|
|
valid_archive = true
|
2020-08-18 19:51:02 +05:30
|
|
|
|
2022-07-01 11:34:44 +05:30
|
|
|
validate_archive_path
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
Timeout.timeout(TIMEOUT_LIMIT) do
|
2022-08-27 11:52:29 +05:30
|
|
|
stderr_r, stderr_w = IO.pipe
|
|
|
|
stdout, wait_threads = Open3.pipeline_r(*command, pgroup: true, err: stderr_w )
|
2021-09-04 01:27:46 +05:30
|
|
|
|
|
|
|
# When validation is performed on a small archive (e.g. 100 bytes)
|
|
|
|
# `wait_thr` finishes before we can get process group id. Do not
|
|
|
|
# raise exception in this scenario.
|
2022-08-27 11:52:29 +05:30
|
|
|
pgrps = wait_threads.map do |wait_thr|
|
2021-09-04 01:27:46 +05:30
|
|
|
Process.getpgid(wait_thr[:pid])
|
|
|
|
rescue Errno::ESRCH
|
|
|
|
nil
|
|
|
|
end
|
2022-08-27 11:52:29 +05:30
|
|
|
pgrps.compact!
|
2021-09-04 01:27:46 +05:30
|
|
|
|
2022-08-27 11:52:29 +05:30
|
|
|
status = wait_threads.last.value
|
2020-08-18 19:51:02 +05:30
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
if status.success?
|
|
|
|
result = stdout.readline
|
2020-08-18 19:51:02 +05:30
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
if result.to_i > @max_bytes
|
|
|
|
valid_archive = false
|
2020-08-18 19:51:02 +05:30
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
log_error('Decompressed archive size limit reached')
|
|
|
|
end
|
|
|
|
else
|
|
|
|
valid_archive = false
|
|
|
|
|
|
|
|
log_error(stderr.readline)
|
2020-08-18 19:51:02 +05:30
|
|
|
end
|
2021-03-11 19:13:27 +05:30
|
|
|
|
|
|
|
ensure
|
|
|
|
stdout.close
|
2022-08-27 11:52:29 +05:30
|
|
|
stderr_w.close
|
|
|
|
stderr_r.close
|
2020-08-18 19:51:02 +05:30
|
|
|
end
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
valid_archive
|
|
|
|
rescue Timeout::Error
|
|
|
|
log_error('Timeout reached during archive decompression')
|
2020-08-18 19:51:02 +05:30
|
|
|
|
2022-08-27 11:52:29 +05:30
|
|
|
pgrps.each { |pgrp| Process.kill(-1, pgrp) } if pgrps
|
2020-08-18 19:51:02 +05:30
|
|
|
|
|
|
|
false
|
2021-06-08 01:23:25 +05:30
|
|
|
rescue StandardError => e
|
2021-03-11 19:13:27 +05:30
|
|
|
log_error(e.message)
|
2020-08-18 19:51:02 +05:30
|
|
|
|
2022-08-27 11:52:29 +05:30
|
|
|
pgrps.each { |pgrp| Process.kill(-1, pgrp) } if pgrps
|
2021-03-11 19:13:27 +05:30
|
|
|
|
|
|
|
false
|
2020-08-18 19:51:02 +05:30
|
|
|
end
|
|
|
|
|
2022-07-01 11:34:44 +05:30
|
|
|
def validate_archive_path
|
|
|
|
Gitlab::Utils.check_path_traversal!(@archive_path)
|
|
|
|
|
|
|
|
raise(ServiceError, 'Archive path is not a string') unless @archive_path.is_a?(String)
|
|
|
|
raise(ServiceError, 'Archive path is a symlink') if File.lstat(@archive_path).symlink?
|
|
|
|
raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
|
|
|
|
end
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
def command
|
2022-08-27 11:52:29 +05:30
|
|
|
[['gzip', '-dc', @archive_path], ['wc', '-c']]
|
2020-08-18 19:51:02 +05:30
|
|
|
end
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
def log_error(error)
|
2022-07-01 11:34:44 +05:30
|
|
|
archive_size = begin
|
|
|
|
File.size(@archive_path)
|
|
|
|
rescue StandardError
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2021-03-11 19:13:27 +05:30
|
|
|
Gitlab::Import::Logger.info(
|
|
|
|
message: error,
|
|
|
|
import_upload_archive_path: @archive_path,
|
2022-07-01 11:34:44 +05:30
|
|
|
import_upload_archive_size: archive_size
|
2021-03-11 19:13:27 +05:30
|
|
|
)
|
2020-08-18 19:51:02 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|