diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-03 00:59:19 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-03 00:59:19 +0300 |
commit | 1385478346704d03ab9d3a9bf8ae3812cea0b6b5 (patch) | |
tree | c2b68728119200c48fbfe09bb09397d4e31659b7 /lib/gitlab | |
parent | 361d9dae8bafae8c830d68d16ea0f76482ba9343 (diff) |
Add latest changes from gitlab-org/security/gitlab@16-0-stable-ee
Diffstat (limited to 'lib/gitlab')
9 files changed, 200 insertions, 38 deletions
diff --git a/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.html.haml b/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.html.haml index d8f7466a1ca..53d64fdf488 100644 --- a/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.html.haml +++ b/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.html.haml @@ -3,16 +3,16 @@ Dear GitLab user, %p - As part of our commitment to keeping GitLab secure, we have identified and addressed a vulnerability in GitLab that allowed some users to bypass the email verification process in a #{link_to("recent security release", "https://about.gitlab.com/releases/2020/05/27/security-release-13-0-1-released", target: '_blank')}. + As part of our commitment to keeping GitLab secure, we have identified and addressed a vulnerability in GitLab that allowed some users to bypass the email verification process in a #{link_to('recent security release', 'https://about.gitlab.com/releases/2020/05/27/security-release-13-0-1-released', target: '_blank')}. %p As a precautionary measure, you will need to re-verify some of your account's email addresses before continuing to use GitLab. Sorry for the inconvenience! %p - We have already sent the re-verification email with a subject line of "Confirmation instructions" from #{@verification_from_mail}. Please feel free to contribute any questions or comments to #{link_to("this issue", "https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/7942", target: '_blank')}. + We have already sent the re-verification email with a subject line of 'Confirmation instructions' from #{@verification_from_mail}. Please feel free to contribute any questions or comments to #{link_to('this issue', 'https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/7942', target: '_blank')}. %p - If you are not "#{@user.username}", please #{link_to 'report this to our administrator', new_abuse_report_url(user_id: @user.id)} + If you are not "#{@user.username}", please report abuse from the user's #{link_to('profile page', user_url(@user.id), target: '_blank', rel: 'noopener noreferrer')}. #{link_to('Learn more.', help_page_url('user/report_abuse', anchor: 'report-abuse-from-the-users-profile-page', target: '_blank', rel: 'noopener noreferrer'))} %p Thank you for being a GitLab user! diff --git a/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.text.erb b/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.text.erb index d20af9b9803..81ef68e99f0 100644 --- a/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.text.erb +++ b/lib/gitlab/background_migration/mailers/views/unconfirm_mailer/unconfirm_notification_email.text.erb @@ -9,6 +9,8 @@ As a precautionary measure, you will need to re-verify some of your account's em We have already sent the re-verification email with a subject line of "Confirmation instructions" from <%= @verification_from_mail %>. Please feel free to contribute any questions or comments to this issue: https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/7942 -If you are not "<%= @user.username %>", please report this to our administrator. Report link: <%= new_abuse_report_url(user_id: @user.id) %> +If you are not "<%= @user.username %>", please report abuse from the user's profile page: <%= user_url(@user.id) %>. + +Learn more: <%= help_page_url('user/report_abuse', anchor: 'report-abuse-from-the-users-profile-page') %> Thank you for being a GitLab user! diff --git a/lib/gitlab/checks/tag_check.rb b/lib/gitlab/checks/tag_check.rb index 007a775eaf5..5c43ca946b5 100644 --- a/lib/gitlab/checks/tag_check.rb +++ b/lib/gitlab/checks/tag_check.rb @@ -10,7 +10,8 @@ module Gitlab 'Only a project maintainer or owner can delete a protected tag.', delete_protected_tag_non_web: 'You can only delete protected tags using the web interface.', create_protected_tag: 'You are not allowed to create this tag as it is protected.', - default_branch_collision: 'You cannot use default branch name to create a tag' + default_branch_collision: 'You cannot use default branch name to create a tag', + prohibited_tag_name: 'You cannot create a tag with a prohibited pattern.' }.freeze LOG_MESSAGES = { @@ -29,11 +30,20 @@ module Gitlab end default_branch_collision_check + prohibited_tag_checks protected_tag_checks end private + def prohibited_tag_checks + return if deletion? + + if tag_name.start_with?("refs/tags/") # rubocop: disable Style/GuardClause + raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_tag_name] + end + end + def protected_tag_checks logger.log_timed(LOG_MESSAGES[__method__]) do return unless ProtectedTag.protected?(project, tag_name) # rubocop:disable Cop/AvoidReturnFromBlocks diff --git a/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator.rb b/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator.rb new file mode 100644 index 00000000000..04930da5e30 --- /dev/null +++ b/lib/gitlab/ci/artifacts/decompressed_artifact_size_validator.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Artifacts + class DecompressedArtifactSizeValidator + DEFAULT_MAX_BYTES = 4.gigabytes.freeze + + FILE_FORMAT_VALIDATORS = { + gzip: ::Gitlab::Ci::DecompressedGzipSizeValidator + }.freeze + + FileDecompressionError = Class.new(StandardError) + + def initialize(file:, file_format:, max_bytes: DEFAULT_MAX_BYTES) + @file = file + @file_path = file&.path + @file_format = file_format + @max_bytes = max_bytes + end + + def validate! + validator_class = FILE_FORMAT_VALIDATORS[file_format.to_sym] + + return if file_path.nil? + return if validator_class.nil? + + if file.respond_to?(:object_store) && file.object_store == ObjectStorage::Store::REMOTE + return if valid_on_storage?(validator_class) + elsif validator_class.new(archive_path: file_path, max_bytes: max_bytes).valid? + return + end + + raise(FileDecompressionError, 'File decompression error') + end + + private + + attr_reader :file_path, :file, :file_format, :max_bytes + + def valid_on_storage?(validator_class) + temp_filename = "#{SecureRandom.uuid}.gz" + + is_valid = false + Tempfile.open(temp_filename, '/tmp') do |tempfile| + tempfile.binmode + ::Faraday.get(file.url) do |req| + req.options.on_data = proc { |chunk, _| tempfile.write(chunk) } + end + + is_valid = validator_class.new(archive_path: tempfile.path, max_bytes: max_bytes).valid? + tempfile.unlink + end + + is_valid + end + end + end + end +end diff --git a/lib/gitlab/ci/decompressed_gzip_size_validator.rb b/lib/gitlab/ci/decompressed_gzip_size_validator.rb new file mode 100644 index 00000000000..a92f3007671 --- /dev/null +++ b/lib/gitlab/ci/decompressed_gzip_size_validator.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class DecompressedGzipSizeValidator + DEFAULT_MAX_BYTES = 4.gigabytes.freeze + TIMEOUT_LIMIT = 210.seconds + + ServiceError = Class.new(StandardError) + + def initialize(archive_path:, max_bytes: DEFAULT_MAX_BYTES) + @archive_path = archive_path + @max_bytes = max_bytes + end + + def valid? + validate + end + + private + + def validate + pgrps = nil + valid_archive = true + + validate_archive_path + + Timeout.timeout(TIMEOUT_LIMIT) do + stderr_r, stderr_w = IO.pipe + stdout, wait_threads = Open3.pipeline_r(*command, pgroup: true, err: stderr_w) + + # 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. + pgrps = wait_threads.map do |wait_thr| + Process.getpgid(wait_thr[:pid]) + rescue Errno::ESRCH + nil + end + pgrps.compact! + + status = wait_threads.last.value + + if status.success? + result = stdout.readline + + valid_archive = false if result.to_i > max_bytes + else + valid_archive = false + end + + ensure + stdout.close + stderr_w.close + stderr_r.close + end + + valid_archive + rescue StandardError + pgrps.each { |pgrp| Process.kill(-1, pgrp) } if pgrps + + false + end + + def validate_archive_path + Gitlab::Utils.check_path_traversal!(archive_path) + + 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 + + def command + [['gzip', '-dc', archive_path], ['wc', '-c']] + end + + attr_reader :archive_path, :max_bytes + end + end +end diff --git a/lib/gitlab/front_matter.rb b/lib/gitlab/front_matter.rb index 093501e860b..2a759434b81 100644 --- a/lib/gitlab/front_matter.rb +++ b/lib/gitlab/front_matter.rb @@ -8,15 +8,35 @@ module Gitlab ';;;' => 'json' }.freeze - DELIM = Regexp.union(DELIM_LANG.keys) + DELIM_UNTRUSTED = "(?:#{Gitlab::FrontMatter::DELIM_LANG.keys.map { |x| RE2::Regexp.escape(x) }.join('|')})".freeze - PATTERN = %r{ - \A(?<encoding>[^\r\n]*coding:[^\r\n]*\R)? # optional encoding line - (?<before>\s*) - ^(?<delim>#{DELIM})[ \t]*(?<lang>\S*)\R # opening front matter marker (optional language specifier) - (?<front_matter>.*?) # front matter block content (not greedy) - ^(\k<delim> | \.{3}) # closing front matter marker - [^\S\r\n]*(\R|\z) - }mx.freeze + # Original pattern: + # \A(?<encoding>[^\r\n]*coding:[^\r\n]*\R)? # optional encoding line + # (?<before>\s*) + # ^(?<delim>#{DELIM})[ \t]*(?<lang>\S*)\R # opening front matter marker (optional language specifier) + # (?<front_matter>.*?) # front matter block content (not greedy) + # ^(\k<delim> | \.{3}) # closing front matter marker + # [^\S\r\n]*(\R|\z) + # rubocop:disable Style/StringConcatenation + # rubocop:disable Style/LineEndConcatenation + PATTERN_UNTRUSTED = + # optional encoding line + "\\A(?P<encoding>[^\\r\\n]*coding:[^\\r\\n]*#{::Gitlab::UntrustedRegexp::BACKSLASH_R})?" + + '(?P<before>\s*)' + + + # opening front matter marker (optional language specifier) + "^(?P<delim>#{DELIM_UNTRUSTED})[ \\t]*(?P<lang>\\S*)#{::Gitlab::UntrustedRegexp::BACKSLASH_R}" + + + # front matter block content (not greedy) + '(?P<front_matter>(?:\n|.)*?)' + + + # closing front matter marker + "^((?P<delim_closing>#{DELIM_UNTRUSTED})|\\.{3})" + + "[^\\S\\r\\n]*(#{::Gitlab::UntrustedRegexp::BACKSLASH_R}|\\z)" + # rubocop:enable Style/LineEndConcatenation + # rubocop:enable Style/StringConcatenation + + PATTERN_UNTRUSTED_REGEX = + Gitlab::UntrustedRegexp.new(PATTERN_UNTRUSTED, multiline: true) end end diff --git a/lib/gitlab/import_export/json/streaming_serializer.rb b/lib/gitlab/import_export/json/streaming_serializer.rb index 9bb0770dc90..2c64ca53f76 100644 --- a/lib/gitlab/import_export/json/streaming_serializer.rb +++ b/lib/gitlab/import_export/json/streaming_serializer.rb @@ -133,29 +133,10 @@ module Gitlab def authorized_record_json(record, options) include_keys = options[:include].flat_map(&:keys) keys_to_authorize = record.try(:restricted_associations, include_keys) - return record.to_json(options) if keys_to_authorize.blank? - - record_hash = record.as_json(options).with_indifferent_access - filtered_record_hash(record, keys_to_authorize, record_hash).to_json(options) - end - - def filtered_record_hash(record, keys_to_authorize, record_hash) - keys_to_authorize.each do |key| - next unless record_hash[key].present? - - readable = record.try(:readable_records, key, current_user: current_user) - if record.has_many_association?(key) - readable_ids = readable.pluck(:id) - record_hash[key].keep_if do |association_record| - readable_ids.include?(association_record[:id]) - end - else - record_hash[key] = nil unless readable.present? - end - end + return record.to_json(options) if keys_to_authorize.blank? - record_hash + record.to_authorized_json(keys_to_authorize, current_user, options) end def batch(relation, key) diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb index fe3377dae68..150f50e4da6 100644 --- a/lib/gitlab/untrusted_regexp.rb +++ b/lib/gitlab/untrusted_regexp.rb @@ -16,6 +16,10 @@ module Gitlab class UntrustedRegexp require_dependency 're2' + # recreate Ruby's \R metacharacter + # https://ruby-doc.org/3.2.2/Regexp.html#class-Regexp-label-Character+Classes + BACKSLASH_R = '(\n|\v|\f|\r|\x{0085}|\x{2028}|\x{2029}|\r\n)' + delegate :===, :source, to: :regexp def initialize(pattern, multiline: false) diff --git a/lib/gitlab/wiki_pages/front_matter_parser.rb b/lib/gitlab/wiki_pages/front_matter_parser.rb index 071b0dde619..dbe848f0acf 100644 --- a/lib/gitlab/wiki_pages/front_matter_parser.rb +++ b/lib/gitlab/wiki_pages/front_matter_parser.rb @@ -53,7 +53,7 @@ module Gitlab include Gitlab::Utils::StrongMemoize def initialize(delim = nil, lang = '', text = nil) - @lang = lang.downcase.presence || Gitlab::FrontMatter::DELIM_LANG[delim] + @lang = lang&.downcase.presence || Gitlab::FrontMatter::DELIM_LANG[delim] @text = text&.strip! end @@ -109,11 +109,17 @@ module Gitlab end def parse_front_matter_block - wiki_content.match(Gitlab::FrontMatter::PATTERN) { |m| Block.new(m[:delim], m[:lang], m[:front_matter]) } || Block.new + if match = Gitlab::FrontMatter::PATTERN_UNTRUSTED_REGEX.match(wiki_content) + Block.new(match[:delim], match[:lang], match[:front_matter]) + else + Block.new + end end def strip_front_matter_block - wiki_content.gsub(Gitlab::FrontMatter::PATTERN, '') + Gitlab::FrontMatter::PATTERN_UNTRUSTED_REGEX.replace_gsub(wiki_content) do + '' + end end end end |