// Copyright 2018 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "snapshot/sanitized/memory_snapshot_sanitized.h"

#include <string.h>

#include "util/linux/pac_helper.h"

namespace crashpad {
namespace internal {

namespace {

class MemorySanitizer : public MemorySnapshot::Delegate {
 public:
  MemorySanitizer(MemorySnapshot::Delegate* delegate,
                  RangeSet* ranges,
                  VMAddress address,
                  bool is_64_bit)
      : delegate_(delegate),
        ranges_(ranges),
        address_(address),
        is_64_bit_(is_64_bit) {}

  MemorySanitizer(const MemorySanitizer&) = delete;
  MemorySanitizer& operator=(const MemorySanitizer&) = delete;

  ~MemorySanitizer() = default;

  bool MemorySnapshotDelegateRead(void* data, size_t size) override {
    if (is_64_bit_) {
      Sanitize<uint64_t>(data, size);
    } else {
      Sanitize<uint32_t>(data, size);
    }
    return delegate_->MemorySnapshotDelegateRead(data, size);
  }

 private:
  template <typename Pointer>
  void Sanitize(void* data, size_t size) {
    const Pointer defaced =
        static_cast<Pointer>(MemorySnapshotSanitized::kDefaced);

    // Sanitize up to a word-aligned address.
    const size_t aligned_offset =
        ((address_ + sizeof(Pointer) - 1) & ~(sizeof(Pointer) - 1)) - address_;
    memcpy(data, &defaced, aligned_offset);

    // Sanitize words that aren't small and don't look like pointers.
    size_t word_count = (size - aligned_offset) / sizeof(Pointer);
    auto words =
        reinterpret_cast<Pointer*>(static_cast<char*>(data) + aligned_offset);
    for (size_t index = 0; index < word_count; ++index) {
      auto word = StripPACBits(words[index]);
      if (word > MemorySnapshotSanitized::kSmallWordMax &&
          !ranges_->Contains(word)) {
        words[index] = defaced;
      }
    }

    // Sanitize trailing bytes beyond the word-sized items.
    const size_t sanitized_bytes =
        aligned_offset + word_count * sizeof(Pointer);
    memcpy(static_cast<char*>(data) + sanitized_bytes,
           &defaced,
           size - sanitized_bytes);
  }

  MemorySnapshot::Delegate* delegate_;
  RangeSet* ranges_;
  VMAddress address_;
  bool is_64_bit_;
};

}  // namespace

MemorySnapshotSanitized::MemorySnapshotSanitized(const MemorySnapshot* snapshot,
                                                 RangeSet* ranges,
                                                 bool is_64_bit)
    : snapshot_(snapshot), ranges_(ranges), is_64_bit_(is_64_bit) {}

MemorySnapshotSanitized::~MemorySnapshotSanitized() = default;

uint64_t MemorySnapshotSanitized::Address() const {
  return snapshot_->Address();
}

size_t MemorySnapshotSanitized::Size() const {
  return snapshot_->Size();
}

bool MemorySnapshotSanitized::Read(Delegate* delegate) const {
  MemorySanitizer sanitizer(delegate, ranges_, Address(), is_64_bit_);
  return snapshot_->Read(&sanitizer);
}

}  // namespace internal
}  // namespace crashpad
