diff options
Diffstat (limited to 'security/sandbox/chromium/sandbox')
221 files changed, 47022 insertions, 0 deletions
diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc b/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc new file mode 100644 index 0000000000..fed6368db6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl.cc @@ -0,0 +1,343 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" + +#include <stddef.h> +#include <stdint.h> + +#include <limits> + +#include "base/logging.h" +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/errorcode.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" + +namespace sandbox { +namespace bpf_dsl { +namespace { + +class ReturnResultExprImpl : public internal::ResultExprImpl { + public: + explicit ReturnResultExprImpl(uint32_t ret) : ret_(ret) {} + ~ReturnResultExprImpl() override {} + + CodeGen::Node Compile(PolicyCompiler* pc) const override { + return pc->Return(ret_); + } + + bool IsAllow() const override { return IsAction(SECCOMP_RET_ALLOW); } + + bool IsDeny() const override { + return IsAction(SECCOMP_RET_ERRNO) || IsAction(SECCOMP_RET_KILL); + } + + private: + bool IsAction(uint32_t action) const { + return (ret_ & SECCOMP_RET_ACTION) == action; + } + + uint32_t ret_; + + DISALLOW_COPY_AND_ASSIGN(ReturnResultExprImpl); +}; + +class TrapResultExprImpl : public internal::ResultExprImpl { + public: + TrapResultExprImpl(TrapRegistry::TrapFnc func, const void* arg, bool safe) + : func_(func), arg_(arg), safe_(safe) { + DCHECK(func_); + } + ~TrapResultExprImpl() override {} + + CodeGen::Node Compile(PolicyCompiler* pc) const override { + return pc->Trap(func_, arg_, safe_); + } + + bool HasUnsafeTraps() const override { return safe_ == false; } + + bool IsDeny() const override { return true; } + + private: + TrapRegistry::TrapFnc func_; + const void* arg_; + bool safe_; + + DISALLOW_COPY_AND_ASSIGN(TrapResultExprImpl); +}; + +class IfThenResultExprImpl : public internal::ResultExprImpl { + public: + IfThenResultExprImpl(BoolExpr cond, + ResultExpr then_result, + ResultExpr else_result) + : cond_(std::move(cond)), + then_result_(std::move(then_result)), + else_result_(std::move(else_result)) {} + ~IfThenResultExprImpl() override {} + + CodeGen::Node Compile(PolicyCompiler* pc) const override { + // We compile the "then" and "else" expressions in separate statements so + // they have a defined sequencing. See https://crbug.com/529480. + CodeGen::Node then_node = then_result_->Compile(pc); + CodeGen::Node else_node = else_result_->Compile(pc); + return cond_->Compile(pc, then_node, else_node); + } + + bool HasUnsafeTraps() const override { + return then_result_->HasUnsafeTraps() || else_result_->HasUnsafeTraps(); + } + + private: + BoolExpr cond_; + ResultExpr then_result_; + ResultExpr else_result_; + + DISALLOW_COPY_AND_ASSIGN(IfThenResultExprImpl); +}; + +class ConstBoolExprImpl : public internal::BoolExprImpl { + public: + ConstBoolExprImpl(bool value) : value_(value) {} + ~ConstBoolExprImpl() override {} + + CodeGen::Node Compile(PolicyCompiler* pc, + CodeGen::Node then_node, + CodeGen::Node else_node) const override { + return value_ ? then_node : else_node; + } + + private: + bool value_; + + DISALLOW_COPY_AND_ASSIGN(ConstBoolExprImpl); +}; + +class MaskedEqualBoolExprImpl : public internal::BoolExprImpl { + public: + MaskedEqualBoolExprImpl(int argno, + size_t width, + uint64_t mask, + uint64_t value) + : argno_(argno), width_(width), mask_(mask), value_(value) {} + ~MaskedEqualBoolExprImpl() override {} + + CodeGen::Node Compile(PolicyCompiler* pc, + CodeGen::Node then_node, + CodeGen::Node else_node) const override { + return pc->MaskedEqual(argno_, width_, mask_, value_, then_node, else_node); + } + + private: + int argno_; + size_t width_; + uint64_t mask_; + uint64_t value_; + + DISALLOW_COPY_AND_ASSIGN(MaskedEqualBoolExprImpl); +}; + +class NegateBoolExprImpl : public internal::BoolExprImpl { + public: + explicit NegateBoolExprImpl(BoolExpr cond) : cond_(std::move(cond)) {} + ~NegateBoolExprImpl() override {} + + CodeGen::Node Compile(PolicyCompiler* pc, + CodeGen::Node then_node, + CodeGen::Node else_node) const override { + return cond_->Compile(pc, else_node, then_node); + } + + private: + BoolExpr cond_; + + DISALLOW_COPY_AND_ASSIGN(NegateBoolExprImpl); +}; + +class AndBoolExprImpl : public internal::BoolExprImpl { + public: + AndBoolExprImpl(BoolExpr lhs, BoolExpr rhs) + : lhs_(std::move(lhs)), rhs_(std::move(rhs)) {} + ~AndBoolExprImpl() override {} + + CodeGen::Node Compile(PolicyCompiler* pc, + CodeGen::Node then_node, + CodeGen::Node else_node) const override { + return lhs_->Compile(pc, rhs_->Compile(pc, then_node, else_node), + else_node); + } + + private: + BoolExpr lhs_; + BoolExpr rhs_; + + DISALLOW_COPY_AND_ASSIGN(AndBoolExprImpl); +}; + +class OrBoolExprImpl : public internal::BoolExprImpl { + public: + OrBoolExprImpl(BoolExpr lhs, BoolExpr rhs) + : lhs_(std::move(lhs)), rhs_(std::move(rhs)) {} + ~OrBoolExprImpl() override {} + + CodeGen::Node Compile(PolicyCompiler* pc, + CodeGen::Node then_node, + CodeGen::Node else_node) const override { + return lhs_->Compile(pc, then_node, + rhs_->Compile(pc, then_node, else_node)); + } + + private: + BoolExpr lhs_; + BoolExpr rhs_; + + DISALLOW_COPY_AND_ASSIGN(OrBoolExprImpl); +}; + +} // namespace + +namespace internal { + +bool ResultExprImpl::HasUnsafeTraps() const { + return false; +} + +bool ResultExprImpl::IsAllow() const { + return false; +} + +bool ResultExprImpl::IsDeny() const { + return false; +} + +uint64_t DefaultMask(size_t size) { + switch (size) { + case 4: + return std::numeric_limits<uint32_t>::max(); + case 8: + return std::numeric_limits<uint64_t>::max(); + default: + CHECK(false) << "Unimplemented DefaultMask case"; + return 0; + } +} + +BoolExpr ArgEq(int num, size_t size, uint64_t mask, uint64_t val) { + // If this is changed, update Arg<T>::EqualTo's static_cast rules + // accordingly. + CHECK(size == 4 || size == 8); + + return std::make_shared<MaskedEqualBoolExprImpl>(num, size, mask, val); +} + +} // namespace internal + +ResultExpr Allow() { + return std::make_shared<ReturnResultExprImpl>(SECCOMP_RET_ALLOW); +} + +ResultExpr Error(int err) { + CHECK(err >= ErrorCode::ERR_MIN_ERRNO && err <= ErrorCode::ERR_MAX_ERRNO); + return std::make_shared<ReturnResultExprImpl>(SECCOMP_RET_ERRNO + err); +} + +ResultExpr Kill() { + return std::make_shared<ReturnResultExprImpl>(SECCOMP_RET_KILL); +} + +ResultExpr Trace(uint16_t aux) { + return std::make_shared<ReturnResultExprImpl>(SECCOMP_RET_TRACE + aux); +} + +ResultExpr Trap(TrapRegistry::TrapFnc trap_func, const void* aux) { + return std::make_shared<TrapResultExprImpl>(trap_func, aux, true /* safe */); +} + +ResultExpr UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux) { + return std::make_shared<TrapResultExprImpl>(trap_func, aux, + false /* unsafe */); +} + +BoolExpr BoolConst(bool value) { + return std::make_shared<ConstBoolExprImpl>(value); +} + +BoolExpr Not(BoolExpr cond) { + return std::make_shared<NegateBoolExprImpl>(std::move(cond)); +} + +BoolExpr AllOf() { + return BoolConst(true); +} + +BoolExpr AllOf(BoolExpr lhs, BoolExpr rhs) { + return std::make_shared<AndBoolExprImpl>(std::move(lhs), std::move(rhs)); +} + +BoolExpr AnyOf() { + return BoolConst(false); +} + +BoolExpr AnyOf(BoolExpr lhs, BoolExpr rhs) { + return std::make_shared<OrBoolExprImpl>(std::move(lhs), std::move(rhs)); +} + +Elser If(BoolExpr cond, ResultExpr then_result) { + return Elser(nullptr).ElseIf(std::move(cond), std::move(then_result)); +} + +Elser::Elser(cons::List<Clause> clause_list) : clause_list_(clause_list) { +} + +Elser::Elser(const Elser& elser) : clause_list_(elser.clause_list_) { +} + +Elser::~Elser() { +} + +Elser Elser::ElseIf(BoolExpr cond, ResultExpr then_result) const { + return Elser(Cons(std::make_pair(std::move(cond), std::move(then_result)), + clause_list_)); +} + +ResultExpr Elser::Else(ResultExpr else_result) const { + // We finally have the default result expression for this + // if/then/else sequence. Also, we've already accumulated all + // if/then pairs into a list of reverse order (i.e., lower priority + // conditions are listed before higher priority ones). E.g., an + // expression like + // + // If(b1, e1).ElseIf(b2, e2).ElseIf(b3, e3).Else(e4) + // + // will have built up a list like + // + // [(b3, e3), (b2, e2), (b1, e1)]. + // + // Now that we have e4, we can walk the list and create a ResultExpr + // tree like: + // + // expr = e4 + // expr = (b3 ? e3 : expr) = (b3 ? e3 : e4) + // expr = (b2 ? e2 : expr) = (b2 ? e2 : (b3 ? e3 : e4)) + // expr = (b1 ? e1 : expr) = (b1 ? e1 : (b2 ? e2 : (b3 ? e3 : e4))) + // + // and end up with an appropriately chained tree. + + ResultExpr expr = std::move(else_result); + for (const Clause& clause : clause_list_) { + expr = std::make_shared<IfThenResultExprImpl>(clause.first, clause.second, + std::move(expr)); + } + return expr; +} + +} // namespace bpf_dsl +} // namespace sandbox + +namespace std { +template class shared_ptr<const sandbox::bpf_dsl::internal::BoolExprImpl>; +template class shared_ptr<const sandbox::bpf_dsl::internal::ResultExprImpl>; +} // namespace std diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h new file mode 100644 index 0000000000..555d820e01 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl.h @@ -0,0 +1,338 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ +#define SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/linux/bpf_dsl/cons.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/sandbox_export.h" + +// The sandbox::bpf_dsl namespace provides a domain-specific language +// to make writing BPF policies more expressive. In general, the +// object types all have value semantics (i.e., they can be copied +// around, returned from or passed to function calls, etc. without any +// surprising side effects), though not all support assignment. +// +// An idiomatic and demonstrative (albeit silly) example of this API +// would be: +// +// #include "sandbox/linux/bpf_dsl/bpf_dsl.h" +// +// namespace dsl = sandbox::bpf_dsl; +// +// class SillyPolicy : public dsl::Policy { +// public: +// SillyPolicy() = default; +// SillyPolicy(const SillyPolicy&) = delete; +// SillyPolicy& operator=(const SillyPolicy&) = delete; +// ~SillyPolicy() override = default; +// +// dsl::ResultExpr EvaluateSyscall(int sysno) const override { +// if (sysno != __NR_fcntl) +// return dsl::Allow(); +// dsl::Arg<int> fd(0), cmd(1); +// dsl::Arg<unsigned long> flags(2); +// constexpr uint64_t kGoodFlags = O_ACCMODE | O_NONBLOCK; +// return dsl::If(dsl::AllOf(fd == 0, +// cmd == F_SETFL, +// (flags & ~kGoodFlags) == 0), +// dsl::Allow()) +// .dsl::ElseIf(dsl::AnyOf(cmd == F_DUPFD, cmd == F_DUPFD_CLOEXEC), +// dsl::Error(EMFILE)) +// .dsl::Else(dsl::Trap(SetFlagHandler, nullptr)); +// } +// }; +// +// More generally, the DSL currently supports the following grammar: +// +// result = Allow() | Error(errno) | Kill() | Trace(aux) +// | Trap(trap_func, aux) | UnsafeTrap(trap_func, aux) +// | If(bool, result)[.ElseIf(bool, result)].Else(result) +// | Switch(arg)[.Case(val, result)].Default(result) +// bool = BoolConst(boolean) | Not(bool) | AllOf(bool...) | AnyOf(bool...) +// | arg == val | arg != val +// arg = Arg<T>(num) | arg & mask +// +// The semantics of each function and operator are intended to be +// intuitive, but are described in more detail below. +// +// (Credit to Sean Parent's "Inheritance is the Base Class of Evil" +// talk at Going Native 2013 for promoting value semantics via shared +// pointers to immutable state.) + +namespace sandbox { +namespace bpf_dsl { + +template <typename T> +class Caser; + +class Elser; + +// ResultExpr is an opaque reference to an immutable result expression tree. +using ResultExpr = std::shared_ptr<const internal::ResultExprImpl>; + +// BoolExpr is an opaque reference to an immutable boolean expression tree. +using BoolExpr = std::shared_ptr<const internal::BoolExprImpl>; + +// Allow specifies a result that the system call should be allowed to +// execute normally. +SANDBOX_EXPORT ResultExpr Allow(); + +// Error specifies a result that the system call should fail with +// error number |err|. As a special case, Error(0) will result in the +// system call appearing to have succeeded, but without having any +// side effects. +SANDBOX_EXPORT ResultExpr Error(int err); + +// Kill specifies a result to kill the process (task) immediately. +SANDBOX_EXPORT ResultExpr Kill(); + +// Trace specifies a result to notify a tracing process via the +// PTRACE_EVENT_SECCOMP event and allow it to change or skip the system call. +// The value of |aux| will be available to the tracer via PTRACE_GETEVENTMSG. +SANDBOX_EXPORT ResultExpr Trace(uint16_t aux); + +// Trap specifies a result that the system call should be handled by +// trapping back into userspace and invoking |trap_func|, passing +// |aux| as the second parameter. +SANDBOX_EXPORT ResultExpr + Trap(TrapRegistry::TrapFnc trap_func, const void* aux); + +// UnsafeTrap is like Trap, except the policy is marked as "unsafe" +// and allowed to use SandboxSyscall to invoke any system call. +// +// NOTE: This feature, by definition, disables all security features of +// the sandbox. It should never be used in production, but it can be +// very useful to diagnose code that is incompatible with the sandbox. +// If even a single system call returns "UnsafeTrap", the security of +// entire sandbox should be considered compromised. +SANDBOX_EXPORT ResultExpr + UnsafeTrap(TrapRegistry::TrapFnc trap_func, const void* aux); + +// BoolConst converts a bool value into a BoolExpr. +SANDBOX_EXPORT BoolExpr BoolConst(bool value); + +// Not returns a BoolExpr representing the logical negation of |cond|. +SANDBOX_EXPORT BoolExpr Not(BoolExpr cond); + +// AllOf returns a BoolExpr representing the logical conjunction ("and") +// of zero or more BoolExprs. +SANDBOX_EXPORT BoolExpr AllOf(); +SANDBOX_EXPORT BoolExpr AllOf(BoolExpr lhs, BoolExpr rhs); +template <typename... Rest> +SANDBOX_EXPORT BoolExpr AllOf(BoolExpr first, Rest&&... rest); + +// AnyOf returns a BoolExpr representing the logical disjunction ("or") +// of zero or more BoolExprs. +SANDBOX_EXPORT BoolExpr AnyOf(); +SANDBOX_EXPORT BoolExpr AnyOf(BoolExpr lhs, BoolExpr rhs); +template <typename... Rest> +SANDBOX_EXPORT BoolExpr AnyOf(BoolExpr first, Rest&&... rest); + +template <typename T> +class SANDBOX_EXPORT Arg { + public: + // Initializes the Arg to represent the |num|th system call + // argument (indexed from 0), which is of type |T|. + explicit Arg(int num); + + Arg(const Arg& arg) : num_(arg.num_), mask_(arg.mask_) {} + + // Returns an Arg representing the current argument, but after + // bitwise-and'ing it with |rhs|. + friend Arg operator&(const Arg& lhs, uint64_t rhs) { + return Arg(lhs.num_, lhs.mask_ & rhs); + } + + // Returns a boolean expression comparing whether the system call argument + // (after applying any bitmasks, if appropriate) equals |rhs|. + friend BoolExpr operator==(const Arg& lhs, T rhs) { return lhs.EqualTo(rhs); } + + // Returns a boolean expression comparing whether the system call argument + // (after applying any bitmasks, if appropriate) does not equal |rhs|. + friend BoolExpr operator!=(const Arg& lhs, T rhs) { return Not(lhs == rhs); } + + private: + Arg(int num, uint64_t mask) : num_(num), mask_(mask) {} + + BoolExpr EqualTo(T val) const; + + int num_; + uint64_t mask_; + + DISALLOW_ASSIGN(Arg); +}; + +// If begins a conditional result expression predicated on the +// specified boolean expression. +SANDBOX_EXPORT Elser If(BoolExpr cond, ResultExpr then_result); + +class SANDBOX_EXPORT Elser { + public: + Elser(const Elser& elser); + ~Elser(); + + // ElseIf extends the conditional result expression with another + // "if then" clause, predicated on the specified boolean expression. + Elser ElseIf(BoolExpr cond, ResultExpr then_result) const; + + // Else terminates a conditional result expression using |else_result| as + // the default fallback result expression. + ResultExpr Else(ResultExpr else_result) const; + + private: + using Clause = std::pair<BoolExpr, ResultExpr>; + + explicit Elser(cons::List<Clause> clause_list); + + cons::List<Clause> clause_list_; + + friend Elser If(BoolExpr, ResultExpr); + template <typename T> + friend Caser<T> Switch(const Arg<T>&); + DISALLOW_ASSIGN(Elser); +}; + +// Switch begins a switch expression dispatched according to the +// specified argument value. +template <typename T> +SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg); + +template <typename T> +class SANDBOX_EXPORT Caser { + public: + Caser(const Caser<T>& caser) : arg_(caser.arg_), elser_(caser.elser_) {} + ~Caser() {} + + // Case adds a single-value "case" clause to the switch. + Caser<T> Case(T value, ResultExpr result) const; + + // Cases adds a multiple-value "case" clause to the switch. + // See also the SANDBOX_BPF_DSL_CASES macro below for a more idiomatic way + // of using this function. + template <typename... Values> + Caser<T> CasesImpl(ResultExpr result, const Values&... values) const; + + // Terminate the switch with a "default" clause. + ResultExpr Default(ResultExpr result) const; + + private: + Caser(const Arg<T>& arg, Elser elser) : arg_(arg), elser_(elser) {} + + Arg<T> arg_; + Elser elser_; + + template <typename U> + friend Caser<U> Switch(const Arg<U>&); + DISALLOW_ASSIGN(Caser); +}; + +// Recommended usage is to put +// #define CASES SANDBOX_BPF_DSL_CASES +// near the top of the .cc file (e.g., nearby any "using" statements), then +// use like: +// Switch(arg).CASES((3, 5, 7), result)...; +#define SANDBOX_BPF_DSL_CASES(values, result) \ + CasesImpl(result, SANDBOX_BPF_DSL_CASES_HELPER values) + +// Helper macro to strip parentheses. +#define SANDBOX_BPF_DSL_CASES_HELPER(...) __VA_ARGS__ + +// ===================================================================== +// Official API ends here. +// ===================================================================== + +namespace internal { + +// Make argument-dependent lookup work. This is necessary because although +// BoolExpr is defined in bpf_dsl, since it's merely a typedef for +// scoped_refptr<const internal::BoolExplImpl>, argument-dependent lookup only +// searches the "internal" nested namespace. +using bpf_dsl::Not; +using bpf_dsl::AllOf; +using bpf_dsl::AnyOf; + +// Returns a boolean expression that represents whether system call +// argument |num| of size |size| is equal to |val|, when masked +// according to |mask|. Users should use the Arg template class below +// instead of using this API directly. +SANDBOX_EXPORT BoolExpr + ArgEq(int num, size_t size, uint64_t mask, uint64_t val); + +// Returns the default mask for a system call argument of the specified size. +SANDBOX_EXPORT uint64_t DefaultMask(size_t size); + +} // namespace internal + +template <typename T> +Arg<T>::Arg(int num) + : num_(num), mask_(internal::DefaultMask(sizeof(T))) { +} + +// Definition requires ArgEq to have been declared. Moved out-of-line +// to minimize how much internal clutter users have to ignore while +// reading the header documentation. +// +// Additionally, we use this helper member function to avoid linker errors +// caused by defining operator== out-of-line. For a more detailed explanation, +// see http://www.parashift.com/c++-faq-lite/template-friends.html. +template <typename T> +BoolExpr Arg<T>::EqualTo(T val) const { + if (sizeof(T) == 4) { + // Prevent sign-extension of negative int32_t values. + return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint32_t>(val)); + } + return internal::ArgEq(num_, sizeof(T), mask_, static_cast<uint64_t>(val)); +} + +template <typename T> +SANDBOX_EXPORT Caser<T> Switch(const Arg<T>& arg) { + return Caser<T>(arg, Elser(nullptr)); +} + +template <typename T> +Caser<T> Caser<T>::Case(T value, ResultExpr result) const { + return SANDBOX_BPF_DSL_CASES((value), std::move(result)); +} + +template <typename T> +template <typename... Values> +Caser<T> Caser<T>::CasesImpl(ResultExpr result, const Values&... values) const { + // Theoretically we could evaluate arg_ just once and emit a more efficient + // dispatch table, but for now we simply translate into an equivalent + // If/ElseIf/Else chain. + + return Caser<T>(arg_, + elser_.ElseIf(AnyOf((arg_ == values)...), std::move(result))); +} + +template <typename T> +ResultExpr Caser<T>::Default(ResultExpr result) const { + return elser_.Else(std::move(result)); +} + +template <typename... Rest> +BoolExpr AllOf(BoolExpr first, Rest&&... rest) { + return AllOf(std::move(first), AllOf(std::forward<Rest>(rest)...)); +} + +template <typename... Rest> +BoolExpr AnyOf(BoolExpr first, Rest&&... rest) { + return AnyOf(std::move(first), AnyOf(std::forward<Rest>(rest)...)); +} + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl_forward.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl_forward.h new file mode 100644 index 0000000000..af1b48b407 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl_forward.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_BPF_DSL_FORWARD_H_ +#define SANDBOX_LINUX_BPF_DSL_BPF_DSL_FORWARD_H_ + +#include <memory> + +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +// The bpf_dsl_forward.h header provides forward declarations for the +// types defined in bpf_dsl.h. It's intended for use in user headers +// that need to reference bpf_dsl types, but don't require definitions. + +namespace internal { +class ResultExprImpl; +class BoolExprImpl; +} + +using ResultExpr = std::shared_ptr<const internal::ResultExprImpl>; +using BoolExpr = std::shared_ptr<const internal::BoolExprImpl>; + +} // namespace bpf_dsl +} // namespace sandbox + +namespace std { +extern template class SANDBOX_EXPORT + shared_ptr<const sandbox::bpf_dsl::internal::BoolExprImpl>; +extern template class SANDBOX_EXPORT + shared_ptr<const sandbox::bpf_dsl::internal::ResultExprImpl>; +} // namespace std + +#endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_FORWARD_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl_impl.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl_impl.h new file mode 100644 index 0000000000..f397321edd --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/bpf_dsl_impl.h @@ -0,0 +1,67 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_BPF_DSL_IMPL_H_ +#define SANDBOX_LINUX_BPF_DSL_BPF_DSL_IMPL_H_ + +#include <memory> + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { +class PolicyCompiler; + +namespace internal { + +// Internal interface implemented by BoolExpr implementations. +class BoolExprImpl { + public: + // Compile uses |pc| to emit a CodeGen::Node that conditionally continues + // to either |then_node| or |false_node|, depending on whether the represented + // boolean expression is true or false. + virtual CodeGen::Node Compile(PolicyCompiler* pc, + CodeGen::Node then_node, + CodeGen::Node else_node) const = 0; + + protected: + BoolExprImpl() {} + virtual ~BoolExprImpl() {} + + private: + DISALLOW_COPY_AND_ASSIGN(BoolExprImpl); +}; + +// Internal interface implemented by ResultExpr implementations. +class ResultExprImpl { + public: + // Compile uses |pc| to emit a CodeGen::Node that executes the + // represented result expression. + virtual CodeGen::Node Compile(PolicyCompiler* pc) const = 0; + + // HasUnsafeTraps returns whether the result expression is or recursively + // contains an unsafe trap expression. + virtual bool HasUnsafeTraps() const; + + // IsAllow returns whether the result expression is an "allow" result. + virtual bool IsAllow() const; + + // IsAllow returns whether the result expression is a "deny" result. + virtual bool IsDeny() const; + + protected: + ResultExprImpl() {} + virtual ~ResultExprImpl() {} + + private: + DISALLOW_COPY_AND_ASSIGN(ResultExprImpl); +}; + +} // namespace internal +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_BPF_DSL_IMPL_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/codegen.cc b/security/sandbox/chromium/sandbox/linux/bpf_dsl/codegen.cc new file mode 100644 index 0000000000..d88bd531a2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/codegen.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/codegen.h" + +#include <stddef.h> +#include <stdint.h> + +#include <limits> +#include <utility> + +#include "base/logging.h" +#include "sandbox/linux/system_headers/linux_filter.h" + +// This CodeGen implementation strives for simplicity while still +// generating acceptable BPF programs under typical usage patterns +// (e.g., by PolicyCompiler). +// +// The key to its simplicity is that BPF programs only support forward +// jumps/branches, which allows constraining the DAG construction API +// to make instruction nodes immutable. Immutable nodes admits a +// simple greedy approach of emitting new instructions as needed and +// then reusing existing ones that have already been emitted. This +// cleanly avoids any need to compute basic blocks or apply +// topological sorting because the API effectively sorts instructions +// for us (e.g., before MakeInstruction() can be called to emit a +// branch instruction, it must have already been called for each +// branch path). +// +// This greedy algorithm is not without (theoretical) weakness though: +// +// 1. In the general case, we don't eliminate dead code. If needed, +// we could trace back through the program in Compile() and elide +// any unneeded instructions, but in practice we only emit live +// instructions anyway. +// +// 2. By not dividing instructions into basic blocks and sorting, we +// lose an opportunity to move non-branch/non-return instructions +// adjacent to their successor instructions, which means we might +// need to emit additional jumps. But in practice, they'll +// already be nearby as long as callers don't go out of their way +// to interleave MakeInstruction() calls for unrelated code +// sequences. + +namespace sandbox { + +// kBranchRange is the maximum value that can be stored in +// sock_filter's 8-bit jt and jf fields. +const size_t kBranchRange = std::numeric_limits<uint8_t>::max(); + +const CodeGen::Node CodeGen::kNullNode; + +CodeGen::CodeGen() : program_(), equivalent_(), memos_() { +} + +CodeGen::~CodeGen() { +} + +CodeGen::Program CodeGen::Compile(CodeGen::Node head) { + return Program(program_.rbegin() + Offset(head), program_.rend()); +} + +CodeGen::Node CodeGen::MakeInstruction(uint16_t code, + uint32_t k, + Node jt, + Node jf) { + // To avoid generating redundant code sequences, we memoize the + // results from AppendInstruction(). + auto res = memos_.insert(std::make_pair(MemoKey(code, k, jt, jf), kNullNode)); + CodeGen::Node* node = &res.first->second; + if (res.second) { // Newly inserted memo entry. + *node = AppendInstruction(code, k, jt, jf); + } + return *node; +} + +CodeGen::Node CodeGen::AppendInstruction(uint16_t code, + uint32_t k, + Node jt, + Node jf) { + if (BPF_CLASS(code) == BPF_JMP) { + CHECK_NE(BPF_JA, BPF_OP(code)) << "CodeGen inserts JAs as needed"; + + // Optimally adding jumps is rather tricky, so we use a quick + // approximation: by artificially reducing |jt|'s range, |jt| will + // stay within its true range even if we add a jump for |jf|. + jt = WithinRange(jt, kBranchRange - 1); + jf = WithinRange(jf, kBranchRange); + return Append(code, k, Offset(jt), Offset(jf)); + } + + CHECK_EQ(kNullNode, jf) << "Non-branch instructions shouldn't provide jf"; + if (BPF_CLASS(code) == BPF_RET) { + CHECK_EQ(kNullNode, jt) << "Return instructions shouldn't provide jt"; + } else { + // For non-branch/non-return instructions, execution always + // proceeds to the next instruction; so we need to arrange for + // that to be |jt|. + jt = WithinRange(jt, 0); + CHECK_EQ(0U, Offset(jt)) << "ICE: Failed to setup next instruction"; + } + return Append(code, k, 0, 0); +} + +CodeGen::Node CodeGen::WithinRange(Node target, size_t range) { + // Just use |target| if it's already within range. + if (Offset(target) <= range) { + return target; + } + + // Alternatively, look for an equivalent instruction within range. + if (Offset(equivalent_.at(target)) <= range) { + return equivalent_.at(target); + } + + // Otherwise, fall back to emitting a jump instruction. + Node jump = Append(BPF_JMP | BPF_JA, Offset(target), 0, 0); + equivalent_.at(target) = jump; + return jump; +} + +CodeGen::Node CodeGen::Append(uint16_t code, uint32_t k, size_t jt, size_t jf) { + if (BPF_CLASS(code) == BPF_JMP && BPF_OP(code) != BPF_JA) { + CHECK_LE(jt, kBranchRange); + CHECK_LE(jf, kBranchRange); + } else { + CHECK_EQ(0U, jt); + CHECK_EQ(0U, jf); + } + + CHECK_LT(program_.size(), static_cast<size_t>(BPF_MAXINSNS)); + CHECK_EQ(program_.size(), equivalent_.size()); + + Node res = program_.size(); + program_.push_back(sock_filter{ + code, static_cast<uint8_t>(jt), static_cast<uint8_t>(jf), k}); + equivalent_.push_back(res); + return res; +} + +size_t CodeGen::Offset(Node target) const { + CHECK_LT(target, program_.size()) << "Bogus offset target node"; + return (program_.size() - 1) - target; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/codegen.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/codegen.h new file mode 100644 index 0000000000..3fc3f35a0d --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/codegen.h @@ -0,0 +1,119 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ +#define SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <tuple> +#include <vector> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +struct sock_filter; + +namespace sandbox { + +// The code generator implements a basic assembler that can convert a +// graph of BPF instructions into a well-formed array of BPF +// instructions. Most notably, it ensures that jumps are always +// forward and don't exceed the limit of 255 instructions imposed by +// the instruction set. +// +// Callers would typically create a new CodeGen object and then use it +// to build a DAG of instruction nodes. They'll eventually call +// Compile() to convert this DAG to a Program. +// +// CodeGen gen; +// CodeGen::Node allow, branch, dag; +// +// allow = +// gen.MakeInstruction(BPF_RET+BPF_K, +// ErrorCode(ErrorCode::ERR_ALLOWED).err())); +// branch = +// gen.MakeInstruction(BPF_JMP+BPF_EQ+BPF_K, __NR_getpid, +// Trap(GetPidHandler, NULL), allow); +// dag = +// gen.MakeInstruction(BPF_LD+BPF_W+BPF_ABS, +// offsetof(struct arch_seccomp_data, nr), branch); +// +// // Simplified code follows; in practice, it is important to avoid calling +// // any C++ destructors after starting the sandbox. +// CodeGen::Program program = gen.Compile(dag); +// const struct sock_fprog prog = { +// static_cast<unsigned short>(program.size()), &program[0] }; +// prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog); +// +class SANDBOX_EXPORT CodeGen { + public: + // A vector of BPF instructions that need to be installed as a filter + // program in the kernel. + typedef std::vector<struct sock_filter> Program; + + // Node represents a node within the instruction DAG being compiled. + using Node = Program::size_type; + + // kNullNode represents the "null" node; i.e., the reserved node + // value guaranteed to not equal any actual nodes. + static const Node kNullNode = -1; + + CodeGen(); + ~CodeGen(); + + // MakeInstruction creates a node representing the specified + // instruction, or returns and existing equivalent node if one + // exists. For details on the possible parameters refer to + // https://www.kernel.org/doc/Documentation/networking/filter.txt. + // TODO(mdempsky): Reconsider using default arguments here. + Node MakeInstruction(uint16_t code, + uint32_t k, + Node jt = kNullNode, + Node jf = kNullNode); + + // Compile linearizes the instruction DAG rooted at |head| into a + // program that can be executed by a BPF virtual machine. + Program Compile(Node head); + + private: + using MemoKey = std::tuple<uint16_t, uint32_t, Node, Node>; + + // AppendInstruction adds a new instruction, ensuring that |jt| and + // |jf| are within range as necessary for |code|. + Node AppendInstruction(uint16_t code, uint32_t k, Node jt, Node jf); + + // WithinRange returns a node equivalent to |next| that is at most + // |range| instructions away from the (logical) beginning of the + // program. + Node WithinRange(Node next, size_t range); + + // Append appends a new instruction to the physical end (i.e., + // logical beginning) of |program_|. + Node Append(uint16_t code, uint32_t k, size_t jt, size_t jf); + + // Offset returns how many instructions exist in |program_| after |target|. + size_t Offset(Node target) const; + + // NOTE: program_ is the compiled program in *reverse*, so that + // indices remain stable as we add instructions. + Program program_; + + // equivalent_ stores the most recent semantically-equivalent node for each + // instruction in program_. A node is defined as semantically-equivalent to N + // if it has the same instruction code and constant as N and its successor + // nodes (if any) are semantically-equivalent to N's successor nodes, or + // if it's an unconditional jump to a node semantically-equivalent to N. + std::vector<Node> equivalent_; + + std::map<MemoKey, Node> memos_; + + DISALLOW_COPY_AND_ASSIGN(CodeGen); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_CODEGEN_H__ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/cons.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/cons.h new file mode 100644 index 0000000000..07ac3dfc23 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/cons.h @@ -0,0 +1,137 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_CONS_H_ +#define SANDBOX_LINUX_BPF_DSL_CONS_H_ + +#include <memory> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace cons { + +// Namespace cons provides an abstraction for immutable "cons list" +// data structures as commonly provided in functional programming +// languages like Lisp or Haskell. +// +// A cons list is a linked list consisting of "cells", each of which +// have a "head" and a "tail" element. A cell's head element contains +// a user specified value, while the tail element contains a (possibly +// null) pointer to another cell. +// +// An empty list (idiomatically referred to as "nil") can be +// constructed as "cons::List<Foo>()" or simply as "nullptr" if Foo +// can be inferred from context (e.g., calling a function that has a +// "cons::List<Foo>" parameter). +// +// Existing lists (including empty lists) can be extended by +// prepending new values to the front using the "Cons(head, tail)" +// function, which will allocate a new cons cell. Notably, cons lists +// support creating multiple lists that share a common tail sequence. +// +// Lastly, lists support iteration via C++11's range-based for loop +// construct. +// +// Examples: +// +// // basic construction +// const cons::List<char> kNil = nullptr; +// cons::List<char> ba = Cons('b', Cons('a', kNil)); +// +// // common tail sequence +// cons::List<char> cba = Cons('c', ba); +// cons::List<char> dba = Cons('d', ba); +// +// // iteration +// for (const char& ch : cba) { +// // iterates 'c', 'b', 'a' +// } +// for (const char& ch : dba) { +// // iterates 'd', 'b', 'a' +// } + +// Forward declarations. +template <typename T> +class Cell; +template <typename T> +class ListIterator; + +// List represents a (possibly null) pointer to a cons cell. +template <typename T> +using List = std::shared_ptr<const Cell<T>>; + +// Cons extends a cons list by prepending a new value to the front. +template <typename T> +List<T> Cons(const T& head, List<T> tail) { + return std::make_shared<Cell<T>>(head, std::move(tail)); +} + +// Cell represents an individual "cons cell" within a cons list. +template <typename T> +class Cell { + public: + Cell(const T& head, List<T> tail) : head_(head), tail_(std::move(tail)) {} + + // Head returns this cell's head element. + const T& head() const { return head_; } + + // Tail returns this cell's tail element. + const List<T>& tail() const { return tail_; } + + private: + T head_; + List<T> tail_; + + DISALLOW_COPY_AND_ASSIGN(Cell); +}; + +// Begin returns a list iterator pointing to the first element of the +// cons list. It's provided to support range-based for loops. +template <typename T> +ListIterator<T> begin(const List<T>& list) { + return ListIterator<T>(list); +} + +// End returns a list iterator pointing to the "past-the-end" element +// of the cons list (i.e., nil). It's provided to support range-based +// for loops. +template <typename T> +ListIterator<T> end(const List<T>& list) { + return ListIterator<T>(); +} + +// ListIterator provides C++ forward iterator semantics for traversing +// a cons list. +template <typename T> +class ListIterator { + public: + ListIterator() : list_() {} + explicit ListIterator(const List<T>& list) : list_(list) {} + + const T& operator*() const { return list_->head(); } + + ListIterator& operator++() { + list_ = list_->tail(); + return *this; + } + + friend bool operator==(const ListIterator& lhs, const ListIterator& rhs) { + return lhs.list_ == rhs.list_; + } + + private: + List<T> list_; +}; + +template <typename T> +bool operator!=(const ListIterator<T>& lhs, const ListIterator<T>& rhs) { + return !(lhs == rhs); +} + +} // namespace cons +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_CONS_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/dump_bpf.cc b/security/sandbox/chromium/sandbox/linux/bpf_dsl/dump_bpf.cc new file mode 100644 index 0000000000..2edf592f68 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/dump_bpf.cc @@ -0,0 +1,159 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/dump_bpf.h" + +#include <inttypes.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> + +#include <string> + +#include "base/strings/stringprintf.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" + +namespace sandbox { +namespace bpf_dsl { + +namespace { + +const char* AluOpToken(uint32_t code) { + switch (BPF_OP(code)) { + case BPF_ADD: + return "+"; + case BPF_SUB: + return "-"; + case BPF_MUL: + return "*"; + case BPF_DIV: + return "/"; + case BPF_MOD: + return "%"; + case BPF_OR: + return "|"; + case BPF_XOR: + return "^"; + case BPF_AND: + return "&"; + case BPF_LSH: + return "<<"; + case BPF_RSH: + return ">>"; + default: + return "???"; + } +} + +const char* JmpOpToken(uint32_t code) { + switch (BPF_OP(code)) { + case BPF_JSET: + return "&"; + case BPF_JEQ: + return "=="; + case BPF_JGE: + return ">="; + default: + return "???"; + } +} + +const char* DataOffsetName(size_t off) { + switch (off) { + case SECCOMP_NR_IDX: + return "System call number"; + case SECCOMP_ARCH_IDX: + return "Architecture"; + case SECCOMP_IP_LSB_IDX: + return "Instruction pointer (LSB)"; + case SECCOMP_IP_MSB_IDX: + return "Instruction pointer (MSB)"; + default: + return "???"; + } +} + +void AppendInstruction(std::string* dst, size_t pc, const sock_filter& insn) { + base::StringAppendF(dst, "%3zu) ", pc); + switch (BPF_CLASS(insn.code)) { + case BPF_LD: + if (insn.code == BPF_LD + BPF_W + BPF_ABS) { + base::StringAppendF(dst, "LOAD %" PRIu32 " // ", insn.k); + size_t maybe_argno = + (insn.k - offsetof(struct arch_seccomp_data, args)) / + sizeof(uint64_t); + if (maybe_argno < 6 && insn.k == SECCOMP_ARG_LSB_IDX(maybe_argno)) { + base::StringAppendF(dst, "Argument %zu (LSB)\n", maybe_argno); + } else if (maybe_argno < 6 && + insn.k == SECCOMP_ARG_MSB_IDX(maybe_argno)) { + base::StringAppendF(dst, "Argument %zu (MSB)\n", maybe_argno); + } else { + base::StringAppendF(dst, "%s\n", DataOffsetName(insn.k)); + } + } else { + base::StringAppendF(dst, "Load ???\n"); + } + break; + case BPF_JMP: + if (BPF_OP(insn.code) == BPF_JA) { + base::StringAppendF(dst, "JMP %zu\n", pc + insn.k + 1); + } else { + base::StringAppendF( + dst, "if A %s 0x%" PRIx32 "; then JMP %zu else JMP %zu\n", + JmpOpToken(insn.code), insn.k, pc + insn.jt + 1, pc + insn.jf + 1); + } + break; + case BPF_RET: + base::StringAppendF(dst, "RET 0x%" PRIx32 " // ", insn.k); + if ((insn.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP) { + base::StringAppendF(dst, "Trap #%" PRIu32 "\n", + insn.k & SECCOMP_RET_DATA); + } else if ((insn.k & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { + base::StringAppendF(dst, "errno = %" PRIu32 "\n", + insn.k & SECCOMP_RET_DATA); + } else if ((insn.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE) { + base::StringAppendF(dst, "Trace #%" PRIu32 "\n", + insn.k & SECCOMP_RET_DATA); + } else if (insn.k == SECCOMP_RET_ALLOW) { + base::StringAppendF(dst, "Allowed\n"); + } else if (insn.k == SECCOMP_RET_KILL) { + base::StringAppendF(dst, "Kill\n"); + } else { + base::StringAppendF(dst, "???\n"); + } + break; + case BPF_ALU: + if (BPF_OP(insn.code) == BPF_NEG) { + base::StringAppendF(dst, "A := -A\n"); + } else { + base::StringAppendF(dst, "A := A %s 0x%" PRIx32 "\n", + AluOpToken(insn.code), insn.k); + } + break; + default: + base::StringAppendF(dst, "???\n"); + break; + } +} + +} // namespace + +void DumpBPF::PrintProgram(const CodeGen::Program& program) { + fputs(StringPrintProgram(program).c_str(), stderr); +} + +std::string DumpBPF::StringPrintProgram(const CodeGen::Program& program) { + std::string res; + for (size_t i = 0; i < program.size(); i++) { + AppendInstruction(&res, i + 1, program[i]); + } + return res; +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/dump_bpf.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/dump_bpf.h new file mode 100644 index 0000000000..34f1aa99f2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/dump_bpf.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_DUMP_BPF_H_ +#define SANDBOX_LINUX_BPF_DSL_DUMP_BPF_H_ + +#include <string> + +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +class SANDBOX_EXPORT DumpBPF { + public: + // PrintProgram writes |program| in a human-readable format to stderr. + static void PrintProgram(const CodeGen::Program& program); + + // StringPrintProgram writes |program| in a human-readable format to + // a std::string. + static std::string StringPrintProgram(const CodeGen::Program& program); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_DUMP_BPF_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/errorcode.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/errorcode.h new file mode 100644 index 0000000000..611c27dd80 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/errorcode.h @@ -0,0 +1,37 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_ERRORCODE_H__ +#define SANDBOX_LINUX_BPF_DSL_ERRORCODE_H__ + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +// TODO(mdempsky): Find a proper home for ERR_{MIN,MAX}_ERRNO and +// remove this header. +class SANDBOX_EXPORT ErrorCode { + public: + enum { + ERR_MIN_ERRNO = 0, +#if defined(__mips__) + // MIPS only supports errno up to 1133 + ERR_MAX_ERRNO = 1133, +#else + // TODO(markus): Android only supports errno up to 255 + // (crbug.com/181647). + ERR_MAX_ERRNO = 4095, +#endif + }; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(ErrorCode); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_ERRORCODE_H__ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/linux_syscall_ranges.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/linux_syscall_ranges.h new file mode 100644 index 0000000000..313511f22e --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/linux_syscall_ranges.h @@ -0,0 +1,63 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_LINUX_SYSCALL_RANGES_H_ +#define SANDBOX_LINUX_BPF_DSL_LINUX_SYSCALL_RANGES_H_ + +#include "build/build_config.h" + +#if defined(__x86_64__) + +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL 1024u +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#elif defined(__i386__) + +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL 1024u +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#elif defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) + +// ARM EABI includes "ARM private" system calls starting at |__ARM_NR_BASE|, +// and a "ghost syscall private to the kernel", cmpxchg, +// at |__ARM_NR_BASE+0x00fff0|. +// See </arch/arm/include/asm/unistd.h> in the Linux kernel. + +// __NR_SYSCALL_BASE is 0 in thumb and ARM EABI. +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL (MIN_SYSCALL + 1024u) +// __ARM_NR_BASE is __NR_SYSCALL_BASE + 0xf0000u +#define MIN_PRIVATE_SYSCALL 0xf0000u +#define MAX_PRIVATE_SYSCALL (MIN_PRIVATE_SYSCALL + 16u) +#define MIN_GHOST_SYSCALL (MIN_PRIVATE_SYSCALL + 0xfff0u) +#define MAX_SYSCALL (MIN_GHOST_SYSCALL + 4u) + +#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS) + +#include <asm/unistd.h> // for __NR_O32_Linux and __NR_Linux_syscalls +#define MIN_SYSCALL __NR_O32_Linux +#define MAX_PUBLIC_SYSCALL (MIN_SYSCALL + __NR_Linux_syscalls) +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_64_BITS) + +#include <asm/unistd.h> // for __NR_64_Linux and __NR_64_Linux_syscalls +#define MIN_SYSCALL __NR_64_Linux +#define MAX_PUBLIC_SYSCALL (MIN_SYSCALL + __NR_64_Linux_syscalls) +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#elif defined(__aarch64__) + +#include <asm-generic/unistd.h> +#define MIN_SYSCALL 0u +#define MAX_PUBLIC_SYSCALL __NR_syscalls +#define MAX_SYSCALL MAX_PUBLIC_SYSCALL + +#else +#error "Unsupported architecture" +#endif + +#endif // SANDBOX_LINUX_BPF_DSL_LINUX_SYSCALL_RANGES_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy.cc b/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy.cc new file mode 100644 index 0000000000..c20edc6da8 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy.cc @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/policy.h" + +#include <errno.h> + +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" + +namespace sandbox { +namespace bpf_dsl { + +ResultExpr Policy::InvalidSyscall() const { + return Error(ENOSYS); +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy.h new file mode 100644 index 0000000000..6c67589456 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_POLICY_H_ +#define SANDBOX_LINUX_BPF_DSL_POLICY_H_ + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { + +// Interface to implement to define a BPF sandbox policy. +class SANDBOX_EXPORT Policy { + public: + Policy() {} + virtual ~Policy() {} + + // User extension point for writing custom sandbox policies. + // The returned ResultExpr will control how the kernel responds to the + // specified system call number. + virtual ResultExpr EvaluateSyscall(int sysno) const = 0; + + // Optional overload for specifying alternate behavior for invalid + // system calls. The default is to return ENOSYS. + virtual ResultExpr InvalidSyscall() const; + + private: + DISALLOW_COPY_AND_ASSIGN(Policy); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy_compiler.cc b/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy_compiler.cc new file mode 100644 index 0000000000..b909fc37f6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy_compiler.cc @@ -0,0 +1,481 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/policy_compiler.h" + +#include <errno.h> +#include <stddef.h> +#include <stdint.h> +#include <sys/syscall.h> + +#include <limits> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/syscall_set.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { +namespace bpf_dsl { + +namespace { + +#if defined(__i386__) || defined(__x86_64__) +const bool kIsIntel = true; +#else +const bool kIsIntel = false; +#endif +#if defined(__x86_64__) && defined(__ILP32__) +const bool kIsX32 = true; +#else +const bool kIsX32 = false; +#endif + +const int kSyscallsRequiredForUnsafeTraps[] = { + __NR_rt_sigprocmask, + __NR_rt_sigreturn, +#if defined(__NR_sigprocmask) + __NR_sigprocmask, +#endif +#if defined(__NR_sigreturn) + __NR_sigreturn, +#endif +}; + +bool HasExactlyOneBit(uint64_t x) { + // Common trick; e.g., see http://stackoverflow.com/a/108329. + return x != 0 && (x & (x - 1)) == 0; +} + +ResultExpr DefaultPanic(const char* error) { + return Kill(); +} + +// A Trap() handler that returns an "errno" value. The value is encoded +// in the "aux" parameter. +intptr_t ReturnErrno(const struct arch_seccomp_data&, void* aux) { + // TrapFnc functions report error by following the native kernel convention + // of returning an exit code in the range of -1..-4096. They do not try to + // set errno themselves. The glibc wrapper that triggered the SIGSYS will + // ultimately do so for us. + int err = reinterpret_cast<intptr_t>(aux) & SECCOMP_RET_DATA; + return -err; +} + +bool HasUnsafeTraps(const Policy* policy) { + DCHECK(policy); + for (uint32_t sysnum : SyscallSet::ValidOnly()) { + if (policy->EvaluateSyscall(sysnum)->HasUnsafeTraps()) { + return true; + } + } + return policy->InvalidSyscall()->HasUnsafeTraps(); +} + +} // namespace + +struct PolicyCompiler::Range { + uint32_t from; + CodeGen::Node node; +}; + +PolicyCompiler::PolicyCompiler(const Policy* policy, TrapRegistry* registry) + : policy_(policy), + registry_(registry), + escapepc_(0), + panic_func_(DefaultPanic), + gen_(), + has_unsafe_traps_(HasUnsafeTraps(policy_)) { + DCHECK(policy); +} + +PolicyCompiler::~PolicyCompiler() { +} + +CodeGen::Program PolicyCompiler::Compile() { + CHECK(policy_->InvalidSyscall()->IsDeny()) + << "Policies should deny invalid system calls"; + + // If our BPF program has unsafe traps, enable support for them. + if (has_unsafe_traps_) { + CHECK_NE(0U, escapepc_) << "UnsafeTrap() requires a valid escape PC"; + + for (int sysnum : kSyscallsRequiredForUnsafeTraps) { + CHECK(policy_->EvaluateSyscall(sysnum)->IsAllow()) + << "Policies that use UnsafeTrap() must unconditionally allow all " + "required system calls"; + } + + CHECK(registry_->EnableUnsafeTraps()) + << "We'd rather die than enable unsafe traps"; + } + + // Assemble the BPF filter program. + return gen_.Compile(AssemblePolicy()); +} + +void PolicyCompiler::DangerousSetEscapePC(uint64_t escapepc) { + escapepc_ = escapepc; +} + +void PolicyCompiler::SetPanicFunc(PanicFunc panic_func) { + panic_func_ = panic_func; +} + +CodeGen::Node PolicyCompiler::AssemblePolicy() { + // A compiled policy consists of three logical parts: + // 1. Check that the "arch" field matches the expected architecture. + // 2. If the policy involves unsafe traps, check if the syscall was + // invoked by Syscall::Call, and then allow it unconditionally. + // 3. Check the system call number and jump to the appropriate compiled + // system call policy number. + return CheckArch(MaybeAddEscapeHatch(DispatchSyscall())); +} + +CodeGen::Node PolicyCompiler::CheckArch(CodeGen::Node passed) { + // If the architecture doesn't match SECCOMP_ARCH, disallow the + // system call. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_ARCH_IDX, + gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, SECCOMP_ARCH, passed, + CompileResult(panic_func_( + "Invalid audit architecture in BPF filter")))); +} + +CodeGen::Node PolicyCompiler::MaybeAddEscapeHatch(CodeGen::Node rest) { + // If no unsafe traps, then simply return |rest|. + if (!has_unsafe_traps_) { + return rest; + } + + // We already enabled unsafe traps in Compile, but enable them again to give + // the trap registry a second chance to complain before we add the backdoor. + CHECK(registry_->EnableUnsafeTraps()); + + // Allow system calls, if they originate from our magic return address. + const uint32_t lopc = static_cast<uint32_t>(escapepc_); + const uint32_t hipc = static_cast<uint32_t>(escapepc_ >> 32); + + // BPF cannot do native 64-bit comparisons, so we have to compare + // both 32-bit halves of the instruction pointer. If they match what + // we expect, we return ERR_ALLOWED. If either or both don't match, + // we continue evalutating the rest of the sandbox policy. + // + // For simplicity, we check the full 64-bit instruction pointer even + // on 32-bit architectures. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_LSB_IDX, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, lopc, + gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_MSB_IDX, + gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, hipc, + CompileResult(Allow()), rest)), + rest)); +} + +CodeGen::Node PolicyCompiler::DispatchSyscall() { + // Evaluate all possible system calls and group their Nodes into + // ranges of identical codes. + Ranges ranges; + FindRanges(&ranges); + + // Compile the system call ranges to an optimized BPF jumptable + CodeGen::Node jumptable = AssembleJumpTable(ranges.begin(), ranges.end()); + + // Grab the system call number, so that we can check it and then + // execute the jump table. + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, SECCOMP_NR_IDX, CheckSyscallNumber(jumptable)); +} + +CodeGen::Node PolicyCompiler::CheckSyscallNumber(CodeGen::Node passed) { + if (kIsIntel) { + // On Intel architectures, verify that system call numbers are in the + // expected number range. + CodeGen::Node invalidX32 = + CompileResult(panic_func_("Illegal mixing of system call ABIs")); + if (kIsX32) { + // The newer x32 API always sets bit 30. + return gen_.MakeInstruction( + BPF_JMP + BPF_JSET + BPF_K, 0x40000000, passed, invalidX32); + } else { + // The older i386 and x86-64 APIs clear bit 30 on all system calls. + return gen_.MakeInstruction( + BPF_JMP + BPF_JSET + BPF_K, 0x40000000, invalidX32, passed); + } + } + + // TODO(mdempsky): Similar validation for other architectures? + return passed; +} + +void PolicyCompiler::FindRanges(Ranges* ranges) { + // Please note that "struct seccomp_data" defines system calls as a signed + // int32_t, but BPF instructions always operate on unsigned quantities. We + // deal with this disparity by enumerating from MIN_SYSCALL to MAX_SYSCALL, + // and then verifying that the rest of the number range (both positive and + // negative) all return the same Node. + const CodeGen::Node invalid_node = CompileResult(policy_->InvalidSyscall()); + uint32_t old_sysnum = 0; + CodeGen::Node old_node = + SyscallSet::IsValid(old_sysnum) + ? CompileResult(policy_->EvaluateSyscall(old_sysnum)) + : invalid_node; + + for (uint32_t sysnum : SyscallSet::All()) { + CodeGen::Node node = + SyscallSet::IsValid(sysnum) + ? CompileResult(policy_->EvaluateSyscall(static_cast<int>(sysnum))) + : invalid_node; + // N.B., here we rely on CodeGen folding (i.e., returning the same + // node value for) identical code sequences, otherwise our jump + // table will blow up in size. + if (node != old_node) { + ranges->push_back(Range{old_sysnum, old_node}); + old_sysnum = sysnum; + old_node = node; + } + } + ranges->push_back(Range{old_sysnum, old_node}); +} + +CodeGen::Node PolicyCompiler::AssembleJumpTable(Ranges::const_iterator start, + Ranges::const_iterator stop) { + // We convert the list of system call ranges into jump table that performs + // a binary search over the ranges. + // As a sanity check, we need to have at least one distinct ranges for us + // to be able to build a jump table. + CHECK(start < stop) << "Invalid iterator range"; + const auto n = stop - start; + if (n == 1) { + // If we have narrowed things down to a single range object, we can + // return from the BPF filter program. + return start->node; + } + + // Pick the range object that is located at the mid point of our list. + // We compare our system call number against the lowest valid system call + // number in this range object. If our number is lower, it is outside of + // this range object. If it is greater or equal, it might be inside. + Ranges::const_iterator mid = start + n / 2; + + // Sub-divide the list of ranges and continue recursively. + CodeGen::Node jf = AssembleJumpTable(start, mid); + CodeGen::Node jt = AssembleJumpTable(mid, stop); + return gen_.MakeInstruction(BPF_JMP + BPF_JGE + BPF_K, mid->from, jt, jf); +} + +CodeGen::Node PolicyCompiler::CompileResult(const ResultExpr& res) { + return res->Compile(this); +} + +CodeGen::Node PolicyCompiler::MaskedEqual(int argno, + size_t width, + uint64_t mask, + uint64_t value, + CodeGen::Node passed, + CodeGen::Node failed) { + // Sanity check that arguments make sense. + CHECK(argno >= 0 && argno < 6) << "Invalid argument number " << argno; + CHECK(width == 4 || width == 8) << "Invalid argument width " << width; + CHECK_NE(0U, mask) << "Zero mask is invalid"; + CHECK_EQ(value, value & mask) << "Value contains masked out bits"; + if (sizeof(void*) == 4) { + CHECK_EQ(4U, width) << "Invalid width on 32-bit platform"; + } + if (width == 4) { + CHECK_EQ(0U, mask >> 32) << "Mask exceeds argument size"; + CHECK_EQ(0U, value >> 32) << "Value exceeds argument size"; + } + + // We want to emit code to check "(arg & mask) == value" where arg, mask, and + // value are 64-bit values, but the BPF machine is only 32-bit. We implement + // this by independently testing the upper and lower 32-bits and continuing to + // |passed| if both evaluate true, or to |failed| if either evaluate false. + return MaskedEqualHalf(argno, width, mask, value, ArgHalf::UPPER, + MaskedEqualHalf(argno, width, mask, value, + ArgHalf::LOWER, passed, failed), + failed); +} + +CodeGen::Node PolicyCompiler::MaskedEqualHalf(int argno, + size_t width, + uint64_t full_mask, + uint64_t full_value, + ArgHalf half, + CodeGen::Node passed, + CodeGen::Node failed) { + if (width == 4 && half == ArgHalf::UPPER) { + // Special logic for sanity checking the upper 32-bits of 32-bit system + // call arguments. + + CodeGen::Node invalid_64bit = Unexpected64bitArgument(argno); + + const uint32_t upper = SECCOMP_ARG_MSB_IDX(argno); + const uint32_t lower = SECCOMP_ARG_LSB_IDX(argno); + + if (sizeof(void*) == 4) { + // On 32-bit platforms, the upper 32-bits should always be 0: + // LDW [upper] + // JEQ 0, passed, invalid + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + upper, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, 0, passed, invalid_64bit)); + } + + // On 64-bit platforms, the ABI (at least on x86_64) allows any value + // for the upper half, but to avoid potential vulnerabilties if an + // argument is incorrectly tested as a 32-bit type, we require it to be + // either zero-extended or sign-extended. That is, the upper 32-bits + // may be 0 or ~0; but we only allow ~0 if the sign bit of the lower + // 32-bits is set too: + // + // LDW [upper] + // JEQ 0, passed, (next) + // JEQ ~0, (next), invalid + // LDW [lower] + // JSET (1<<31), passed, invalid + // + // TODO(mdempsky): The JSET instruction could perhaps jump to passed->next + // instead, as the first instruction of passed should be "LDW [lower]". + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + upper, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, + 0, + passed, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, + std::numeric_limits<uint32_t>::max(), + gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + lower, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, + 1U << 31, + passed, + invalid_64bit)), + invalid_64bit))); + } + + const uint32_t idx = (half == ArgHalf::UPPER) ? SECCOMP_ARG_MSB_IDX(argno) + : SECCOMP_ARG_LSB_IDX(argno); + const uint32_t mask = (half == ArgHalf::UPPER) ? full_mask >> 32 : full_mask; + const uint32_t value = + (half == ArgHalf::UPPER) ? full_value >> 32 : full_value; + + // Emit a suitable instruction sequence for (arg & mask) == value. + + // For (arg & 0) == 0, just return passed. + if (mask == 0) { + CHECK_EQ(0U, value); + return passed; + } + + // For (arg & ~0) == value, emit: + // LDW [idx] + // JEQ value, passed, failed + if (mask == std::numeric_limits<uint32_t>::max()) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed)); + } + + // For (arg & mask) == 0, emit: + // LDW [idx] + // JSET mask, failed, passed + // (Note: failed and passed are intentionally swapped.) + if (value == 0) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, failed, passed)); + } + + // For (arg & x) == x where x is a single-bit value, emit: + // LDW [idx] + // JSET mask, passed, failed + if (mask == value && HasExactlyOneBit(mask)) { + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, passed, failed)); + } + + // Generic fallback: + // LDW [idx] + // AND mask + // JEQ value, passed, failed + return gen_.MakeInstruction( + BPF_LD + BPF_W + BPF_ABS, + idx, + gen_.MakeInstruction( + BPF_ALU + BPF_AND + BPF_K, + mask, + gen_.MakeInstruction( + BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed))); +} + +CodeGen::Node PolicyCompiler::Unexpected64bitArgument(int argno) { + // This situation is unlikely, but possible. Return to userspace, + // zero-extend the problematic argument, and re-issue the syscall. + return CompileResult(bpf_dsl::Trap( + [](const arch_seccomp_data& args_ref, void* aux) -> intptr_t { + arch_seccomp_data args = args_ref; + int argno = (int)(intptr_t)aux; + args.args[argno] &= 0xFFFFFFFF; + return Syscall::Call(args.nr, args.args[0], args.args[1], args.args[2], + args.args[3], args.args[4], args.args[5]); + }, + (void*)(intptr_t)argno)); +} + +CodeGen::Node PolicyCompiler::Return(uint32_t ret) { + if (has_unsafe_traps_ && (ret & SECCOMP_RET_ACTION) == SECCOMP_RET_ERRNO) { + // When inside an UnsafeTrap() callback, we want to allow all system calls. + // This means, we must conditionally disable the sandbox -- and that's not + // something that kernel-side BPF filters can do, as they cannot inspect + // any state other than the syscall arguments. + // But if we redirect all error handlers to user-space, then we can easily + // make this decision. + // The performance penalty for this extra round-trip to user-space is not + // actually that bad, as we only ever pay it for denied system calls; and a + // typical program has very few of these. + return Trap(ReturnErrno, reinterpret_cast<void*>(ret & SECCOMP_RET_DATA), + true); + } + + return gen_.MakeInstruction(BPF_RET + BPF_K, ret); +} + +CodeGen::Node PolicyCompiler::Trap(TrapRegistry::TrapFnc fnc, + const void* aux, + bool safe) { + uint16_t trap_id = registry_->Add(fnc, aux, safe); + return gen_.MakeInstruction(BPF_RET + BPF_K, SECCOMP_RET_TRAP + trap_id); +} + +bool PolicyCompiler::IsRequiredForUnsafeTrap(int sysno) { + for (size_t i = 0; i < base::size(kSyscallsRequiredForUnsafeTraps); ++i) { + if (sysno == kSyscallsRequiredForUnsafeTraps[i]) { + return true; + } + } + return false; +} + +} // namespace bpf_dsl +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy_compiler.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy_compiler.h new file mode 100644 index 0000000000..2acf878474 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/policy_compiler.h @@ -0,0 +1,155 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ +#define SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +namespace bpf_dsl { +class Policy; + +// PolicyCompiler implements the bpf_dsl compiler, allowing users to +// transform bpf_dsl policies into BPF programs to be executed by the +// Linux kernel. +class SANDBOX_EXPORT PolicyCompiler { + public: + using PanicFunc = bpf_dsl::ResultExpr (*)(const char* error); + + PolicyCompiler(const Policy* policy, TrapRegistry* registry); + ~PolicyCompiler(); + + // Compile registers any trap handlers needed by the policy and + // compiles the policy to a BPF program, which it returns. + CodeGen::Program Compile(); + + // DangerousSetEscapePC sets the "escape PC" that is allowed to issue any + // system calls, regardless of policy. + void DangerousSetEscapePC(uint64_t escapepc); + + // SetPanicFunc sets the callback function used for handling faulty + // system call conditions. The default behavior is to immediately kill + // the process. + // TODO(mdempsky): Move this into Policy? + void SetPanicFunc(PanicFunc panic_func); + + // UnsafeTraps require some syscalls to always be allowed. + // This helper function returns true for these calls. + static bool IsRequiredForUnsafeTrap(int sysno); + + // Functions below are meant for use within bpf_dsl itself. + + // Return returns a CodeGen::Node that returns the specified seccomp + // return value. + CodeGen::Node Return(uint32_t ret); + + // Trap returns a CodeGen::Node to indicate the system call should + // instead invoke a trap handler. + CodeGen::Node Trap(TrapRegistry::TrapFnc fnc, const void* aux, bool safe); + + // MaskedEqual returns a CodeGen::Node that represents a conditional branch. + // Argument "argno" (1..6) will be bitwise-AND'd with "mask" and compared + // to "value"; if equal, then "passed" will be executed, otherwise "failed". + // If "width" is 4, the argument must in the range of 0x0..(1u << 32 - 1) + // If it is outside this range, the sandbox treats the system call just + // the same as any other ABI violation (i.e., it panics). + CodeGen::Node MaskedEqual(int argno, + size_t width, + uint64_t mask, + uint64_t value, + CodeGen::Node passed, + CodeGen::Node failed); + + private: + struct Range; + typedef std::vector<Range> Ranges; + + // Used by MaskedEqualHalf to track which half of the argument it's + // emitting instructions for. + enum class ArgHalf { + LOWER, + UPPER, + }; + + // Compile the configured policy into a complete instruction sequence. + CodeGen::Node AssemblePolicy(); + + // Return an instruction sequence that checks the + // arch_seccomp_data's "arch" field is valid, and then passes + // control to |passed| if so. + CodeGen::Node CheckArch(CodeGen::Node passed); + + // If |has_unsafe_traps_| is true, returns an instruction sequence + // that allows all system calls from |escapepc_|, and otherwise + // passes control to |rest|. Otherwise, simply returns |rest|. + CodeGen::Node MaybeAddEscapeHatch(CodeGen::Node rest); + + // Return an instruction sequence that loads and checks the system + // call number, performs a binary search, and then dispatches to an + // appropriate instruction sequence compiled from the current + // policy. + CodeGen::Node DispatchSyscall(); + + // Return an instruction sequence that checks the system call number + // (expected to be loaded in register A) and if valid, passes + // control to |passed| (with register A still valid). + CodeGen::Node CheckSyscallNumber(CodeGen::Node passed); + + // Finds all the ranges of system calls that need to be handled. Ranges are + // sorted in ascending order of system call numbers. There are no gaps in the + // ranges. System calls with identical CodeGen::Nodes are coalesced into a + // single + // range. + void FindRanges(Ranges* ranges); + + // Returns a BPF program snippet that implements a jump table for the + // given range of system call numbers. This function runs recursively. + CodeGen::Node AssembleJumpTable(Ranges::const_iterator start, + Ranges::const_iterator stop); + + // CompileResult compiles an individual result expression into a + // CodeGen node. + CodeGen::Node CompileResult(const ResultExpr& res); + + // Returns a BPF program that evaluates half of a conditional expression; + // it should only ever be called from CondExpression(). + CodeGen::Node MaskedEqualHalf(int argno, + size_t width, + uint64_t full_mask, + uint64_t full_value, + ArgHalf half, + CodeGen::Node passed, + CodeGen::Node failed); + + // Returns the CodeGen::Node that is used to handle the case where a + // system call argument was expected to be a 32-bit type, but the + // value in the 64-bit register doesn't correspond to a + // zero-extended or sign-extended 32-bit value. + CodeGen::Node Unexpected64bitArgument(int argno); + + const Policy* policy_; + TrapRegistry* registry_; + uint64_t escapepc_; + PanicFunc panic_func_; + + CodeGen gen_; + bool has_unsafe_traps_; + + DISALLOW_COPY_AND_ASSIGN(PolicyCompiler); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_POLICY_COMPILER_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/seccomp_macros.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/seccomp_macros.h new file mode 100644 index 0000000000..1a407b9523 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/seccomp_macros.h @@ -0,0 +1,354 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_SECCOMP_MACROS_H_ +#define SANDBOX_LINUX_BPF_DSL_SECCOMP_MACROS_H_ + +#include <sys/types.h> // For __BIONIC__. +// Old Bionic versions do not have sys/user.h. The if can be removed once we no +// longer need to support these old Bionic versions. +// All x86_64 builds use a new enough bionic to have sys/user.h. +#if !defined(__BIONIC__) || defined(__x86_64__) +#if !defined(__native_client_nonsfi__) +#include <sys/user.h> +#endif +#if defined(__mips__) +// sys/user.h in eglibc misses size_t definition +#include <stddef.h> +#endif +#endif + +#include "build/build_config.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" // For AUDIT_ARCH_* + +// Impose some reasonable maximum BPF program size. Realistically, the +// kernel probably has much lower limits. But by limiting to less than +// 30 bits, we can ease requirements on some of our data types. +#define SECCOMP_MAX_PROGRAM_SIZE (1<<30) + +#if defined(__i386__) +#define SECCOMP_ARCH AUDIT_ARCH_I386 + +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, REG_EAX) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, REG_EAX) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, REG_EIP) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, REG_EBX) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, REG_ECX) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, REG_EDX) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, REG_ESI) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, REG_EDI) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, REG_EBP) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + + +#if defined(__BIONIC__) || defined(__native_client_nonsfi__) +// Old Bionic versions and PNaCl toolchain don't have sys/user.h, so we just +// define regs_struct directly. This can be removed once we no longer need to +// support these old Bionic versions and PNaCl toolchain. +struct regs_struct { + long int ebx; + long int ecx; + long int edx; + long int esi; + long int edi; + long int ebp; + long int eax; + long int xds; + long int xes; + long int xfs; + long int xgs; + long int orig_eax; + long int eip; + long int xcs; + long int eflags; + long int esp; + long int xss; +}; +#else +typedef user_regs_struct regs_struct; +#endif + +#define SECCOMP_PT_RESULT(_regs) (_regs).eax +#define SECCOMP_PT_SYSCALL(_regs) (_regs).orig_eax +#define SECCOMP_PT_IP(_regs) (_regs).eip +#define SECCOMP_PT_PARM1(_regs) (_regs).ebx +#define SECCOMP_PT_PARM2(_regs) (_regs).ecx +#define SECCOMP_PT_PARM3(_regs) (_regs).edx +#define SECCOMP_PT_PARM4(_regs) (_regs).esi +#define SECCOMP_PT_PARM5(_regs) (_regs).edi +#define SECCOMP_PT_PARM6(_regs) (_regs).ebp + +#elif defined(__x86_64__) +#define SECCOMP_ARCH AUDIT_ARCH_X86_64 + +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[(_reg)]) +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, REG_RAX) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, REG_RAX) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, REG_RIP) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, REG_RDI) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, REG_RSI) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, REG_RDX) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, REG_R10) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, REG_R8) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, REG_R9) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + +typedef user_regs_struct regs_struct; +#define SECCOMP_PT_RESULT(_regs) (_regs).rax +#define SECCOMP_PT_SYSCALL(_regs) (_regs).orig_rax +#define SECCOMP_PT_IP(_regs) (_regs).rip +#define SECCOMP_PT_PARM1(_regs) (_regs).rdi +#define SECCOMP_PT_PARM2(_regs) (_regs).rsi +#define SECCOMP_PT_PARM3(_regs) (_regs).rdx +#define SECCOMP_PT_PARM4(_regs) (_regs).r10 +#define SECCOMP_PT_PARM5(_regs) (_regs).r8 +#define SECCOMP_PT_PARM6(_regs) (_regs).r9 + +#elif defined(__arm__) && (defined(__thumb__) || defined(__ARM_EABI__)) +#define SECCOMP_ARCH AUDIT_ARCH_ARM + +// ARM sigcontext_t is different from i386/x86_64. +// See </arch/arm/include/asm/sigcontext.h> in the Linux kernel. +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.arm_##_reg) +// ARM EABI syscall convention. +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, r0) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, r7) +#define SECCOMP_IP(_ctx) SECCOMP_REG(_ctx, pc) +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, r0) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, r1) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, r2) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, r3) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, r4) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, r5) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + +#if defined(__BIONIC__) || defined(__native_client_nonsfi__) +// Old Bionic versions and PNaCl toolchain don't have sys/user.h, so we just +// define regs_struct directly. This can be removed once we no longer need to +// support these old Bionic versions and PNaCl toolchain. +struct regs_struct { + unsigned long uregs[18]; +}; +#else +typedef user_regs regs_struct; +#endif + +#define REG_cpsr uregs[16] +#define REG_pc uregs[15] +#define REG_lr uregs[14] +#define REG_sp uregs[13] +#define REG_ip uregs[12] +#define REG_fp uregs[11] +#define REG_r10 uregs[10] +#define REG_r9 uregs[9] +#define REG_r8 uregs[8] +#define REG_r7 uregs[7] +#define REG_r6 uregs[6] +#define REG_r5 uregs[5] +#define REG_r4 uregs[4] +#define REG_r3 uregs[3] +#define REG_r2 uregs[2] +#define REG_r1 uregs[1] +#define REG_r0 uregs[0] +#define REG_ORIG_r0 uregs[17] + +#define SECCOMP_PT_RESULT(_regs) (_regs).REG_r0 +#define SECCOMP_PT_SYSCALL(_regs) (_regs).REG_r7 +#define SECCOMP_PT_IP(_regs) (_regs).REG_pc +#define SECCOMP_PT_PARM1(_regs) (_regs).REG_r0 +#define SECCOMP_PT_PARM2(_regs) (_regs).REG_r1 +#define SECCOMP_PT_PARM3(_regs) (_regs).REG_r2 +#define SECCOMP_PT_PARM4(_regs) (_regs).REG_r3 +#define SECCOMP_PT_PARM5(_regs) (_regs).REG_r4 +#define SECCOMP_PT_PARM6(_regs) (_regs).REG_r5 + +#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS) +#define SECCOMP_ARCH AUDIT_ARCH_MIPSEL +#define SYSCALL_EIGHT_ARGS +// MIPS sigcontext_t is different from i386/x86_64 and ARM. +// See </arch/mips/include/uapi/asm/sigcontext.h> in the Linux kernel. +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[_reg]) +// Based on MIPS o32 ABI syscall convention. +// On MIPS, when indirect syscall is being made (syscall(__NR_foo)), +// real identificator (__NR_foo) is not in v0, but in a0 +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, 2) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, 2) +#define SECCOMP_IP(_ctx) (_ctx)->uc_mcontext.pc +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, 4) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, 5) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, 6) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, 7) +// Only the first 4 arguments of syscall are in registers. +// The rest are on the stack. +#define SECCOMP_STACKPARM(_ctx, n) (((long *)SECCOMP_REG(_ctx, 29))[(n)]) +#define SECCOMP_PARM5(_ctx) SECCOMP_STACKPARM(_ctx, 4) +#define SECCOMP_PARM6(_ctx) SECCOMP_STACKPARM(_ctx, 5) +#define SECCOMP_PARM7(_ctx) SECCOMP_STACKPARM(_ctx, 6) +#define SECCOMP_PARM8(_ctx) SECCOMP_STACKPARM(_ctx, 7) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + +// On MIPS we don't have structures like user_regs or user_regs_struct in +// sys/user.h that we could use, so we just define regs_struct directly. +struct regs_struct { + unsigned long long regs[32]; +}; + +#define REG_a3 regs[7] +#define REG_a2 regs[6] +#define REG_a1 regs[5] +#define REG_a0 regs[4] +#define REG_v1 regs[3] +#define REG_v0 regs[2] + +#define SECCOMP_PT_RESULT(_regs) (_regs).REG_v0 +#define SECCOMP_PT_SYSCALL(_regs) (_regs).REG_v0 +#define SECCOMP_PT_PARM1(_regs) (_regs).REG_a0 +#define SECCOMP_PT_PARM2(_regs) (_regs).REG_a1 +#define SECCOMP_PT_PARM3(_regs) (_regs).REG_a2 +#define SECCOMP_PT_PARM4(_regs) (_regs).REG_a3 + +#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_64_BITS) +#define SECCOMP_ARCH AUDIT_ARCH_MIPSEL64 +#define SYSCALL_EIGHT_ARGS +// MIPS sigcontext_t is different from i386/x86_64 and ARM. +// See </arch/mips/include/uapi/asm/sigcontext.h> in the Linux kernel. +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.gregs[_reg]) +// Based on MIPS n64 ABI syscall convention. +// On MIPS, when an indirect syscall is being made (syscall(__NR_foo)), +// the real identifier (__NR_foo) is not in v0, but in a0. +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, 2) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, 2) +#define SECCOMP_IP(_ctx) (_ctx)->uc_mcontext.pc +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, 4) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, 5) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, 6) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, 7) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, 8) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, 9) +#define SECCOMP_PARM7(_ctx) SECCOMP_REG(_ctx, 10) +#define SECCOMP_PARM8(_ctx) SECCOMP_REG(_ctx, 11) +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX (offsetof(struct arch_seccomp_data, \ + instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) (offsetof(struct arch_seccomp_data, args) + \ + 8*(nr) + 0) + +// On MIPS we don't have structures like user_regs or user_regs_struct in +// sys/user.h that we could use, so we just define regs_struct directly. +struct regs_struct { + unsigned long long regs[32]; +}; + +#define REG_a7 regs[11] +#define REG_a6 regs[10] +#define REG_a5 regs[9] +#define REG_a4 regs[8] +#define REG_a3 regs[7] +#define REG_a2 regs[6] +#define REG_a1 regs[5] +#define REG_a0 regs[4] +#define REG_v1 regs[3] +#define REG_v0 regs[2] + +#define SECCOMP_PT_RESULT(_regs) (_regs).REG_v0 +#define SECCOMP_PT_SYSCALL(_regs) (_regs).REG_v0 +#define SECCOMP_PT_PARM1(_regs) (_regs).REG_a0 +#define SECCOMP_PT_PARM2(_regs) (_regs).REG_a1 +#define SECCOMP_PT_PARM3(_regs) (_regs).REG_a2 +#define SECCOMP_PT_PARM4(_regs) (_regs).REG_a3 +#define SECCOMP_PT_PARM5(_regs) (_regs).REG_a4 +#define SECCOMP_PT_PARM6(_regs) (_regs).REG_a5 +#define SECCOMP_PT_PARM7(_regs) (_regs).REG_a6 +#define SECCOMP_PT_PARM8(_regs) (_regs).REG_a7 + +#elif defined(__aarch64__) +struct regs_struct { + unsigned long long regs[31]; + unsigned long long sp; + unsigned long long pc; + unsigned long long pstate; +}; + +#define SECCOMP_ARCH AUDIT_ARCH_AARCH64 + +#define SECCOMP_REG(_ctx, _reg) ((_ctx)->uc_mcontext.regs[_reg]) + +#define SECCOMP_RESULT(_ctx) SECCOMP_REG(_ctx, 0) +#define SECCOMP_SYSCALL(_ctx) SECCOMP_REG(_ctx, 8) +#define SECCOMP_IP(_ctx) (_ctx)->uc_mcontext.pc +#define SECCOMP_PARM1(_ctx) SECCOMP_REG(_ctx, 0) +#define SECCOMP_PARM2(_ctx) SECCOMP_REG(_ctx, 1) +#define SECCOMP_PARM3(_ctx) SECCOMP_REG(_ctx, 2) +#define SECCOMP_PARM4(_ctx) SECCOMP_REG(_ctx, 3) +#define SECCOMP_PARM5(_ctx) SECCOMP_REG(_ctx, 4) +#define SECCOMP_PARM6(_ctx) SECCOMP_REG(_ctx, 5) + +#define SECCOMP_NR_IDX (offsetof(struct arch_seccomp_data, nr)) +#define SECCOMP_ARCH_IDX (offsetof(struct arch_seccomp_data, arch)) +#define SECCOMP_IP_MSB_IDX \ + (offsetof(struct arch_seccomp_data, instruction_pointer) + 4) +#define SECCOMP_IP_LSB_IDX \ + (offsetof(struct arch_seccomp_data, instruction_pointer) + 0) +#define SECCOMP_ARG_MSB_IDX(nr) \ + (offsetof(struct arch_seccomp_data, args) + 8 * (nr) + 4) +#define SECCOMP_ARG_LSB_IDX(nr) \ + (offsetof(struct arch_seccomp_data, args) + 8 * (nr) + 0) + +#define SECCOMP_PT_RESULT(_regs) (_regs).regs[0] +#define SECCOMP_PT_SYSCALL(_regs) (_regs).regs[8] +#define SECCOMP_PT_IP(_regs) (_regs).pc +#define SECCOMP_PT_PARM1(_regs) (_regs).regs[0] +#define SECCOMP_PT_PARM2(_regs) (_regs).regs[1] +#define SECCOMP_PT_PARM3(_regs) (_regs).regs[2] +#define SECCOMP_PT_PARM4(_regs) (_regs).regs[3] +#define SECCOMP_PT_PARM5(_regs) (_regs).regs[4] +#define SECCOMP_PT_PARM6(_regs) (_regs).regs[5] +#else +#error Unsupported target platform + +#endif + +#endif // SANDBOX_LINUX_BPF_DSL_SECCOMP_MACROS_H_ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/syscall_set.cc b/security/sandbox/chromium/sandbox/linux/bpf_dsl/syscall_set.cc new file mode 100644 index 0000000000..55b2d5a49a --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/syscall_set.cc @@ -0,0 +1,150 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/bpf_dsl/syscall_set.h" + +#include <stdint.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/linux_syscall_ranges.h" + +namespace sandbox { + +namespace { + +#if defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS) +// This is true for Mips O32 ABI. +static_assert(MIN_SYSCALL == __NR_Linux, "min syscall number should be 4000"); +#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_64_BITS) +// This is true for MIPS N64 ABI. +static_assert(MIN_SYSCALL == __NR_Linux, "min syscall number should be 5000"); +#else +// This true for supported architectures (Intel and ARM EABI). +static_assert(MIN_SYSCALL == 0u, + "min syscall should always be zero"); +#endif + +// SyscallRange represents an inclusive range of system call numbers. +struct SyscallRange { + uint32_t first; + uint32_t last; +}; + +const SyscallRange kValidSyscallRanges[] = { + // First we iterate up to MAX_PUBLIC_SYSCALL, which is equal to MAX_SYSCALL + // on Intel architectures, but leaves room for private syscalls on ARM. + {MIN_SYSCALL, MAX_PUBLIC_SYSCALL}, +#if defined(__arm__) + // ARM EABI includes "ARM private" system calls starting at + // MIN_PRIVATE_SYSCALL, and a "ghost syscall private to the kernel" at + // MIN_GHOST_SYSCALL. + {MIN_PRIVATE_SYSCALL, MAX_PRIVATE_SYSCALL}, + {MIN_GHOST_SYSCALL, MAX_SYSCALL}, +#endif +}; + +} // namespace + +SyscallSet::Iterator SyscallSet::begin() const { + return Iterator(set_, false); +} + +SyscallSet::Iterator SyscallSet::end() const { + return Iterator(set_, true); +} + +bool SyscallSet::IsValid(uint32_t num) { + for (const SyscallRange& range : kValidSyscallRanges) { + if (num >= range.first && num <= range.last) { + return true; + } + } + return false; +} + +bool operator==(const SyscallSet& lhs, const SyscallSet& rhs) { + return (lhs.set_ == rhs.set_); +} + +SyscallSet::Iterator::Iterator(Set set, bool done) + : set_(set), done_(done), num_(0) { + // If the set doesn't contain 0, we need to skip to the next element. + if (!done && set_ == (IsValid(num_) ? Set::INVALID_ONLY : Set::VALID_ONLY)) { + ++*this; + } +} + +uint32_t SyscallSet::Iterator::operator*() const { + DCHECK(!done_); + return num_; +} + +SyscallSet::Iterator& SyscallSet::Iterator::operator++() { + DCHECK(!done_); + + num_ = NextSyscall(); + if (num_ == 0) { + done_ = true; + } + + return *this; +} + +// NextSyscall returns the next system call in the iterated system +// call set after |num_|, or 0 if no such system call exists. +uint32_t SyscallSet::Iterator::NextSyscall() const { + const bool want_valid = (set_ != Set::INVALID_ONLY); + const bool want_invalid = (set_ != Set::VALID_ONLY); + + for (const SyscallRange& range : kValidSyscallRanges) { + if (want_invalid && range.first > 0 && num_ < range.first - 1) { + // Even when iterating invalid syscalls, we only include the end points; + // so skip directly to just before the next (valid) range. + return range.first - 1; + } + if (want_valid && num_ < range.first) { + return range.first; + } + if (want_valid && num_ < range.last) { + return num_ + 1; + } + if (want_invalid && num_ <= range.last) { + return range.last + 1; + } + } + + if (want_invalid) { + // BPF programs only ever operate on unsigned quantities. So, + // that's how we iterate; we return values from + // 0..0xFFFFFFFFu. But there are places, where the kernel might + // interpret system call numbers as signed quantities, so the + // boundaries between signed and unsigned values are potential + // problem cases. We want to explicitly return these values from + // our iterator. + if (num_ < 0x7FFFFFFFu) + return 0x7FFFFFFFu; + if (num_ < 0x80000000u) + return 0x80000000u; + + if (num_ < 0xFFFFFFFFu) + return 0xFFFFFFFFu; + } + + return 0; +} + +bool operator==(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs) { + DCHECK(lhs.set_ == rhs.set_); + return (lhs.done_ == rhs.done_) && (lhs.num_ == rhs.num_); +} + +bool operator!=(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs) { + return !(lhs == rhs); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/syscall_set.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/syscall_set.h new file mode 100644 index 0000000000..b9f076d932 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/syscall_set.h @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ +#define SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ + +#include <stdint.h> + +#include <iterator> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Iterates over the entire system call range from 0..0xFFFFFFFFu. This +// iterator is aware of how system calls look like and will skip quickly +// over ranges that can't contain system calls. It iterates more slowly +// whenever it reaches a range that is potentially problematic, returning +// the last invalid value before a valid range of system calls, and the +// first invalid value after a valid range of syscalls. It iterates over +// individual values whenever it is in the normal range for system calls +// (typically MIN_SYSCALL..MAX_SYSCALL). +// +// Example usage: +// for (uint32_t sysnum : SyscallSet::All()) { +// // Do something with sysnum. +// } +class SANDBOX_EXPORT SyscallSet { + public: + class Iterator; + + SyscallSet(const SyscallSet& ss) : set_(ss.set_) {} + ~SyscallSet() {} + + Iterator begin() const; + Iterator end() const; + + // All returns a SyscallSet that contains both valid and invalid + // system call numbers. + static SyscallSet All() { return SyscallSet(Set::ALL); } + + // ValidOnly returns a SyscallSet that contains only valid system + // call numbers. + static SyscallSet ValidOnly() { return SyscallSet(Set::VALID_ONLY); } + + // InvalidOnly returns a SyscallSet that contains only invalid + // system call numbers, but still omits numbers in the middle of a + // range of invalid system call numbers. + static SyscallSet InvalidOnly() { return SyscallSet(Set::INVALID_ONLY); } + + // IsValid returns whether |num| specifies a valid system call + // number. + static bool IsValid(uint32_t num); + + private: + enum class Set { ALL, VALID_ONLY, INVALID_ONLY }; + + explicit SyscallSet(Set set) : set_(set) {} + + Set set_; + + friend bool operator==(const SyscallSet&, const SyscallSet&); + DISALLOW_ASSIGN(SyscallSet); +}; + +SANDBOX_EXPORT bool operator==(const SyscallSet& lhs, const SyscallSet& rhs); + +// Iterator provides C++ input iterator semantics for traversing a +// SyscallSet. +class SyscallSet::Iterator + : public std::iterator<std::input_iterator_tag, uint32_t> { + public: + Iterator(const Iterator& it) + : set_(it.set_), done_(it.done_), num_(it.num_) {} + ~Iterator() {} + + uint32_t operator*() const; + Iterator& operator++(); + + private: + Iterator(Set set, bool done); + + uint32_t NextSyscall() const; + + Set set_; + bool done_; + uint32_t num_; + + friend SyscallSet; + friend bool operator==(const Iterator&, const Iterator&); + DISALLOW_ASSIGN(Iterator); +}; + +SANDBOX_EXPORT bool operator==(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs); +SANDBOX_EXPORT bool operator!=(const SyscallSet::Iterator& lhs, + const SyscallSet::Iterator& rhs); + +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_SYSCALL_SET_H__ diff --git a/security/sandbox/chromium/sandbox/linux/bpf_dsl/trap_registry.h b/security/sandbox/chromium/sandbox/linux/bpf_dsl/trap_registry.h new file mode 100644 index 0000000000..0a5d2f14cc --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/bpf_dsl/trap_registry.h @@ -0,0 +1,73 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_BPF_DSL_TRAP_REGISTRY_H_ +#define SANDBOX_LINUX_BPF_DSL_TRAP_REGISTRY_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This must match the kernel's seccomp_data structure. +struct arch_seccomp_data { + int nr; + uint32_t arch; + uint64_t instruction_pointer; + uint64_t args[6]; +}; + +namespace bpf_dsl { + +// TrapRegistry provides an interface for registering "trap handlers" +// by associating them with non-zero 16-bit trap IDs. Trap IDs should +// remain valid for the lifetime of the trap registry. +class SANDBOX_EXPORT TrapRegistry { + public: + // TrapFnc is a pointer to a function that fulfills the trap handler + // function signature. + // + // Trap handlers follow the calling convention of native system + // calls; e.g., to report an error, they return an exit code in the + // range -1..-4096 instead of directly modifying errno. However, + // modifying errno is harmless, as the original value will be + // restored afterwards. + // + // Trap handlers are executed from signal context and possibly an + // async-signal context, so they must be async-signal safe: + // http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html + typedef intptr_t (*TrapFnc)(const struct arch_seccomp_data& args, void* aux); + + // Add registers the specified trap handler tuple and returns a + // non-zero trap ID that uniquely identifies the tuple for the life + // time of the trap registry. If the same tuple is registered + // multiple times, the same value will be returned each time. + virtual uint16_t Add(TrapFnc fnc, const void* aux, bool safe) = 0; + + // EnableUnsafeTraps tries to enable unsafe traps and returns + // whether it was successful. This is a one-way operation. + // + // CAUTION: Enabling unsafe traps effectively defeats the security + // guarantees provided by the sandbox policy. TrapRegistry + // implementations should ensure unsafe traps are only enabled + // during testing. + virtual bool EnableUnsafeTraps() = 0; + + protected: + TrapRegistry() {} + + // TrapRegistry's destructor is intentionally non-virtual so that + // implementations can omit their destructor. Instead we protect against + // misuse by marking it protected. + ~TrapRegistry() {} + + DISALLOW_COPY_AND_ASSIGN(TrapRegistry); +}; + +} // namespace bpf_dsl +} // namespace sandbox + +#endif // SANDBOX_LINUX_BPF_DSL_TRAP_REGISTRY_H_ diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h new file mode 100644 index 0000000000..a4315ba3c2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_ + +#include <memory> + +#include "base/macros.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h" + +namespace sandbox { + +// This templated class allows building a BPFTesterDelegate from a +// deprecated-style BPF policy (that is a SyscallEvaluator function pointer, +// instead of a SandboxBPFPolicy class), specified in |policy_function| and a +// function pointer to a test in |test_function|. +// This allows both the policy and the test function to take a pointer to an +// object of type "Aux" as a parameter. This is used to implement the BPF_TEST +// macro and should generally not be used directly. +template <class Policy, class Aux> +class BPFTesterCompatibilityDelegate : public BPFTesterDelegate { + public: + typedef void (*TestFunction)(Aux*); + + explicit BPFTesterCompatibilityDelegate(TestFunction test_function) + : aux_(), test_function_(test_function) {} + + ~BPFTesterCompatibilityDelegate() override {} + + std::unique_ptr<bpf_dsl::Policy> GetSandboxBPFPolicy() override { + // The current method is guaranteed to only run in the child process + // running the test. In this process, the current object is guaranteed + // to live forever. So it's ok to pass aux_pointer_for_policy_ to + // the policy, which could in turn pass it to the kernel via Trap(). + return std::unique_ptr<bpf_dsl::Policy>(new Policy(&aux_)); + } + + void RunTestFunction() override { + // Run the actual test. + // The current object is guaranteed to live forever in the child process + // where this will run. + test_function_(&aux_); + } + + private: + Aux aux_; + TestFunction test_function_; + + DISALLOW_COPY_AND_ASSIGN(BPFTesterCompatibilityDelegate); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTER_COMPATIBILITY_DELEGATE_H_ diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h new file mode 100644 index 0000000000..8b2b12afd8 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tests.h @@ -0,0 +1,124 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__ + +#include <memory> + +#include "base/logging.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/seccomp-bpf/bpf_tester_compatibility_delegate.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +// BPF_TEST_C() is a special version of SANDBOX_TEST(). It runs a test function +// in a sub-process, under a seccomp-bpf policy specified in +// |bpf_policy_class_name| without failing on configurations that are allowed +// to not support seccomp-bpf in their kernels. +// This is the preferred format for new BPF tests. |bpf_policy_class_name| is a +// class name (which will be default-constructed) that implements the +// Policy interface. +// The test function's body can simply follow. Test functions should use +// the BPF_ASSERT macros defined below, not GTEST's macros. The use of +// CHECK* macros is supported but less robust. +#define BPF_TEST_C(test_case_name, test_name, bpf_policy_class_name) \ + BPF_DEATH_TEST_C( \ + test_case_name, test_name, DEATH_SUCCESS(), bpf_policy_class_name) + +// Identical to BPF_TEST_C but allows to specify the nature of death. +#define BPF_DEATH_TEST_C( \ + test_case_name, test_name, death, bpf_policy_class_name) \ + void BPF_TEST_C_##test_name(); \ + TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \ + sandbox::SandboxBPFTestRunner bpf_test_runner( \ + new sandbox::BPFTesterSimpleDelegate<bpf_policy_class_name>( \ + BPF_TEST_C_##test_name)); \ + sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \ + } \ + void BPF_TEST_C_##test_name() + +// This form of BPF_TEST is a little verbose and should be reserved for complex +// tests where a lot of control is required. +// |bpf_tester_delegate_class| must be a classname implementing the +// BPFTesterDelegate interface. +#define BPF_TEST_D(test_case_name, test_name, bpf_tester_delegate_class) \ + BPF_DEATH_TEST_D( \ + test_case_name, test_name, DEATH_SUCCESS(), bpf_tester_delegate_class) + +// Identical to BPF_TEST_D but allows to specify the nature of death. +#define BPF_DEATH_TEST_D( \ + test_case_name, test_name, death, bpf_tester_delegate_class) \ + TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \ + sandbox::SandboxBPFTestRunner bpf_test_runner( \ + new bpf_tester_delegate_class()); \ + sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \ + } + +// Assertions are handled exactly the same as with a normal SANDBOX_TEST() +#define BPF_ASSERT SANDBOX_ASSERT +#define BPF_ASSERT_EQ(x, y) BPF_ASSERT((x) == (y)) +#define BPF_ASSERT_NE(x, y) BPF_ASSERT((x) != (y)) +#define BPF_ASSERT_LT(x, y) BPF_ASSERT((x) < (y)) +#define BPF_ASSERT_GT(x, y) BPF_ASSERT((x) > (y)) +#define BPF_ASSERT_LE(x, y) BPF_ASSERT((x) <= (y)) +#define BPF_ASSERT_GE(x, y) BPF_ASSERT((x) >= (y)) + +// This form of BPF_TEST is now discouraged (but still allowed) in favor of +// BPF_TEST_D and BPF_TEST_C. +// The |policy| parameter should be a Policy subclass. +// BPF_TEST() takes a C++ data type as an fourth parameter. A variable +// of this type will be allocated and a pointer to it will be +// available within the test function as "BPF_AUX". The pointer will +// also be passed as an argument to the policy's constructor. Policies +// would typically use it as an argument to SandboxBPF::Trap(), if +// they want to communicate data between the BPF_TEST() and a Trap() +// function. The life-time of this object is the same as the life-time +// of the process running under the seccomp-bpf policy. +// |aux| must not be void. +#define BPF_TEST(test_case_name, test_name, policy, aux) \ + BPF_DEATH_TEST(test_case_name, test_name, DEATH_SUCCESS(), policy, aux) + +// A BPF_DEATH_TEST is just the same as a BPF_TEST, but it assumes that the +// test will fail with a particular known error condition. Use the DEATH_XXX() +// macros from unit_tests.h to specify the expected error condition. +#define BPF_DEATH_TEST(test_case_name, test_name, death, policy, aux) \ + void BPF_TEST_##test_name(aux* BPF_AUX); \ + TEST(test_case_name, DISABLE_ON_TSAN(test_name)) { \ + sandbox::SandboxBPFTestRunner bpf_test_runner( \ + new sandbox::BPFTesterCompatibilityDelegate<policy, aux>( \ + BPF_TEST_##test_name)); \ + sandbox::UnitTests::RunTestInProcess(&bpf_test_runner, death); \ + } \ + void BPF_TEST_##test_name(aux* BPF_AUX) + +// This class takes a simple function pointer as a constructor parameter and a +// class name as a template parameter to implement the BPFTesterDelegate +// interface which can be used to build BPF unittests with +// the SandboxBPFTestRunner class. +template <class PolicyClass> +class BPFTesterSimpleDelegate : public BPFTesterDelegate { + public: + explicit BPFTesterSimpleDelegate(void (*test_function)(void)) + : test_function_(test_function) {} + ~BPFTesterSimpleDelegate() override {} + + std::unique_ptr<bpf_dsl::Policy> GetSandboxBPFPolicy() override { + return std::unique_ptr<bpf_dsl::Policy>(new PolicyClass()); + } + void RunTestFunction() override { + DCHECK(test_function_); + test_function_(); + } + + private: + void (*test_function_)(void); + DISALLOW_COPY_AND_ASSIGN(BPFTesterSimpleDelegate); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_BPF_TESTS_H__ diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc new file mode 100644 index 0000000000..d45bc87292 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/bpf_tests_unittest.cc @@ -0,0 +1,155 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" + +#include <errno.h> +#include <sys/ptrace.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <memory> + +#include "base/logging.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::Error; +using sandbox::bpf_dsl::ResultExpr; + +namespace sandbox { + +namespace { + +class FourtyTwo { + public: + static const int kMagicValue = 42; + FourtyTwo() : value_(kMagicValue) {} + int value() { return value_; } + + private: + int value_; + DISALLOW_COPY_AND_ASSIGN(FourtyTwo); +}; + +class EmptyClassTakingPolicy : public bpf_dsl::Policy { + public: + explicit EmptyClassTakingPolicy(FourtyTwo* fourty_two) { + BPF_ASSERT(fourty_two); + BPF_ASSERT(FourtyTwo::kMagicValue == fourty_two->value()); + } + ~EmptyClassTakingPolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + return Allow(); + } +}; + +BPF_TEST(BPFTest, + BPFAUXPointsToClass, + EmptyClassTakingPolicy, + FourtyTwo /* *BPF_AUX */) { + // BPF_AUX should point to an instance of FourtyTwo. + BPF_ASSERT(BPF_AUX); + BPF_ASSERT(FourtyTwo::kMagicValue == BPF_AUX->value()); +} + +void DummyTestFunction(FourtyTwo *fourty_two) { +} + +TEST(BPFTest, BPFTesterCompatibilityDelegateLeakTest) { + // Don't do anything, simply gives dynamic tools an opportunity to detect + // leaks. + { + BPFTesterCompatibilityDelegate<EmptyClassTakingPolicy, FourtyTwo> + simple_delegate(DummyTestFunction); + } + { + // Test polymorphism. + std::unique_ptr<BPFTesterDelegate> simple_delegate( + new BPFTesterCompatibilityDelegate<EmptyClassTakingPolicy, FourtyTwo>( + DummyTestFunction)); + } +} + +class EnosysPtracePolicy : public bpf_dsl::Policy { + public: + EnosysPtracePolicy() { my_pid_ = sys_getpid(); } + ~EnosysPtracePolicy() override { + // Policies should be able to bind with the process on which they are + // created. They should never be created in a parent process. + BPF_ASSERT_EQ(my_pid_, sys_getpid()); + } + + ResultExpr EvaluateSyscall(int system_call_number) const override { + CHECK(SandboxBPF::IsValidSyscallNumber(system_call_number)); + if (system_call_number == __NR_ptrace) { + // The EvaluateSyscall function should run in the process that created + // the current object. + BPF_ASSERT_EQ(my_pid_, sys_getpid()); + return Error(ENOSYS); + } else { + return Allow(); + } + } + + private: + pid_t my_pid_; + DISALLOW_COPY_AND_ASSIGN(EnosysPtracePolicy); +}; + +class BasicBPFTesterDelegate : public BPFTesterDelegate { + public: + BasicBPFTesterDelegate() {} + ~BasicBPFTesterDelegate() override {} + + std::unique_ptr<bpf_dsl::Policy> GetSandboxBPFPolicy() override { + return std::unique_ptr<bpf_dsl::Policy>(new EnosysPtracePolicy()); + } + void RunTestFunction() override { + errno = 0; + int ret = ptrace(PTRACE_TRACEME, -1, NULL, NULL); + BPF_ASSERT(-1 == ret); + BPF_ASSERT(ENOSYS == errno); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BasicBPFTesterDelegate); +}; + +// This is the most powerful and complex way to create a BPF test, but it +// requires a full class definition (BasicBPFTesterDelegate). +BPF_TEST_D(BPFTest, BPFTestWithDelegateClass, BasicBPFTesterDelegate) + +// This is the simplest form of BPF tests. +BPF_TEST_C(BPFTest, BPFTestWithInlineTest, EnosysPtracePolicy) { + errno = 0; + int ret = ptrace(PTRACE_TRACEME, -1, NULL, NULL); + BPF_ASSERT(-1 == ret); + BPF_ASSERT(ENOSYS == errno); +} + +const char kHelloMessage[] = "Hello"; + +BPF_DEATH_TEST_C(BPFTest, + BPFDeathTestWithInlineTest, + DEATH_MESSAGE(kHelloMessage), + EnosysPtracePolicy) { + LOG(ERROR) << kHelloMessage; + _exit(1); +} + +} // namespace + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/die.cc b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/die.cc new file mode 100644 index 0000000000..3baf1f13d9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/die.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/die.h" + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include <string> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_signal.h" + +namespace sandbox { + +void Die::ExitGroup() { + // exit_group() should exit our program. After all, it is defined as a + // function that doesn't return. But things can theoretically go wrong. + // Especially, since we are dealing with system call filters. Continuing + // execution would be very bad in most cases where ExitGroup() gets called. + // So, we'll try a few other strategies too. + Syscall::Call(__NR_exit_group, 1); + + // We have no idea what our run-time environment looks like. So, signal + // handlers might or might not do the right thing. Try to reset settings + // to a defined state; but we have not way to verify whether we actually + // succeeded in doing so. Nonetheless, triggering a fatal signal could help + // us terminate. + struct sigaction sa = {}; + sa.sa_handler = LINUX_SIG_DFL; + sa.sa_flags = LINUX_SA_RESTART; + sys_sigaction(LINUX_SIGSEGV, &sa, nullptr); + Syscall::Call(__NR_prctl, PR_SET_DUMPABLE, (void*)0, (void*)0, (void*)0); + if (*(volatile char*)0) { + } + + // If there is no way for us to ask for the program to exit, the next + // best thing we can do is to loop indefinitely. Maybe, somebody will notice + // and file a bug... + // We in fact retry the system call inside of our loop so that it will + // stand out when somebody tries to diagnose the problem by using "strace". + for (;;) { + Syscall::Call(__NR_exit_group, 1); + } +} + +void Die::SandboxDie(const char* msg, const char* file, int line) { + if (simple_exit_) { + LogToStderr(msg, file, line); + } else { + logging::LogMessage(file, line, logging::LOG_FATAL).stream() << msg; + } + ExitGroup(); +} + +void Die::RawSandboxDie(const char* msg) { + if (!msg) + msg = ""; + RAW_LOG(FATAL, msg); + ExitGroup(); +} + +void Die::SandboxInfo(const char* msg, const char* file, int line) { + if (!suppress_info_) { + logging::LogMessage(file, line, logging::LOG_INFO).stream() << msg; + } +} + +void Die::LogToStderr(const char* msg, const char* file, int line) { + if (msg) { + char buf[40]; + snprintf(buf, sizeof(buf), "%d", line); + std::string s = std::string(file) + ":" + buf + ":" + msg + "\n"; + + // No need to loop. Short write()s are unlikely and if they happen we + // probably prefer them over a loop that blocks. + ignore_result( + HANDLE_EINTR(Syscall::Call(__NR_write, 2, s.c_str(), s.length()))); + } +} + +bool Die::simple_exit_ = false; +bool Die::suppress_info_ = false; + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/die.h b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/die.h new file mode 100644 index 0000000000..b3f3f72c2f --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/die.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ + +#include "base/macros.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This is the main API for using this file. Prints a error message and +// exits with a fatal error. This is not async-signal safe. +#define SANDBOX_DIE(m) sandbox::Die::SandboxDie(m, __FILE__, __LINE__) + +// An async signal safe version of the same API. Won't print the filename +// and line numbers. +#define RAW_SANDBOX_DIE(m) sandbox::Die::RawSandboxDie(m) + +// Adds an informational message to the log file or stderr as appropriate. +#define SANDBOX_INFO(m) sandbox::Die::SandboxInfo(m, __FILE__, __LINE__) + +class SANDBOX_EXPORT Die { + public: + // Terminate the program, even if the current sandbox policy prevents some + // of the more commonly used functions used for exiting. + // Most users would want to call SANDBOX_DIE() instead, as it logs extra + // information. But calling ExitGroup() is correct and in some rare cases + // preferable. So, we make it part of the public API. + static void ExitGroup() __attribute__((noreturn)); + + // This method gets called by SANDBOX_DIE(). There is normally no reason + // to call it directly unless you are defining your own exiting macro. + static void SandboxDie(const char* msg, const char* file, int line) + __attribute__((noreturn)); + + static void RawSandboxDie(const char* msg) __attribute__((noreturn)); + + // This method gets called by SANDBOX_INFO(). There is normally no reason + // to call it directly unless you are defining your own logging macro. + static void SandboxInfo(const char* msg, const char* file, int line); + + // Writes a message to stderr. Used as a fall-back choice, if we don't have + // any other way to report an error. + static void LogToStderr(const char* msg, const char* file, int line); + + // We generally want to run all exit handlers. This means, on SANDBOX_DIE() + // we should be calling LOG(FATAL). But there are some situations where + // we just need to print a message and then terminate. This would typically + // happen in cases where we consume the error message internally (e.g. in + // unit tests or in the supportsSeccompSandbox() method). + static void EnableSimpleExit() { simple_exit_ = true; } + + // Sometimes we need to disable all informational messages (e.g. from within + // unittests). + static void SuppressInfoMessages(bool flag) { suppress_info_ = flag; } + + private: + static bool simple_exit_; + static bool suppress_info_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(Die); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_DIE_H__ diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc new file mode 100644 index 0000000000..72a79670d3 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.cc @@ -0,0 +1,259 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" + +#include <errno.h> +#include <stdint.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/compiler_specific.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/bpf_dsl/syscall_set.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf/trap.h" +#include "sandbox/linux/services/proc_util.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/services/thread_helpers.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { + +namespace { + +// Check if the kernel supports seccomp-filter (a.k.a. seccomp mode 2) via +// prctl(). +bool KernelSupportsSeccompBPF() { + errno = 0; + const int rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, nullptr); + + if (rv == -1 && EFAULT == errno) { + return true; + } + return false; +} + +// LG introduced a buggy syscall, sys_set_media_ext, with the same number as +// seccomp. Return true if the current kernel has this buggy syscall. +// +// We want this to work with upcoming versions of seccomp, so we pass bogus +// flags that are unlikely to ever be used by the kernel. A normal kernel would +// return -EINVAL, but a buggy LG kernel would return 1. +bool KernelHasLGBug() { +#if defined(OS_ANDROID) + // sys_set_media will see this as NULL, which should be a safe (non-crashing) + // way to invoke it. A genuine seccomp syscall will see it as + // SECCOMP_SET_MODE_STRICT. + const unsigned int operation = 0; + // Chosen by fair dice roll. Guaranteed to be random. + const unsigned int flags = 0xf7a46a5c; + const int rv = sys_seccomp(operation, flags, nullptr); + // A genuine kernel would return -EINVAL (which would set rv to -1 and errno + // to EINVAL), or at the very least return some kind of error (which would + // set rv to -1). Any other behavior indicates that whatever code received + // our syscall was not the real seccomp. + if (rv != -1) { + return true; + } +#endif // defined(OS_ANDROID) + + return false; +} + +// Check if the kernel supports seccomp-filter via the seccomp system call +// and the TSYNC feature to enable seccomp on all threads. +bool KernelSupportsSeccompTsync() { + if (KernelHasLGBug()) { + return false; + } + + errno = 0; + const int rv = + sys_seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, nullptr); + + if (rv == -1 && errno == EFAULT) { + return true; + } + + DCHECK_EQ(-1, rv); + DCHECK(ENOSYS == errno || EINVAL == errno); + return false; +} + +uint64_t EscapePC() { + intptr_t rv = Syscall::Call(-1); + if (rv == -1 && errno == ENOSYS) { + return 0; + } + return static_cast<uint64_t>(static_cast<uintptr_t>(rv)); +} + +intptr_t SandboxPanicTrap(const struct arch_seccomp_data&, void* aux) { + SANDBOX_DIE(static_cast<const char*>(aux)); +} + +bpf_dsl::ResultExpr SandboxPanic(const char* error) { + return bpf_dsl::Trap(SandboxPanicTrap, error); +} + +} // namespace + +SandboxBPF::SandboxBPF(std::unique_ptr<bpf_dsl::Policy> policy) + : proc_fd_(), sandbox_has_started_(false), policy_(std::move(policy)) {} + +SandboxBPF::~SandboxBPF() { +} + +// static +bool SandboxBPF::SupportsSeccompSandbox(SeccompLevel level) { + switch (level) { + case SeccompLevel::SINGLE_THREADED: + return KernelSupportsSeccompBPF(); + case SeccompLevel::MULTI_THREADED: + return KernelSupportsSeccompTsync(); + } + NOTREACHED(); + return false; +} + +bool SandboxBPF::StartSandbox(SeccompLevel seccomp_level) { + DCHECK(policy_); + CHECK(seccomp_level == SeccompLevel::SINGLE_THREADED || + seccomp_level == SeccompLevel::MULTI_THREADED); + + if (sandbox_has_started_) { + SANDBOX_DIE( + "Cannot repeatedly start sandbox. Create a separate Sandbox " + "object instead."); + return false; + } + + if (!proc_fd_.is_valid()) { + SetProcFd(ProcUtil::OpenProc()); + } + + const bool supports_tsync = KernelSupportsSeccompTsync(); + + if (seccomp_level == SeccompLevel::SINGLE_THREADED) { + // Wait for /proc/self/task/ to update if needed and assert the + // process is single threaded. + ThreadHelpers::AssertSingleThreaded(proc_fd_.get()); + } else if (seccomp_level == SeccompLevel::MULTI_THREADED) { + if (!supports_tsync) { + SANDBOX_DIE("Cannot start sandbox; kernel does not support synchronizing " + "filters for a threadgroup"); + return false; + } + } + + // We no longer need access to any files in /proc. We want to do this + // before installing the filters, just in case that our policy denies + // close(). + if (proc_fd_.is_valid()) { + proc_fd_.reset(); + } + + // Install the filters. + InstallFilter(supports_tsync || + seccomp_level == SeccompLevel::MULTI_THREADED); + + return true; +} + +void SandboxBPF::SetProcFd(base::ScopedFD proc_fd) { + proc_fd_.swap(proc_fd); +} + +// static +bool SandboxBPF::IsValidSyscallNumber(int sysnum) { + return SyscallSet::IsValid(sysnum); +} + +// static +bool SandboxBPF::IsRequiredForUnsafeTrap(int sysno) { + return bpf_dsl::PolicyCompiler::IsRequiredForUnsafeTrap(sysno); +} + +// static +intptr_t SandboxBPF::ForwardSyscall(const struct arch_seccomp_data& args) { + return Syscall::Call( + args.nr, static_cast<intptr_t>(args.args[0]), + static_cast<intptr_t>(args.args[1]), static_cast<intptr_t>(args.args[2]), + static_cast<intptr_t>(args.args[3]), static_cast<intptr_t>(args.args[4]), + static_cast<intptr_t>(args.args[5])); +} + +CodeGen::Program SandboxBPF::AssembleFilter() { + DCHECK(policy_); + + bpf_dsl::PolicyCompiler compiler(policy_.get(), Trap::Registry()); + if (Trap::SandboxDebuggingAllowedByUser()) { + compiler.DangerousSetEscapePC(EscapePC()); + } + compiler.SetPanicFunc(SandboxPanic); + return compiler.Compile(); +} + +void SandboxBPF::InstallFilter(bool must_sync_threads) { + // We want to be very careful in not imposing any requirements on the + // policies that are set with SetSandboxPolicy(). This means, as soon as + // the sandbox is active, we shouldn't be relying on libraries that could + // be making system calls. This, for example, means we should avoid + // using the heap and we should avoid using STL functions. + // Temporarily copy the contents of the "program" vector into a + // stack-allocated array; and then explicitly destroy that object. + // This makes sure we don't ex- or implicitly call new/delete after we + // installed the BPF filter program in the kernel. Depending on the + // system memory allocator that is in effect, these operators can result + // in system calls to things like munmap() or brk(). + CodeGen::Program program = AssembleFilter(); + + struct sock_filter bpf[program.size()]; + const struct sock_fprog prog = {static_cast<unsigned short>(program.size()), + bpf}; + memcpy(bpf, &program[0], sizeof(bpf)); + CodeGen::Program().swap(program); // vector swap trick + + // Make an attempt to release memory that is no longer needed here, rather + // than in the destructor. Try to avoid as much as possible to presume of + // what will be possible to do in the new (sandboxed) execution environment. + policy_.reset(); + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + SANDBOX_DIE("Kernel refuses to enable no-new-privs"); + } + + // Install BPF filter program. If the thread state indicates multi-threading + // support, then the kernel hass the seccomp system call. Otherwise, fall + // back on prctl, which requires the process to be single-threaded. + if (must_sync_threads) { + int rv = + sys_seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog); + if (rv) { + SANDBOX_DIE( + "Kernel refuses to turn on and synchronize threads for BPF filters"); + } + } else { + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + SANDBOX_DIE("Kernel refuses to turn on BPF filters"); + } + } + + sandbox_has_started_ = true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h new file mode 100644 index 0000000000..282852992b --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf.h @@ -0,0 +1,113 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/files/scoped_file.h" +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/codegen.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { +struct arch_seccomp_data; + +// This class can be used to apply a syscall sandboxing policy expressed in a +// bpf_dsl::Policy object to the current process. +// Syscall sandboxing policies get inherited by subprocesses and, once applied, +// can never be removed for the lifetime of the process. +class SANDBOX_EXPORT SandboxBPF { + public: + enum class SeccompLevel { + SINGLE_THREADED, + MULTI_THREADED, + }; + + // Ownership of |policy| is transfered here to the sandbox object. + // nullptr is allowed for unit tests. + explicit SandboxBPF(std::unique_ptr<bpf_dsl::Policy> policy); + // NOTE: Setting a policy and starting the sandbox is a one-way operation. + // The kernel does not provide any option for unloading a loaded sandbox. The + // sandbox remains engaged even when the object is destructed. + ~SandboxBPF(); + + // Detect if the kernel supports the specified seccomp level. + // See StartSandbox() for a description of these. + static bool SupportsSeccompSandbox(SeccompLevel level); + + // This is the main public entry point. It sets up the resources needed by + // the sandbox, and enters Seccomp mode. + // The calling process must provide a |level| to tell the sandbox which type + // of kernel support it should engage. + // SINGLE_THREADED will only sandbox the calling thread. Since it would be a + // security risk, the sandbox will also check that the current process is + // single threaded and crash if it isn't the case. + // MULTI_THREADED requires more recent kernel support and allows to sandbox + // all the threads of the current process. Be mindful of potential races, + // with other threads using disallowed system calls either before or after + // the sandbox is engaged. + // + // It is possible to stack multiple sandboxes by creating separate "Sandbox" + // objects and calling "StartSandbox()" on each of them. Please note, that + // this requires special care, though, as newly stacked sandboxes can never + // relax restrictions imposed by earlier sandboxes. Furthermore, installing + // a new policy requires making system calls, that might already be + // disallowed. + // Finally, stacking does add more kernel overhead than having a single + // combined policy. So, it should only be used if there are no alternatives. + bool StartSandbox(SeccompLevel level) WARN_UNUSED_RESULT; + + // The sandbox needs to be able to access files in "/proc/self/". If + // this directory is not accessible when "StartSandbox()" gets called, the + // caller must provide an already opened file descriptor by calling + // "SetProcFd()". + // The sandbox becomes the new owner of this file descriptor and will + // close it when "StartSandbox()" executes or when the sandbox object + // disappears. + void SetProcFd(base::ScopedFD proc_fd); + + // Checks whether a particular system call number is valid on the current + // architecture. + static bool IsValidSyscallNumber(int sysnum); + + // UnsafeTraps require some syscalls to always be allowed. + // This helper function returns true for these calls. + static bool IsRequiredForUnsafeTrap(int sysno); + + // From within an UnsafeTrap() it is often useful to be able to execute + // the system call that triggered the trap. The ForwardSyscall() method + // makes this easy. It is more efficient than calling glibc's syscall() + // function, as it avoid the extra round-trip to the signal handler. And + // it automatically does the correct thing to report kernel-style error + // conditions, rather than setting errno. See the comments for TrapFnc for + // details. In other words, the return value from ForwardSyscall() is + // directly suitable as a return value for a trap handler. + static intptr_t ForwardSyscall(const struct arch_seccomp_data& args); + + private: + friend class SandboxBPFTestRunner; + + // Assembles a BPF filter program from the current policy. After calling this + // function, you must not call any other sandboxing function. + CodeGen::Program AssembleFilter(); + + // Assembles and installs a filter based on the policy that has previously + // been configured with SetSandboxPolicy(). + void InstallFilter(bool must_sync_threads); + + base::ScopedFD proc_fd_; + bool sandbox_has_started_; + std::unique_ptr<bpf_dsl::Policy> policy_; + + DISALLOW_COPY_AND_ASSIGN(SandboxBPF); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_ diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc new file mode 100644 index 0000000000..36f3744b76 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc @@ -0,0 +1,66 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h" + +#include <fcntl.h> + +#include <memory> + +#include "base/logging.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/system_headers/linux_filter.h" +#include "sandbox/linux/tests/unit_tests.h" + +namespace sandbox { + +SandboxBPFTestRunner::SandboxBPFTestRunner( + BPFTesterDelegate* bpf_tester_delegate) + : bpf_tester_delegate_(bpf_tester_delegate) { +} + +SandboxBPFTestRunner::~SandboxBPFTestRunner() { +} + +void SandboxBPFTestRunner::Run() { + DCHECK(bpf_tester_delegate_); + sandbox::Die::EnableSimpleExit(); + + std::unique_ptr<bpf_dsl::Policy> policy = + bpf_tester_delegate_->GetSandboxBPFPolicy(); + + if (sandbox::SandboxBPF::SupportsSeccompSandbox( + SandboxBPF::SeccompLevel::SINGLE_THREADED)) { + // Initialize and then start the sandbox with our custom policy + sandbox::SandboxBPF sandbox(std::move(policy)); + SANDBOX_ASSERT(sandbox.StartSandbox( + sandbox::SandboxBPF::SeccompLevel::SINGLE_THREADED)); + + // Run the actual test. + bpf_tester_delegate_->RunTestFunction(); + } else { + printf("This BPF test is not fully running in this configuration!\n"); + // Android is the only configuration where we accept not having kernel + // BPF support. + if (!IsAndroid()) { + const bool seccomp_bpf_is_supported = false; + SANDBOX_ASSERT(seccomp_bpf_is_supported); + } + // Call the compiler and verify the policy. That's the least we can do, + // if we don't have kernel support. + sandbox::SandboxBPF sandbox(std::move(policy)); + sandbox.AssembleFilter(); + sandbox::UnitTests::IgnoreThisTest(); + } +} + +bool SandboxBPFTestRunner::ShouldCheckForLeaks() const { + // LSAN requires being able to use ptrace() and other system calls that could + // be denied. + return false; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h new file mode 100644 index 0000000000..4fc3c5d169 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_TEST_RUNNER_H_ +#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_TEST_RUNNER_H_ + +#include <memory> + +#include "base/macros.h" +#include "sandbox/linux/tests/sandbox_test_runner.h" + +namespace sandbox { +namespace bpf_dsl { +class Policy; +} + +// To create a SandboxBPFTestRunner object, one needs to implement this +// interface and pass an instance to the SandboxBPFTestRunner constructor. +// In the child process running the test, the BPFTesterDelegate object is +// guaranteed to not be destroyed until the child process terminates. +class BPFTesterDelegate { + public: + BPFTesterDelegate() {} + virtual ~BPFTesterDelegate() {} + + // This will instanciate a policy suitable for the test we want to run. It is + // guaranteed to only be called from the child process that will run the + // test. + virtual std::unique_ptr<bpf_dsl::Policy> GetSandboxBPFPolicy() = 0; + // This will be called from a child process with the BPF sandbox turned on. + virtual void RunTestFunction() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(BPFTesterDelegate); +}; + +// This class implements the SandboxTestRunner interface and Run() will +// initialize a seccomp-bpf sandbox (specified by |bpf_tester_delegate|) and +// run a test function (via |bpf_tester_delegate|) if the current kernel +// configuration allows it. If it can not run the test under seccomp-bpf, +// Run() will still compile the policy which should allow to get some coverage +// under tools that behave like Valgrind. +class SandboxBPFTestRunner : public SandboxTestRunner { + public: + // This constructor takes ownership of the |bpf_tester_delegate| object. + // (It doesn't take a std::unique_ptr since they make polymorphism verbose). + explicit SandboxBPFTestRunner(BPFTesterDelegate* bpf_tester_delegate); + ~SandboxBPFTestRunner() override; + + void Run() override; + + bool ShouldCheckForLeaks() const override; + + private: + std::unique_ptr<BPFTesterDelegate> bpf_tester_delegate_; + DISALLOW_COPY_AND_ASSIGN(SandboxBPFTestRunner); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_TEST_RUNNER_H_ diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall.cc b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall.cc new file mode 100644 index 0000000000..34edabd2b8 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall.cc @@ -0,0 +1,481 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/syscall.h" + +#include <errno.h> +#include <stdint.h> + +#include "base/logging.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" + +namespace sandbox { + +namespace { + +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS_FAMILY) +// Number that's not currently used by any Linux kernel ABIs. +const int kInvalidSyscallNumber = 0x351d3; +#else +#error Unrecognized architecture +#endif + +asm(// We need to be able to tell the kernel exactly where we made a + // system call. The C++ compiler likes to sometimes clone or + // inline code, which would inadvertently end up duplicating + // the entry point. + // "gcc" can suppress code duplication with suitable function + // attributes, but "clang" doesn't have this ability. + // The "clang" developer mailing list suggested that the correct + // and portable solution is a file-scope assembly block. + // N.B. We do mark our code as a proper function so that backtraces + // work correctly. But we make absolutely no attempt to use the + // ABI's calling conventions for passing arguments. We will only + // ever be called from assembly code and thus can pick more + // suitable calling conventions. +#if defined(__i386__) + ".text\n" + ".align 16, 0x90\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.cfi_startproc\n" + // Check if "%eax" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "int $0x80". This address can be + // used as a marker that BPF code inspects. + "test %eax, %eax\n" + "jge 1f\n" + // Always, make sure that our code is position-independent, or + // address space randomization might not work on i386. This means, + // we can't use "lea", but instead have to rely on "call/pop". + "call 0f; .cfi_adjust_cfa_offset 4\n" + "0:pop %eax; .cfi_adjust_cfa_offset -4\n" + "addl $2f-0b, %eax\n" + "ret\n" + // Save register that we don't want to clobber. On i386, we need to + // save relatively aggressively, as there are a couple or registers + // that are used internally (e.g. %ebx for position-independent + // code, and %ebp for the frame pointer), and as we need to keep at + // least a few registers available for the register allocator. + "1:push %esi; .cfi_adjust_cfa_offset 4; .cfi_rel_offset esi, 0\n" + "push %edi; .cfi_adjust_cfa_offset 4; .cfi_rel_offset edi, 0\n" + "push %ebx; .cfi_adjust_cfa_offset 4; .cfi_rel_offset ebx, 0\n" + "push %ebp; .cfi_adjust_cfa_offset 4; .cfi_rel_offset ebp, 0\n" + // Copy entries from the array holding the arguments into the + // correct CPU registers. + "movl 0(%edi), %ebx\n" + "movl 4(%edi), %ecx\n" + "movl 8(%edi), %edx\n" + "movl 12(%edi), %esi\n" + "movl 20(%edi), %ebp\n" + "movl 16(%edi), %edi\n" + // Enter the kernel. + "int $0x80\n" + // This is our "magic" return address that the BPF filter sees. + "2:" + // Restore any clobbered registers that we didn't declare to the + // compiler. + "pop %ebp; .cfi_restore ebp; .cfi_adjust_cfa_offset -4\n" + "pop %ebx; .cfi_restore ebx; .cfi_adjust_cfa_offset -4\n" + "pop %edi; .cfi_restore edi; .cfi_adjust_cfa_offset -4\n" + "pop %esi; .cfi_restore esi; .cfi_adjust_cfa_offset -4\n" + "ret\n" + ".cfi_endproc\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__x86_64__) + ".text\n" + ".align 16, 0x90\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.cfi_startproc\n" + // Check if "%rdi" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "syscall". This address can be + // used as a marker that BPF code inspects. + "test %rdi, %rdi\n" + "jge 1f\n" + // Always make sure that our code is position-independent, or the + // linker will throw a hissy fit on x86-64. + "lea 2f(%rip), %rax\n" + "ret\n" + // Now we load the registers used to pass arguments to the system + // call: system call number in %rax, and arguments in %rdi, %rsi, + // %rdx, %r10, %r8, %r9. Note: These are all caller-save registers + // (only %rbx, %rbp, %rsp, and %r12-%r15 are callee-save), so no + // need to worry here about spilling registers or CFI directives. + "1:movq %rdi, %rax\n" + "movq 0(%rsi), %rdi\n" + "movq 16(%rsi), %rdx\n" + "movq 24(%rsi), %r10\n" + "movq 32(%rsi), %r8\n" + "movq 40(%rsi), %r9\n" + "movq 8(%rsi), %rsi\n" + // Enter the kernel. + "syscall\n" + // This is our "magic" return address that the BPF filter sees. + "2:ret\n" + ".cfi_endproc\n" + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif defined(__arm__) + // Throughout this file, we use the same mode (ARM vs. thumb) + // that the C++ compiler uses. This means, when transfering control + // from C++ to assembly code, we do not need to switch modes (e.g. + // by using the "bx" instruction). It also means that our assembly + // code should not be invoked directly from code that lives in + // other compilation units, as we don't bother implementing thumb + // interworking. That's OK, as we don't make any of the assembly + // symbols public. They are all local to this file. + ".text\n" + ".align 2\n" + ".type SyscallAsm, %function\n" +#if defined(__thumb__) + ".thumb_func\n" +#else + ".arm\n" +#endif + "SyscallAsm:\n" +#if !defined(__native_client_nonsfi__) + // .fnstart and .fnend pseudo operations creates unwind table. + // It also creates a reference to the symbol __aeabi_unwind_cpp_pr0, which + // is not provided by PNaCl toolchain. Disable it. + ".fnstart\n" +#endif + "@ args = 0, pretend = 0, frame = 8\n" + "@ frame_needed = 1, uses_anonymous_args = 0\n" +#if defined(__thumb__) + ".cfi_startproc\n" + "push {r7, lr}\n" + ".save {r7, lr}\n" + ".cfi_offset 14, -4\n" + ".cfi_offset 7, -8\n" + ".cfi_def_cfa_offset 8\n" +#else + "stmfd sp!, {fp, lr}\n" + "add fp, sp, #4\n" +#endif + // Check if "r0" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "swi 0". This address can be + // used as a marker that BPF code inspects. + "cmp r0, #0\n" + "bge 1f\n" + "adr r0, 2f\n" + "b 2f\n" + // We declared (almost) all clobbered registers to the compiler. On + // ARM there is no particular register pressure. So, we can go + // ahead and directly copy the entries from the arguments array + // into the appropriate CPU registers. + "1:ldr r5, [r6, #20]\n" + "ldr r4, [r6, #16]\n" + "ldr r3, [r6, #12]\n" + "ldr r2, [r6, #8]\n" + "ldr r1, [r6, #4]\n" + "mov r7, r0\n" + "ldr r0, [r6, #0]\n" + // Enter the kernel + "swi 0\n" +// Restore the frame pointer. Also restore the program counter from +// the link register; this makes us return to the caller. +#if defined(__thumb__) + "2:pop {r7, pc}\n" + ".cfi_endproc\n" +#else + "2:ldmfd sp!, {fp, pc}\n" +#endif +#if !defined(__native_client_nonsfi__) + // Do not use .fnstart and .fnend for PNaCl toolchain. See above comment, + // for more details. + ".fnend\n" +#endif + "9:.size SyscallAsm, 9b-SyscallAsm\n" +#elif (defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS)) + ".text\n" + ".option pic2\n" + ".align 4\n" + ".global SyscallAsm\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.ent SyscallAsm\n" + ".frame $sp, 40, $ra\n" + ".set push\n" + ".set noreorder\n" + ".cpload $t9\n" + "addiu $sp, $sp, -40\n" + "sw $ra, 36($sp)\n" + // Check if "v0" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "syscall". This address can be + // used as a marker that BPF code inspects. + "bgez $v0, 1f\n" + " nop\n" + // This is equivalent to "la $v0, 2f". + // LA macro has to be avoided since LLVM-AS has issue with LA in PIC mode + // https://llvm.org/bugs/show_bug.cgi?id=27644 + "lw $v0, %got(2f)($gp)\n" + "addiu $v0, $v0, %lo(2f)\n" + "b 2f\n" + " nop\n" + // On MIPS first four arguments go to registers a0 - a3 and any + // argument after that goes to stack. We can go ahead and directly + // copy the entries from the arguments array into the appropriate + // CPU registers and on the stack. + "1:lw $a3, 28($a0)\n" + "lw $a2, 24($a0)\n" + "lw $a1, 20($a0)\n" + "lw $t0, 16($a0)\n" + "sw $a3, 28($sp)\n" + "sw $a2, 24($sp)\n" + "sw $a1, 20($sp)\n" + "sw $t0, 16($sp)\n" + "lw $a3, 12($a0)\n" + "lw $a2, 8($a0)\n" + "lw $a1, 4($a0)\n" + "lw $a0, 0($a0)\n" + // Enter the kernel + "syscall\n" + // This is our "magic" return address that the BPF filter sees. + // Restore the return address from the stack. + "2:lw $ra, 36($sp)\n" + "jr $ra\n" + " addiu $sp, $sp, 40\n" + ".set pop\n" + ".end SyscallAsm\n" + ".size SyscallAsm,.-SyscallAsm\n" +#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_64_BITS) + ".text\n" + ".option pic2\n" + ".global SyscallAsm\n" + ".type SyscallAsm, @function\n" + "SyscallAsm:.ent SyscallAsm\n" + ".frame $sp, 16, $ra\n" + ".set push\n" + ".set noreorder\n" + "daddiu $sp, $sp, -16\n" + ".cpsetup $25, 0, SyscallAsm\n" + "sd $ra, 8($sp)\n" + // Check if "v0" is negative. If so, do not attempt to make a + // system call. Instead, compute the return address that is visible + // to the kernel after we execute "syscall". This address can be + // used as a marker that BPF code inspects. + "bgez $v0, 1f\n" + " nop\n" + // This is equivalent to "la $v0, 2f". + // LA macro has to be avoided since LLVM-AS has issue with LA in PIC mode + // https://llvm.org/bugs/show_bug.cgi?id=27644 + "ld $v0, %got(2f)($gp)\n" + "daddiu $v0, $v0, %lo(2f)\n" + "b 2f\n" + " nop\n" + // On MIPS N64 all eight arguments go to registers a0 - a7 + // We can go ahead and directly copy the entries from the arguments array + // into the appropriate CPU registers. + "1:ld $a7, 56($a0)\n" + "ld $a6, 48($a0)\n" + "ld $a5, 40($a0)\n" + "ld $a4, 32($a0)\n" + "ld $a3, 24($a0)\n" + "ld $a2, 16($a0)\n" + "ld $a1, 8($a0)\n" + "ld $a0, 0($a0)\n" + // Enter the kernel + "syscall\n" + // This is our "magic" return address that the BPF filter sees. + // Restore the return address from the stack. + "2:ld $ra, 8($sp)\n" + ".cpreturn\n" + "jr $ra\n" + "daddiu $sp, $sp, 16\n" + ".set pop\n" + ".end SyscallAsm\n" + ".size SyscallAsm,.-SyscallAsm\n" +#elif defined(__aarch64__) + ".text\n" + ".align 2\n" + ".type SyscallAsm, %function\n" + "SyscallAsm:\n" + ".cfi_startproc\n" + "cmp x0, #0\n" + "b.ge 1f\n" + "adr x0,2f\n" + "b 2f\n" + "1:ldr x5, [x6, #40]\n" + "ldr x4, [x6, #32]\n" + "ldr x3, [x6, #24]\n" + "ldr x2, [x6, #16]\n" + "ldr x1, [x6, #8]\n" + "mov x8, x0\n" + "ldr x0, [x6, #0]\n" + // Enter the kernel + "svc 0\n" + "2:ret\n" + ".cfi_endproc\n" + ".size SyscallAsm, .-SyscallAsm\n" +#endif + ); // asm + +#if defined(__x86_64__) +extern "C" { +intptr_t SyscallAsm(intptr_t nr, const intptr_t args[6]); +} +#elif defined(__mips__) +extern "C" { +intptr_t SyscallAsm(intptr_t nr, const intptr_t args[8]); +} +#endif + +} // namespace + +intptr_t Syscall::InvalidCall() { + // Explicitly pass eight zero arguments just in case. + return Call(kInvalidSyscallNumber, 0, 0, 0, 0, 0, 0, 0, 0); +} + +intptr_t Syscall::Call(int nr, + intptr_t p0, + intptr_t p1, + intptr_t p2, + intptr_t p3, + intptr_t p4, + intptr_t p5, + intptr_t p6, + intptr_t p7) { + // We rely on "intptr_t" to be the exact size as a "void *". This is + // typically true, but just in case, we add a check. The language + // specification allows platforms some leeway in cases, where + // "sizeof(void *)" is not the same as "sizeof(void (*)())". We expect + // that this would only be an issue for IA64, which we are currently not + // planning on supporting. And it is even possible that this would work + // on IA64, but for lack of actual hardware, I cannot test. + static_assert(sizeof(void*) == sizeof(intptr_t), + "pointer types and intptr_t must be exactly the same size"); + + // TODO(nedeljko): Enable use of more than six parameters on architectures + // where that makes sense. +#if defined(__mips__) + const intptr_t args[8] = {p0, p1, p2, p3, p4, p5, p6, p7}; +#else + DCHECK_EQ(p6, 0) << " Support for syscalls with more than six arguments not " + "added for this architecture"; + DCHECK_EQ(p7, 0) << " Support for syscalls with more than six arguments not " + "added for this architecture"; + const intptr_t args[6] = {p0, p1, p2, p3, p4, p5}; +#endif // defined(__mips__) + +// Invoke our file-scope assembly code. The constraints have been picked +// carefully to match what the rest of the assembly code expects in input, +// output, and clobbered registers. +#if defined(__i386__) + intptr_t ret = nr; + asm volatile( + "call SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=a"(ret) + : "0"(ret), "D"(args) + : "cc", "esp", "memory", "ecx", "edx"); +#elif defined(__x86_64__) + intptr_t ret = SyscallAsm(nr, args); +#elif defined(__arm__) + intptr_t ret; + { + register intptr_t inout __asm__("r0") = nr; + register const intptr_t* data __asm__("r6") = args; + asm volatile( + "bl SyscallAsm\n" + // N.B. These are not the calling conventions normally used by the ABI. + : "=r"(inout) + : "0"(inout), "r"(data) + : "cc", + "lr", + "memory", + "r1", + "r2", + "r3", + "r4", + "r5" +#if !defined(__thumb__) + // In thumb mode, we cannot use "r7" as a general purpose register, as + // it is our frame pointer. We have to manually manage and preserve + // it. + // In ARM mode, we have a dedicated frame pointer register and "r7" is + // thus available as a general purpose register. We don't preserve it, + // but instead mark it as clobbered. + , + "r7" +#endif // !defined(__thumb__) + ); + ret = inout; + } +#elif defined(__mips__) + intptr_t err_status; + intptr_t ret = Syscall::SandboxSyscallRaw(nr, args, &err_status); + + if (err_status) { + // On error, MIPS returns errno from syscall instead of -errno. + // The purpose of this negation is for SandboxSyscall() to behave + // more like it would on other architectures. + ret = -ret; + } +#elif defined(__aarch64__) + intptr_t ret; + { + register intptr_t inout __asm__("x0") = nr; + register const intptr_t* data __asm__("x6") = args; + asm volatile("bl SyscallAsm\n" + : "=r"(inout) + : "0"(inout), "r"(data) + : "memory", "x1", "x2", "x3", "x4", "x5", "x8", "x30"); + ret = inout; + } + +#else +#error "Unimplemented architecture" +#endif + return ret; +} + +void Syscall::PutValueInUcontext(intptr_t ret_val, ucontext_t* ctx) { +#if defined(__mips__) + // Mips ABI states that on error a3 CPU register has non zero value and if + // there is no error, it should be zero. + if (ret_val <= -1 && ret_val >= -4095) { + // |ret_val| followes the Syscall::Call() convention of being -errno on + // errors. In order to write correct value to return register this sign + // needs to be changed back. + ret_val = -ret_val; + SECCOMP_PARM4(ctx) = 1; + } else + SECCOMP_PARM4(ctx) = 0; +#endif + SECCOMP_RESULT(ctx) = static_cast<greg_t>(ret_val); +} + +#if defined(__mips__) +intptr_t Syscall::SandboxSyscallRaw(int nr, + const intptr_t* args, + intptr_t* err_ret) { + register intptr_t ret __asm__("v0") = nr; + register intptr_t syscallasm __asm__("t9") = (intptr_t) &SyscallAsm; + // a3 register becomes non zero on error. + register intptr_t err_stat __asm__("a3") = 0; + { + register const intptr_t* data __asm__("a0") = args; + asm volatile( + "jalr $t9\n" + " nop\n" + : "=r"(ret), "=r"(err_stat) + : "0"(ret), + "r"(data), + "r"(syscallasm) + // a2 is in the clober list so inline assembly can not change its + // value. + : "memory", "ra", "a2"); + } + + // Set an error status so it can be used outside of this function + *err_ret = err_stat; + + return ret; +} +#endif // defined(__mips__) + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall.h b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall.h new file mode 100644 index 0000000000..3b02a6723f --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall.h @@ -0,0 +1,166 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ + +#include <signal.h> +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// This purely static class can be used to perform system calls with some +// low-level control. +class SANDBOX_EXPORT Syscall { + public: + // InvalidCall() invokes Call() with a platform-appropriate syscall + // number that is guaranteed to not be implemented (i.e., normally + // returns -ENOSYS). + // This is primarily meant to be useful for writing sandbox policy + // unit tests. + static intptr_t InvalidCall(); + + // System calls can take up to six parameters (up to eight on some + // architectures). Traditionally, glibc + // implements this property by using variadic argument lists. This works, but + // confuses tools that behave like Valgrind, because we are nominally passing + // uninitialized data whenever we call through this function and pass less + // than the full six arguments. + // So, instead, we use C++'s template system to achieve a very similar + // effect. C++ automatically sets the unused parameters to zero for us, and + // it also does the correct type expansion (e.g. from 32bit to 64bit) where + // necessary. + // We have to use C-style cast operators as we want to be able to accept both + // integer and pointer types. + template <class T0, + class T1, + class T2, + class T3, + class T4, + class T5, + class T6, + class T7> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7) { + return Call(nr, + (intptr_t)p0, + (intptr_t)p1, + (intptr_t)p2, + (intptr_t)p3, + (intptr_t)p4, + (intptr_t)p5, + (intptr_t)p6, + (intptr_t)p7); + } + + template <class T0, + class T1, + class T2, + class T3, + class T4, + class T5, + class T6> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6) { + return Call(nr, + (intptr_t)p0, + (intptr_t)p1, + (intptr_t)p2, + (intptr_t)p3, + (intptr_t)p4, + (intptr_t)p5, + (intptr_t)p6, + 0); + } + + template <class T0, class T1, class T2, class T3, class T4, class T5> + static inline intptr_t + Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) { + return Call(nr, + (intptr_t)p0, + (intptr_t)p1, + (intptr_t)p2, + (intptr_t)p3, + (intptr_t)p4, + (intptr_t)p5, + 0, + 0); + } + + template <class T0, class T1, class T2, class T3, class T4> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4) { + return Call(nr, p0, p1, p2, p3, p4, 0, 0, 0); + } + + template <class T0, class T1, class T2, class T3> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2, T3 p3) { + return Call(nr, p0, p1, p2, p3, 0, 0, 0, 0); + } + + template <class T0, class T1, class T2> + static inline intptr_t Call(int nr, T0 p0, T1 p1, T2 p2) { + return Call(nr, p0, p1, p2, 0, 0, 0, 0, 0); + } + + template <class T0, class T1> + static inline intptr_t Call(int nr, T0 p0, T1 p1) { + return Call(nr, p0, p1, 0, 0, 0, 0, 0, 0); + } + + template <class T0> + static inline intptr_t Call(int nr, T0 p0) { + return Call(nr, p0, 0, 0, 0, 0, 0, 0, 0); + } + + static inline intptr_t Call(int nr) { + return Call(nr, 0, 0, 0, 0, 0, 0, 0, 0); + } + + // Set the registers in |ctx| to match what they would be after a system call + // returning |ret_val|. |ret_val| must follow the Syscall::Call() convention + // of being -errno on errors. + static void PutValueInUcontext(intptr_t ret_val, ucontext_t* ctx); + + private: + // This performs system call |nr| with the arguments p0 to p7 from a constant + // userland address, which is for instance observable by seccomp-bpf filters. + // The constant userland address from which these system calls are made will + // be returned if |nr| is passed as -1. + // On error, this function will return a value between -1 and -4095 which + // should be interpreted as -errno. + static intptr_t Call(int nr, + intptr_t p0, + intptr_t p1, + intptr_t p2, + intptr_t p3, + intptr_t p4, + intptr_t p5, + intptr_t p6, + intptr_t p7); + +#if defined(__mips__) + // This function basically does on MIPS what SandboxSyscall() is doing on + // other architectures. However, because of specificity of MIPS regarding + // handling syscall errors, SandboxSyscall() is made as a wrapper for this + // function in order for SandboxSyscall() to behave more like on other + // architectures on places where return value from SandboxSyscall() is used + // directly (like in most tests). + // The syscall "nr" is called with arguments that are set in an array on which + // pointer "args" points to and an information weather there is an error or no + // is returned to SandboxSyscall() by err_stat. + static intptr_t SandboxSyscallRaw(int nr, + const intptr_t* args, + intptr_t* err_stat); +#endif // defined(__mips__) + + DISALLOW_IMPLICIT_CONSTRUCTORS(Syscall); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__ diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc new file mode 100644 index 0000000000..2b776d287b --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/syscall_unittest.cc @@ -0,0 +1,249 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/syscall.h" + +#include <asm/unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <vector> + +#include "base/posix/eintr_wrapper.h" +#include "base/process/process_metrics.h" +#include "base/stl_util.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/policy.h" +#include "sandbox/linux/seccomp-bpf/bpf_tests.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::ResultExpr; +using sandbox::bpf_dsl::Trap; + +namespace sandbox { + +namespace { + +TEST(Syscall, InvalidCallReturnsENOSYS) { + EXPECT_EQ(-ENOSYS, Syscall::InvalidCall()); +} + +TEST(Syscall, WellKnownEntryPoint) { +// Test that Syscall::Call(-1) is handled specially. Don't do this on ARM, +// where syscall(-1) crashes with SIGILL. Not running the test is fine, as we +// are still testing ARM code in the next set of tests. +#if !defined(__arm__) && !defined(__aarch64__) + EXPECT_NE(Syscall::Call(-1), syscall(-1)); +#endif + +// If possible, test that Syscall::Call(-1) returns the address right +// after +// a kernel entry point. +#if defined(__i386__) + EXPECT_EQ(0x80CDu, ((uint16_t*)Syscall::Call(-1))[-1]); // INT 0x80 +#elif defined(__x86_64__) + EXPECT_EQ(0x050Fu, ((uint16_t*)Syscall::Call(-1))[-1]); // SYSCALL +#elif defined(__arm__) +#if defined(__thumb__) + EXPECT_EQ(0xDF00u, ((uint16_t*)Syscall::Call(-1))[-1]); // SWI 0 +#else + EXPECT_EQ(0xEF000000u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 +#endif +#elif defined(__mips__) + // Opcode for MIPS sycall is in the lower 16-bits + EXPECT_EQ(0x0cu, (((uint32_t*)Syscall::Call(-1))[-1]) & 0x0000FFFF); +#elif defined(__aarch64__) + EXPECT_EQ(0xD4000001u, ((uint32_t*)Syscall::Call(-1))[-1]); // SVC 0 +#else +#warning Incomplete test case; need port for target platform +#endif +} + +TEST(Syscall, TrivialSyscallNoArgs) { + // Test that we can do basic system calls + EXPECT_EQ(Syscall::Call(__NR_getpid), syscall(__NR_getpid)); +} + +TEST(Syscall, TrivialSyscallOneArg) { + int new_fd; + // Duplicate standard error and close it. + ASSERT_GE(new_fd = Syscall::Call(__NR_dup, 2), 0); + int close_return_value = IGNORE_EINTR(Syscall::Call(__NR_close, new_fd)); + ASSERT_EQ(close_return_value, 0); +} + +TEST(Syscall, TrivialFailingSyscall) { + errno = -42; + int ret = Syscall::Call(__NR_dup, -1); + ASSERT_EQ(-EBADF, ret); + // Verify that Syscall::Call does not touch errno. + ASSERT_EQ(-42, errno); +} + +// SIGSYS trap handler that will be called on __NR_uname. +intptr_t CopySyscallArgsToAux(const struct arch_seccomp_data& args, void* aux) { + // |aux| is our BPF_AUX pointer. + std::vector<uint64_t>* const seen_syscall_args = + static_cast<std::vector<uint64_t>*>(aux); + BPF_ASSERT(base::size(args.args) == 6); + seen_syscall_args->assign(args.args, args.args + base::size(args.args)); + return -ENOMEM; +} + +class CopyAllArgsOnUnamePolicy : public bpf_dsl::Policy { + public: + explicit CopyAllArgsOnUnamePolicy(std::vector<uint64_t>* aux) : aux_(aux) {} + ~CopyAllArgsOnUnamePolicy() override {} + + ResultExpr EvaluateSyscall(int sysno) const override { + DCHECK(SandboxBPF::IsValidSyscallNumber(sysno)); + if (sysno == __NR_uname) { + return Trap(CopySyscallArgsToAux, aux_); + } else { + return Allow(); + } + } + + private: + std::vector<uint64_t>* aux_; + + DISALLOW_COPY_AND_ASSIGN(CopyAllArgsOnUnamePolicy); +}; + +// We are testing Syscall::Call() by making use of a BPF filter that +// allows us +// to inspect the system call arguments that the kernel saw. +BPF_TEST(Syscall, + SyntheticSixArgs, + CopyAllArgsOnUnamePolicy, + std::vector<uint64_t> /* (*BPF_AUX) */) { + const int kExpectedValue = 42; + // In this test we only pass integers to the kernel. We might want to make + // additional tests to try other types. What we will see depends on + // implementation details of kernel BPF filters and we will need to document + // the expected behavior very clearly. + int syscall_args[6]; + for (size_t i = 0; i < base::size(syscall_args); ++i) { + syscall_args[i] = kExpectedValue + i; + } + + // We could use pretty much any system call we don't need here. uname() is + // nice because it doesn't have any dangerous side effects. + BPF_ASSERT(Syscall::Call(__NR_uname, + syscall_args[0], + syscall_args[1], + syscall_args[2], + syscall_args[3], + syscall_args[4], + syscall_args[5]) == -ENOMEM); + + // We expect the trap handler to have copied the 6 arguments. + BPF_ASSERT(BPF_AUX->size() == 6); + + // Don't loop here so that we can see which argument does cause the failure + // easily from the failing line. + // uint64_t is the type passed to our SIGSYS handler. + BPF_ASSERT((*BPF_AUX)[0] == static_cast<uint64_t>(syscall_args[0])); + BPF_ASSERT((*BPF_AUX)[1] == static_cast<uint64_t>(syscall_args[1])); + BPF_ASSERT((*BPF_AUX)[2] == static_cast<uint64_t>(syscall_args[2])); + BPF_ASSERT((*BPF_AUX)[3] == static_cast<uint64_t>(syscall_args[3])); + BPF_ASSERT((*BPF_AUX)[4] == static_cast<uint64_t>(syscall_args[4])); + BPF_ASSERT((*BPF_AUX)[5] == static_cast<uint64_t>(syscall_args[5])); +} + +TEST(Syscall, ComplexSyscallSixArgs) { + int fd; + const size_t kPageSize = base::GetPageSize(); + + ASSERT_LE(0, + fd = Syscall::Call(__NR_openat, AT_FDCWD, "/dev/null", O_RDWR, 0L)); + + // Use mmap() to allocate some read-only memory + char* addr0; + ASSERT_NE( + (char*)NULL, + addr0 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, + (void*)NULL, + kPageSize, + PROT_READ, + MAP_PRIVATE | MAP_ANONYMOUS, + fd, + 0L))); + + // Try to replace the existing mapping with a read-write mapping + char* addr1; + ASSERT_EQ(addr0, + addr1 = reinterpret_cast<char*>( + Syscall::Call(kMMapNr, + addr0, + kPageSize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, + fd, + 0L))); + ++*addr1; // This should not seg fault + + // Clean up + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr1, kPageSize)); + EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); + + // Check that the offset argument (i.e. the sixth argument) is processed + // correctly. + ASSERT_GE( + fd = Syscall::Call(__NR_openat, AT_FDCWD, "/proc/self/exe", O_RDONLY, 0L), + 0); + char* addr2, *addr3; + ASSERT_NE((char*)NULL, + addr2 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, + (void*)NULL, + 2 * kPageSize, + PROT_READ, + MAP_PRIVATE, + fd, + 0L + ))); + ASSERT_NE((char*)NULL, + addr3 = reinterpret_cast<char*>(Syscall::Call(kMMapNr, + (void*)NULL, + kPageSize, + PROT_READ, + MAP_PRIVATE, + fd, +#if defined(__NR_mmap2) + 1L +#else + kPageSize +#endif + ))); + EXPECT_EQ(0, memcmp(addr2 + kPageSize, addr3, kPageSize)); + + // Just to be absolutely on the safe side, also verify that the file + // contents matches what we are getting from a read() operation. + char buf[2 * kPageSize]; + EXPECT_EQ(2 * kPageSize, static_cast<size_t>(Syscall::Call(__NR_read, + fd, + buf, + 2 * kPageSize + ))); + EXPECT_EQ(0, memcmp(addr2, buf, 2 * kPageSize)); + + // Clean up + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr2, 2 * kPageSize)); + EXPECT_EQ(0, Syscall::Call(__NR_munmap, addr3, kPageSize)); + EXPECT_EQ(0, IGNORE_EINTR(Syscall::Call(__NR_close, fd))); +} + +} // namespace + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/trap.cc b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/trap.cc new file mode 100644 index 0000000000..9884be8bb2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/trap.cc @@ -0,0 +1,394 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/seccomp-bpf/trap.h" + +#include <errno.h> +#include <signal.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <sys/syscall.h> + +#include <algorithm> +#include <limits> +#include <tuple> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "sandbox/linux/bpf_dsl/seccomp_macros.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/services/syscall_wrappers.h" +#include "sandbox/linux/system_headers/linux_seccomp.h" +#include "sandbox/linux/system_headers/linux_signal.h" + +namespace { + +struct arch_sigsys { + void* ip; + int nr; + unsigned int arch; +}; + +const int kCapacityIncrement = 20; + +// Unsafe traps can only be turned on, if the user explicitly allowed them +// by setting the CHROME_SANDBOX_DEBUGGING environment variable. +const char kSandboxDebuggingEnv[] = "CHROME_SANDBOX_DEBUGGING"; + +// We need to tell whether we are performing a "normal" callback, or +// whether we were called recursively from within a UnsafeTrap() callback. +// This is a little tricky to do, because we need to somehow get access to +// per-thread data from within a signal context. Normal TLS storage is not +// safely accessible at this time. We could roll our own, but that involves +// a lot of complexity. Instead, we co-opt one bit in the signal mask. +// If BUS is blocked, we assume that we have been called recursively. +// There is a possibility for collision with other code that needs to do +// this, but in practice the risks are low. +// If SIGBUS turns out to be a problem, we could instead co-opt one of the +// realtime signals. There are plenty of them. Unfortunately, there is no +// way to mark a signal as allocated. So, the potential for collision is +// possibly even worse. +bool GetIsInSigHandler(const ucontext_t* ctx) { + // Note: on Android, sigismember does not take a pointer to const. + return sigismember(const_cast<sigset_t*>(&ctx->uc_sigmask), LINUX_SIGBUS); +} + +void SetIsInSigHandler() { + sigset_t mask; + if (sigemptyset(&mask) || sigaddset(&mask, LINUX_SIGBUS) || + sandbox::sys_sigprocmask(LINUX_SIG_BLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to block SIGBUS"); + } +} + +bool IsDefaultSignalAction(const struct sigaction& sa) { + if (sa.sa_flags & SA_SIGINFO || sa.sa_handler != SIG_DFL) { + return false; + } + return true; +} + +} // namespace + +namespace sandbox { + +Trap::Trap() + : trap_array_(NULL), + trap_array_size_(0), + trap_array_capacity_(0), + has_unsafe_traps_(false) { + // Set new SIGSYS handler + struct sigaction sa = {}; + // In some toolchain, sa_sigaction is not declared in struct sigaction. + // So, here cast the pointer to the sa_handler's type. This works because + // |sa_handler| and |sa_sigaction| shares the same memory. + sa.sa_handler = reinterpret_cast<void (*)(int)>(SigSysAction); + sa.sa_flags = LINUX_SA_SIGINFO | LINUX_SA_NODEFER; + struct sigaction old_sa = {}; + if (sys_sigaction(LINUX_SIGSYS, &sa, &old_sa) < 0) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } + + if (!IsDefaultSignalAction(old_sa)) { + static const char kExistingSIGSYSMsg[] = + "Existing signal handler when trying to install SIGSYS. SIGSYS needs " + "to be reserved for seccomp-bpf."; + DLOG(FATAL) << kExistingSIGSYSMsg; + LOG(ERROR) << kExistingSIGSYSMsg; + } + + // Unmask SIGSYS + sigset_t mask; + if (sigemptyset(&mask) || sigaddset(&mask, LINUX_SIGSYS) || + sys_sigprocmask(LINUX_SIG_UNBLOCK, &mask, NULL)) { + SANDBOX_DIE("Failed to configure SIGSYS handler"); + } +} + +bpf_dsl::TrapRegistry* Trap::Registry() { + // Note: This class is not thread safe. It is the caller's responsibility + // to avoid race conditions. Normally, this is a non-issue as the sandbox + // can only be initialized if there are no other threads present. + // Also, this is not a normal singleton. Once created, the global trap + // object must never be destroyed again. + if (!global_trap_) { + global_trap_ = new Trap(); + if (!global_trap_) { + SANDBOX_DIE("Failed to allocate global trap handler"); + } + } + return global_trap_; +} + +void Trap::SigSysAction(int nr, LinuxSigInfo* info, void* void_context) { + if (info) { + MSAN_UNPOISON(info, sizeof(*info)); + } + + // Obtain the signal context. This, most notably, gives us access to + // all CPU registers at the time of the signal. + ucontext_t* ctx = reinterpret_cast<ucontext_t*>(void_context); + if (ctx) { + MSAN_UNPOISON(ctx, sizeof(*ctx)); + } + + if (!global_trap_) { + RAW_SANDBOX_DIE( + "This can't happen. Found no global singleton instance " + "for Trap() handling."); + } + global_trap_->SigSys(nr, info, ctx); +} + +void Trap::SigSys(int nr, LinuxSigInfo* info, ucontext_t* ctx) { + // Signal handlers should always preserve "errno". Otherwise, we could + // trigger really subtle bugs. + const int old_errno = errno; + + // Various sanity checks to make sure we actually received a signal + // triggered by a BPF filter. If something else triggered SIGSYS + // (e.g. kill()), there is really nothing we can do with this signal. + if (nr != LINUX_SIGSYS || info->si_code != SYS_SECCOMP || !ctx || + info->si_errno <= 0 || + static_cast<size_t>(info->si_errno) > trap_array_size_) { + // ATI drivers seem to send SIGSYS, so this cannot be FATAL. + // See crbug.com/178166. + // TODO(jln): add a DCHECK or move back to FATAL. + RAW_LOG(ERROR, "Unexpected SIGSYS received."); + errno = old_errno; + return; + } + + + // Obtain the siginfo information that is specific to SIGSYS. + struct arch_sigsys sigsys; +#if defined(si_call_addr) && !defined(__native_client_nonsfi__) + sigsys.ip = info->si_call_addr; + sigsys.nr = info->si_syscall; + sigsys.arch = info->si_arch; +#else + // If the version of glibc doesn't include this information in + // siginfo_t (older than 2.17), we need to explicitly copy it + // into an arch_sigsys structure. + memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); +#endif + +#if defined(__mips__) + // When indirect syscall (syscall(__NR_foo, ...)) is made on Mips, the + // number in register SECCOMP_SYSCALL(ctx) is always __NR_syscall and the + // real number of a syscall (__NR_foo) is in SECCOMP_PARM1(ctx) + bool sigsys_nr_is_bad = sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)) && + sigsys.nr != static_cast<int>(SECCOMP_PARM1(ctx)); +#else + bool sigsys_nr_is_bad = sigsys.nr != static_cast<int>(SECCOMP_SYSCALL(ctx)); +#endif + + // Some more sanity checks. + if (sigsys.ip != reinterpret_cast<void*>(SECCOMP_IP(ctx)) || + sigsys_nr_is_bad || sigsys.arch != SECCOMP_ARCH) { + // TODO(markus): + // SANDBOX_DIE() can call LOG(FATAL). This is not normally async-signal + // safe and can lead to bugs. We should eventually implement a different + // logging and reporting mechanism that is safe to be called from + // the sigSys() handler. + RAW_SANDBOX_DIE("Sanity checks are failing after receiving SIGSYS."); + } + + intptr_t rc; + if (has_unsafe_traps_ && GetIsInSigHandler(ctx)) { + errno = old_errno; + if (sigsys.nr == __NR_clone) { + RAW_SANDBOX_DIE("Cannot call clone() from an UnsafeTrap() handler."); + } +#if defined(__mips__) + // Mips supports up to eight arguments for syscall. + // However, seccomp bpf can filter only up to six arguments, so using eight + // arguments has sense only when using UnsafeTrap() handler. + rc = Syscall::Call(SECCOMP_SYSCALL(ctx), + SECCOMP_PARM1(ctx), + SECCOMP_PARM2(ctx), + SECCOMP_PARM3(ctx), + SECCOMP_PARM4(ctx), + SECCOMP_PARM5(ctx), + SECCOMP_PARM6(ctx), + SECCOMP_PARM7(ctx), + SECCOMP_PARM8(ctx)); +#else + rc = Syscall::Call(SECCOMP_SYSCALL(ctx), + SECCOMP_PARM1(ctx), + SECCOMP_PARM2(ctx), + SECCOMP_PARM3(ctx), + SECCOMP_PARM4(ctx), + SECCOMP_PARM5(ctx), + SECCOMP_PARM6(ctx)); +#endif // defined(__mips__) + } else { + const TrapKey& trap = trap_array_[info->si_errno - 1]; + if (!trap.safe) { + SetIsInSigHandler(); + } + + // Copy the seccomp-specific data into a arch_seccomp_data structure. This + // is what we are showing to TrapFnc callbacks that the system call + // evaluator registered with the sandbox. + struct arch_seccomp_data data = { + static_cast<int>(SECCOMP_SYSCALL(ctx)), + SECCOMP_ARCH, + reinterpret_cast<uint64_t>(sigsys.ip), + {static_cast<uint64_t>(SECCOMP_PARM1(ctx)), + static_cast<uint64_t>(SECCOMP_PARM2(ctx)), + static_cast<uint64_t>(SECCOMP_PARM3(ctx)), + static_cast<uint64_t>(SECCOMP_PARM4(ctx)), + static_cast<uint64_t>(SECCOMP_PARM5(ctx)), + static_cast<uint64_t>(SECCOMP_PARM6(ctx))}}; + + // Now call the TrapFnc callback associated with this particular instance + // of SECCOMP_RET_TRAP. + rc = trap.fnc(data, const_cast<void*>(trap.aux)); + } + + // Update the CPU register that stores the return code of the system call + // that we just handled, and restore "errno" to the value that it had + // before entering the signal handler. + Syscall::PutValueInUcontext(rc, ctx); + errno = old_errno; + + return; +} + +bool Trap::TrapKey::operator<(const TrapKey& o) const { + return std::tie(fnc, aux, safe) < std::tie(o.fnc, o.aux, o.safe); +} + +uint16_t Trap::Add(TrapFnc fnc, const void* aux, bool safe) { + if (!safe && !SandboxDebuggingAllowedByUser()) { + // Unless the user set the CHROME_SANDBOX_DEBUGGING environment variable, + // we never return an ErrorCode that is marked as "unsafe". This also + // means, the BPF compiler will never emit code that allow unsafe system + // calls to by-pass the filter (because they use the magic return address + // from Syscall::Call(-1)). + + // This SANDBOX_DIE() can optionally be removed. It won't break security, + // but it might make error messages from the BPF compiler a little harder + // to understand. Removing the SANDBOX_DIE() allows callers to easily check + // whether unsafe traps are supported (by checking whether the returned + // ErrorCode is ET_INVALID). + SANDBOX_DIE( + "Cannot use unsafe traps unless CHROME_SANDBOX_DEBUGGING " + "is enabled"); + + return 0; + } + + // Each unique pair of TrapFnc and auxiliary data make up a distinct instance + // of a SECCOMP_RET_TRAP. + TrapKey key(fnc, aux, safe); + + // We return unique identifiers together with SECCOMP_RET_TRAP. This allows + // us to associate trap with the appropriate handler. The kernel allows us + // identifiers in the range from 0 to SECCOMP_RET_DATA (0xFFFF). We want to + // avoid 0, as it could be confused for a trap without any specific id. + // The nice thing about sequentially numbered identifiers is that we can also + // trivially look them up from our signal handler without making any system + // calls that might be async-signal-unsafe. + // In order to do so, we store all of our traps in a C-style trap_array_. + + TrapIds::const_iterator iter = trap_ids_.find(key); + if (iter != trap_ids_.end()) { + // We have seen this pair before. Return the same id that we assigned + // earlier. + return iter->second; + } + + // This is a new pair. Remember it and assign a new id. + if (trap_array_size_ >= SECCOMP_RET_DATA /* 0xFFFF */ || + trap_array_size_ >= std::numeric_limits<uint16_t>::max()) { + // In practice, this is pretty much impossible to trigger, as there + // are other kernel limitations that restrict overall BPF program sizes. + SANDBOX_DIE("Too many SECCOMP_RET_TRAP callback instances"); + } + + // Our callers ensure that there are no other threads accessing trap_array_ + // concurrently (typically this is done by ensuring that we are single- + // threaded while the sandbox is being set up). But we nonetheless are + // modifying a live data structure that could be accessed any time a + // system call is made; as system calls could be triggering SIGSYS. + // So, we have to be extra careful that we update trap_array_ atomically. + // In particular, this means we shouldn't be using realloc() to resize it. + // Instead, we allocate a new array, copy the values, and then switch the + // pointer. We only really care about the pointer being updated atomically + // and the data that is pointed to being valid, as these are the only + // values accessed from the signal handler. It is OK if trap_array_size_ + // is inconsistent with the pointer, as it is monotonously increasing. + // Also, we only care about compiler barriers, as the signal handler is + // triggered synchronously from a system call. We don't have to protect + // against issues with the memory model or with completely asynchronous + // events. + if (trap_array_size_ >= trap_array_capacity_) { + trap_array_capacity_ += kCapacityIncrement; + TrapKey* old_trap_array = trap_array_; + TrapKey* new_trap_array = new TrapKey[trap_array_capacity_]; + std::copy_n(old_trap_array, trap_array_size_, new_trap_array); + + // Language specs are unclear on whether the compiler is allowed to move + // the "delete[]" above our preceding assignments and/or memory moves, + // iff the compiler believes that "delete[]" doesn't have any other + // global side-effects. + // We insert optimization barriers to prevent this from happening. + // The first barrier is probably not needed, but better be explicit in + // what we want to tell the compiler. + // The clang developer mailing list couldn't answer whether this is a + // legitimate worry; but they at least thought that the barrier is + // sufficient to prevent the (so far hypothetical) problem of re-ordering + // of instructions by the compiler. + // + // TODO(mdempsky): Try to clean this up using base/atomicops or C++11 + // atomics; see crbug.com/414363. + asm volatile("" : "=r"(new_trap_array) : "0"(new_trap_array) : "memory"); + trap_array_ = new_trap_array; + asm volatile("" : "=r"(trap_array_) : "0"(trap_array_) : "memory"); + + delete[] old_trap_array; + } + + uint16_t id = trap_array_size_ + 1; + trap_ids_[key] = id; + trap_array_[trap_array_size_] = key; + trap_array_size_++; + return id; +} + +bool Trap::SandboxDebuggingAllowedByUser() { + const char* debug_flag = getenv(kSandboxDebuggingEnv); + return debug_flag && *debug_flag; +} + +bool Trap::EnableUnsafeTraps() { + if (!has_unsafe_traps_) { + // Unsafe traps are a one-way fuse. Once enabled, they can never be turned + // off again. + // We only allow enabling unsafe traps, if the user explicitly set an + // appropriate environment variable. This prevents bugs that accidentally + // disable all sandboxing for all users. + if (SandboxDebuggingAllowedByUser()) { + // We only ever print this message once, when we enable unsafe traps the + // first time. + SANDBOX_INFO("WARNING! Disabling sandbox for debugging purposes"); + has_unsafe_traps_ = true; + } else { + SANDBOX_INFO( + "Cannot disable sandbox and use unsafe traps unless " + "CHROME_SANDBOX_DEBUGGING is turned on first"); + } + } + // Returns the, possibly updated, value of has_unsafe_traps_. + return has_unsafe_traps_; +} + +Trap* Trap::global_trap_; + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/seccomp-bpf/trap.h b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/trap.h new file mode 100644 index 0000000000..a73d2064b4 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/seccomp-bpf/trap.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ +#define SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ + +#include <stddef.h> +#include <stdint.h> + +#include <map> + +#include "base/macros.h" +#include "sandbox/linux/bpf_dsl/trap_registry.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// The Trap class allows a BPF filter program to branch out to user space by +// raising a SIGSYS signal. +// N.B.: This class does not perform any synchronization operations. If +// modifications are made to any of the traps, it is the caller's +// responsibility to ensure that this happens in a thread-safe fashion. +// Preferably, that means that no other threads should be running at that +// time. For the purposes of our sandbox, this assertion should always be +// true. Threads are incompatible with the seccomp sandbox anyway. +class SANDBOX_EXPORT Trap : public bpf_dsl::TrapRegistry { + public: + uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override; + + bool EnableUnsafeTraps() override; + + // Registry returns the trap registry used by Trap's SIGSYS handler, + // creating it if necessary. + static bpf_dsl::TrapRegistry* Registry(); + + // SandboxDebuggingAllowedByUser returns whether the + // "CHROME_SANDBOX_DEBUGGING" environment variable is set. + static bool SandboxDebuggingAllowedByUser(); + + private: + struct TrapKey { + TrapKey() : fnc(NULL), aux(NULL), safe(false) {} + TrapKey(TrapFnc f, const void* a, bool s) : fnc(f), aux(a), safe(s) {} + TrapFnc fnc; + const void* aux; + bool safe; + bool operator<(const TrapKey&) const; + }; + typedef std::map<TrapKey, uint16_t> TrapIds; + + // Our constructor is private. A shared global instance is created + // automatically as needed. + Trap(); + + // The destructor is unimplemented as destroying this object would + // break subsequent system calls that trigger a SIGSYS. + ~Trap() = delete; + + static void SigSysAction(int nr, LinuxSigInfo* info, void* void_context); + + // Make sure that SigSys is not inlined in order to get slightly better crash + // dumps. + void SigSys(int nr, LinuxSigInfo* info, ucontext_t* ctx) + __attribute__((noinline)); + // We have a global singleton that handles all of our SIGSYS traps. This + // variable must never be deallocated after it has been set up initially, as + // there is no way to reset in-kernel BPF filters that generate SIGSYS + // events. + static Trap* global_trap_; + + TrapIds trap_ids_; // Maps from TrapKeys to numeric ids + TrapKey* trap_array_; // Array of TrapKeys indexed by ids + size_t trap_array_size_; // Currently used size of array + size_t trap_array_capacity_; // Currently allocated capacity of array + bool has_unsafe_traps_; // Whether unsafe traps have been enabled + + // Copying and assigning is unimplemented. It doesn't make sense for a + // singleton. + DISALLOW_COPY_AND_ASSIGN(Trap); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SECCOMP_BPF_TRAP_H__ diff --git a/security/sandbox/chromium/sandbox/linux/services/syscall_wrappers.cc b/security/sandbox/chromium/sandbox/linux/services/syscall_wrappers.cc new file mode 100644 index 0000000000..fcfd2aa129 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/services/syscall_wrappers.cc @@ -0,0 +1,264 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/syscall_wrappers.h" + +#include <pthread.h> +#include <sched.h> +#include <setjmp.h> +#include <sys/resource.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <cstring> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "sandbox/linux/system_headers/capability.h" +#include "sandbox/linux/system_headers/linux_signal.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +namespace sandbox { + +pid_t sys_getpid(void) { + return syscall(__NR_getpid); +} + +pid_t sys_gettid(void) { + return syscall(__NR_gettid); +} + +ssize_t sys_write(int fd, const char* buffer, size_t buffer_size) { + return syscall(__NR_write, fd, buffer, buffer_size); +} + +long sys_clone(unsigned long flags, + std::nullptr_t child_stack, + pid_t* ptid, + pid_t* ctid, + std::nullptr_t tls) { + const bool clone_tls_used = flags & CLONE_SETTLS; + const bool invalid_ctid = + (flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) && !ctid; + const bool invalid_ptid = (flags & CLONE_PARENT_SETTID) && !ptid; + + // We do not support CLONE_VM. + const bool clone_vm_used = flags & CLONE_VM; + if (clone_tls_used || invalid_ctid || invalid_ptid || clone_vm_used) { + RAW_LOG(FATAL, "Invalid usage of sys_clone"); + } + + if (ptid) MSAN_UNPOISON(ptid, sizeof(*ptid)); + if (ctid) MSAN_UNPOISON(ctid, sizeof(*ctid)); + // See kernel/fork.c in Linux. There is different ordering of sys_clone + // parameters depending on CONFIG_CLONE_BACKWARDS* configuration options. +#if defined(ARCH_CPU_X86_64) + return syscall(__NR_clone, flags, child_stack, ptid, ctid, tls); +#elif defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS_FAMILY) + // CONFIG_CLONE_BACKWARDS defined. + return syscall(__NR_clone, flags, child_stack, ptid, tls, ctid); +#endif +} + +long sys_clone(unsigned long flags) { + return sys_clone(flags, nullptr, nullptr, nullptr, nullptr); +} + +void sys_exit_group(int status) { + syscall(__NR_exit_group, status); +} + +int sys_seccomp(unsigned int operation, + unsigned int flags, + const struct sock_fprog* args) { + return syscall(__NR_seccomp, operation, flags, args); +} + +int sys_prlimit64(pid_t pid, + int resource, + const struct rlimit64* new_limit, + struct rlimit64* old_limit) { + int res = syscall(__NR_prlimit64, pid, resource, new_limit, old_limit); + if (res == 0 && old_limit) MSAN_UNPOISON(old_limit, sizeof(*old_limit)); + return res; +} + +int sys_capget(cap_hdr* hdrp, cap_data* datap) { + int res = syscall(__NR_capget, hdrp, datap); + if (res == 0) { + if (hdrp) MSAN_UNPOISON(hdrp, sizeof(*hdrp)); + if (datap) MSAN_UNPOISON(datap, sizeof(*datap)); + } + return res; +} + +int sys_capset(cap_hdr* hdrp, const cap_data* datap) { + return syscall(__NR_capset, hdrp, datap); +} + +int sys_getresuid(uid_t* ruid, uid_t* euid, uid_t* suid) { + int res; +#if defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARMEL) + // On 32-bit x86 or 32-bit arm, getresuid supports 16bit values only. + // Use getresuid32 instead. + res = syscall(__NR_getresuid32, ruid, euid, suid); +#else + res = syscall(__NR_getresuid, ruid, euid, suid); +#endif + if (res == 0) { + if (ruid) MSAN_UNPOISON(ruid, sizeof(*ruid)); + if (euid) MSAN_UNPOISON(euid, sizeof(*euid)); + if (suid) MSAN_UNPOISON(suid, sizeof(*suid)); + } + return res; +} + +int sys_getresgid(gid_t* rgid, gid_t* egid, gid_t* sgid) { + int res; +#if defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARMEL) + // On 32-bit x86 or 32-bit arm, getresgid supports 16bit values only. + // Use getresgid32 instead. + res = syscall(__NR_getresgid32, rgid, egid, sgid); +#else + res = syscall(__NR_getresgid, rgid, egid, sgid); +#endif + if (res == 0) { + if (rgid) MSAN_UNPOISON(rgid, sizeof(*rgid)); + if (egid) MSAN_UNPOISON(egid, sizeof(*egid)); + if (sgid) MSAN_UNPOISON(sgid, sizeof(*sgid)); + } + return res; +} + +int sys_chroot(const char* path) { + return syscall(__NR_chroot, path); +} + +int sys_unshare(int flags) { + return syscall(__NR_unshare, flags); +} + +int sys_sigprocmask(int how, const sigset_t* set, std::nullptr_t oldset) { + // In some toolchain (in particular Android and PNaCl toolchain), + // sigset_t is 32 bits, but the Linux ABI uses more. + LinuxSigSet linux_value; + std::memset(&linux_value, 0, sizeof(LinuxSigSet)); + std::memcpy(&linux_value, set, std::min(sizeof(sigset_t), + sizeof(LinuxSigSet))); + + return syscall(__NR_rt_sigprocmask, how, &linux_value, nullptr, + sizeof(linux_value)); +} + +// When this is built with PNaCl toolchain, we should always use sys_sigaction +// below, because sigaction() provided by the toolchain is incompatible with +// Linux's ABI. +#if !defined(OS_NACL_NONSFI) +int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact) { + return sigaction(signum, act, oldact); +} +#else +#if defined(ARCH_CPU_X86_FAMILY) + +// On x86_64, sa_restorer is required. We specify it on x86 as well in order to +// support kernels with VDSO disabled. +#if !defined(SA_RESTORER) +#define SA_RESTORER 0x04000000 +#endif + +// XSTR(__NR_foo) expands to a string literal containing the value value of +// __NR_foo. +#define STR(x) #x +#define XSTR(x) STR(x) + +// rt_sigreturn is a special system call that interacts with the user land +// stack. Thus, here prologue must not be created, which implies syscall() +// does not work properly, too. Note that rt_sigreturn does not return. +// TODO(rickyz): These assembly functions may still break stack unwinding on +// nonsfi NaCl builds. +#if defined(ARCH_CPU_X86_64) + +extern "C" { + void sys_rt_sigreturn(); +} + +asm( + ".text\n" + "sys_rt_sigreturn:\n" + "mov $" XSTR(__NR_rt_sigreturn) ", %eax\n" + "syscall\n"); + +#elif defined(ARCH_CPU_X86) +extern "C" { + void sys_sigreturn(); + void sys_rt_sigreturn(); +} + +asm( + ".text\n" + "sys_rt_sigreturn:\n" + "mov $" XSTR(__NR_rt_sigreturn) ", %eax\n" + "int $0x80\n" + + "sys_sigreturn:\n" + "pop %eax\n" + "mov $" XSTR(__NR_sigreturn) ", %eax\n" + "int $0x80\n"); +#else +#error "Unsupported architecture." +#endif + +#undef STR +#undef XSTR + +#endif + +int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact) { + LinuxSigAction linux_act = {}; + if (act) { + linux_act.kernel_handler = act->sa_handler; + std::memcpy(&linux_act.sa_mask, &act->sa_mask, + std::min(sizeof(linux_act.sa_mask), sizeof(act->sa_mask))); + linux_act.sa_flags = act->sa_flags; + +#if defined(ARCH_CPU_X86_FAMILY) + if (!(linux_act.sa_flags & SA_RESTORER)) { + linux_act.sa_flags |= SA_RESTORER; +#if defined(ARCH_CPU_X86_64) + linux_act.sa_restorer = sys_rt_sigreturn; +#elif defined(ARCH_CPU_X86) + linux_act.sa_restorer = + linux_act.sa_flags & SA_SIGINFO ? sys_rt_sigreturn : sys_sigreturn; +#else +#error "Unsupported architecture." +#endif + } +#endif + } + + LinuxSigAction linux_oldact = {}; + int result = syscall(__NR_rt_sigaction, signum, act ? &linux_act : nullptr, + oldact ? &linux_oldact : nullptr, + sizeof(LinuxSigSet)); + + if (result == 0 && oldact) { + oldact->sa_handler = linux_oldact.kernel_handler; + sigemptyset(&oldact->sa_mask); + std::memcpy(&oldact->sa_mask, &linux_oldact.sa_mask, + std::min(sizeof(linux_act.sa_mask), sizeof(act->sa_mask))); + oldact->sa_flags = linux_oldact.sa_flags; + } + return result; +} + +#endif // defined(MEMORY_SANITIZER) + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/linux/services/syscall_wrappers.h b/security/sandbox/chromium/sandbox/linux/services/syscall_wrappers.h new file mode 100644 index 0000000000..1975bfbd88 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/services/syscall_wrappers.h @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_SYSCALL_WRAPPERS_H_ +#define SANDBOX_LINUX_SERVICES_SYSCALL_WRAPPERS_H_ + +#include <signal.h> +#include <stdint.h> +#include <sys/types.h> + +#include <cstddef> + +#include "sandbox/sandbox_export.h" + +struct sock_fprog; +struct rlimit64; +struct cap_hdr; +struct cap_data; + +namespace sandbox { + +// Provide direct system call wrappers for a few common system calls. +// These are guaranteed to perform a system call and do not rely on things such +// as caching the current pid (c.f. getpid()) unless otherwise specified. + +SANDBOX_EXPORT pid_t sys_getpid(void); + +SANDBOX_EXPORT pid_t sys_gettid(void); + +SANDBOX_EXPORT ssize_t sys_write(int fd, + const char* buffer, + size_t buffer_size); + +SANDBOX_EXPORT long sys_clone(unsigned long flags); + +// |regs| is not supported and must be passed as nullptr. |child_stack| must be +// nullptr, since otherwise this function cannot safely return. As a +// consequence, this function does not support CLONE_VM. +SANDBOX_EXPORT long sys_clone(unsigned long flags, + std::nullptr_t child_stack, + pid_t* ptid, + pid_t* ctid, + std::nullptr_t regs); + +SANDBOX_EXPORT void sys_exit_group(int status); + +// The official system call takes |args| as void* (in order to be extensible), +// but add more typing for the cases that are currently used. +SANDBOX_EXPORT int sys_seccomp(unsigned int operation, + unsigned int flags, + const struct sock_fprog* args); + +// Some libcs do not expose a prlimit64 wrapper. +SANDBOX_EXPORT int sys_prlimit64(pid_t pid, + int resource, + const struct rlimit64* new_limit, + struct rlimit64* old_limit); + +// Some libcs do not expose capget/capset wrappers. We want to use these +// directly in order to avoid pulling in libcap2. +SANDBOX_EXPORT int sys_capget(struct cap_hdr* hdrp, struct cap_data* datap); +SANDBOX_EXPORT int sys_capset(struct cap_hdr* hdrp, + const struct cap_data* datap); + +// Some libcs do not expose getresuid/getresgid wrappers. +SANDBOX_EXPORT int sys_getresuid(uid_t* ruid, uid_t* euid, uid_t* suid); +SANDBOX_EXPORT int sys_getresgid(gid_t* rgid, gid_t* egid, gid_t* sgid); + +// Some libcs do not expose a chroot wrapper. +SANDBOX_EXPORT int sys_chroot(const char* path); + +// Some libcs do not expose a unshare wrapper. +SANDBOX_EXPORT int sys_unshare(int flags); + +// Some libcs do not expose a sigprocmask. Note that oldset must be a nullptr, +// because of some ABI gap between toolchain's and Linux's. +SANDBOX_EXPORT int sys_sigprocmask(int how, + const sigset_t* set, + std::nullptr_t oldset); + +// Some libcs do not expose a sigaction(). +SANDBOX_EXPORT int sys_sigaction(int signum, + const struct sigaction* act, + struct sigaction* oldact); + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_SYSCALL_WRAPPERS_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/arm64_linux_syscalls.h b/security/sandbox/chromium/sandbox/linux/system_headers/arm64_linux_syscalls.h new file mode 100644 index 0000000000..2224d32438 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/arm64_linux_syscalls.h @@ -0,0 +1,1197 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* Constructed by running: + * curl -vsSL https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/include/uapi/asm-generic/unistd.h?h=v5.8 + * | gcc -D__BITS_PER_LONG=64 -D__ARCH_WANT_STAT64 -D__ARCH_WANT_SET_GET_RLIMIT -D__ARCH_WANT_SYS_CLONE3 -D__ARCH_WANT_RENAMEAT -E -dD - + * | grep __NR | grep -vE '__NR_arch_specific_syscall|__NR_syscalls' | sort -n -k 3 | sed -e 's/__NR3264/__NR/g' + * | awk '{ if ($2 != $3) { print "#if !defined(" $2 ")\n#define " $2 " " $3 "\n#endif\n"; } } + * */ + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_SYSCALLS_H_ + +#include <asm-generic/unistd.h> + +#if !defined(__NR_io_setup) +#define __NR_io_setup 0 +#endif + +#if !defined(__NR_newfstatat) +#define __NR_newfstatat __NR_fstatat +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy 1 +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit 2 +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel 3 +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents 4 +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr 5 +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr 6 +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr 7 +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr 8 +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr 9 +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr 10 +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr 11 +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr 12 +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr 13 +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr 14 +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr 15 +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr 16 +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd 17 +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie 18 +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 19 +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 20 +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl 21 +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait 22 +#endif + +#if !defined(__NR_dup) +#define __NR_dup 23 +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 24 +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl 25 +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 26 +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch 27 +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch 28 +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl 29 +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set 30 +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get 31 +#endif + +#if !defined(__NR_flock) +#define __NR_flock 32 +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat 33 +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat 34 +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat 35 +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat 36 +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat 37 +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat 38 +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 39 +#endif + +#if !defined(__NR_mount) +#define __NR_mount 40 +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root 41 +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl 42 +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs 43 +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs 44 +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate 45 +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate 46 +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate 47 +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat 48 +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir 49 +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir 50 +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot 51 +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod 52 +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat 53 +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat 54 +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown 55 +#endif + +#if !defined(__NR_openat) +#define __NR_openat 56 +#endif + +#if !defined(__NR_close) +#define __NR_close 57 +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup 58 +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 59 +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl 60 +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 61 +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek 62 +#endif + +#if !defined(__NR_read) +#define __NR_read 63 +#endif + +#if !defined(__NR_write) +#define __NR_write 64 +#endif + +#if !defined(__NR_readv) +#define __NR_readv 65 +#endif + +#if !defined(__NR_writev) +#define __NR_writev 66 +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 67 +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 68 +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv 69 +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev 70 +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile 71 +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 72 +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll 73 +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 74 +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice 75 +#endif + +#if !defined(__NR_splice) +#define __NR_splice 76 +#endif + +#if !defined(__NR_tee) +#define __NR_tee 77 +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat 78 +#endif + +#if !defined(__NR_fstatat) +#define __NR_fstatat 79 +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat 80 +#endif + +#if !defined(__NR_sync) +#define __NR_sync 81 +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync 82 +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync 83 +#endif + +#if !defined(__NR_sync_file_range) +#define __NR_sync_file_range 84 +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create 85 +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime 86 +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime 87 +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat 88 +#endif + +#if !defined(__NR_acct) +#define __NR_acct 89 +#endif + +#if !defined(__NR_capget) +#define __NR_capget 90 +#endif + +#if !defined(__NR_capset) +#define __NR_capset 91 +#endif + +#if !defined(__NR_personality) +#define __NR_personality 92 +#endif + +#if !defined(__NR_exit) +#define __NR_exit 93 +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group 94 +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid 95 +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address 96 +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare 97 +#endif + +#if !defined(__NR_futex) +#define __NR_futex 98 +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list 99 +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list 100 +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep 101 +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer 102 +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer 103 +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load 104 +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module 105 +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module 106 +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create 107 +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime 108 +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun 109 +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime 110 +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete 111 +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime 112 +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime 113 +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres 114 +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep 115 +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog 116 +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace 117 +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam 118 +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler 119 +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler 120 +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam 121 +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity 122 +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity 123 +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield 124 +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max 125 +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min 126 +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval 127 +#endif + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall 128 +#endif + +#if !defined(__NR_kill) +#define __NR_kill 129 +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill 130 +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill 131 +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack 132 +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend 133 +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction 134 +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask 135 +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending 136 +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait 137 +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo 138 +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn 139 +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority 140 +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority 141 +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot 142 +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid 143 +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid 144 +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid 145 +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid 146 +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid 147 +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid 148 +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid 149 +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid 150 +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid 151 +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid 152 +#endif + +#if !defined(__NR_times) +#define __NR_times 153 +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid 154 +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid 155 +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid 156 +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid 157 +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups 158 +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups 159 +#endif + +#if !defined(__NR_uname) +#define __NR_uname 160 +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname 161 +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname 162 +#endif + +#if !defined(__NR_getrlimit) +#define __NR_getrlimit 163 +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit 164 +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage 165 +#endif + +#if !defined(__NR_umask) +#define __NR_umask 166 +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl 167 +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu 168 +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday 169 +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday 170 +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex 171 +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid 172 +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid 173 +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid 174 +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid 175 +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid 176 +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid 177 +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid 178 +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo 179 +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open 180 +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink 181 +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend 182 +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive 183 +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify 184 +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr 185 +#endif + +#if !defined(__NR_msgget) +#define __NR_msgget 186 +#endif + +#if !defined(__NR_msgctl) +#define __NR_msgctl 187 +#endif + +#if !defined(__NR_msgrcv) +#define __NR_msgrcv 188 +#endif + +#if !defined(__NR_msgsnd) +#define __NR_msgsnd 189 +#endif + +#if !defined(__NR_semget) +#define __NR_semget 190 +#endif + +#if !defined(__NR_semctl) +#define __NR_semctl 191 +#endif + +#if !defined(__NR_semtimedop) +#define __NR_semtimedop 192 +#endif + +#if !defined(__NR_semop) +#define __NR_semop 193 +#endif + +#if !defined(__NR_shmget) +#define __NR_shmget 194 +#endif + +#if !defined(__NR_shmctl) +#define __NR_shmctl 195 +#endif + +#if !defined(__NR_shmat) +#define __NR_shmat 196 +#endif + +#if !defined(__NR_shmdt) +#define __NR_shmdt 197 +#endif + +#if !defined(__NR_socket) +#define __NR_socket 198 +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair 199 +#endif + +#if !defined(__NR_bind) +#define __NR_bind 200 +#endif + +#if !defined(__NR_listen) +#define __NR_listen 201 +#endif + +#if !defined(__NR_accept) +#define __NR_accept 202 +#endif + +#if !defined(__NR_connect) +#define __NR_connect 203 +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname 204 +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername 205 +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto 206 +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom 207 +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt 208 +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt 209 +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown 210 +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg 211 +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg 212 +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead 213 +#endif + +#if !defined(__NR_brk) +#define __NR_brk 214 +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap 215 +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap 216 +#endif + +#if !defined(__NR_add_key) +#define __NR_add_key 217 +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key 218 +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl 219 +#endif + +#if !defined(__NR_clone) +#define __NR_clone 220 +#endif + +#if !defined(__NR_execve) +#define __NR_execve 221 +#endif + +#if !defined(__NR_mmap) +#define __NR_mmap 222 +#endif + +#if !defined(__NR_fadvise64) +#define __NR_fadvise64 223 +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon 224 +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff 225 +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect 226 +#endif + +#if !defined(__NR_msync) +#define __NR_msync 227 +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock 228 +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock 229 +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall 230 +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall 231 +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore 232 +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise 233 +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages 234 +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind 235 +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy 236 +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy 237 +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages 238 +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages 239 +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo 240 +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open 241 +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 242 +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg 243 +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 260 +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 261 +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init 262 +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark 263 +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at 264 +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at 265 +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime 266 +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs 267 +#endif + +#if !defined(__NR_setns) +#define __NR_setns 268 +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg 269 +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv 270 +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev 271 +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp 272 +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module 273 +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr 274 +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr 275 +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 276 +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp 277 +#endif + +#if !defined(__NR_getrandom) +#define __NR_getrandom 278 +#endif + +#if !defined(__NR_memfd_create) +#define __NR_memfd_create 279 +#endif + +#if !defined(__NR_bpf) +#define __NR_bpf 280 +#endif + +#if !defined(__NR_execveat) +#define __NR_execveat 281 +#endif + +#if !defined(__NR_userfaultfd) +#define __NR_userfaultfd 282 +#endif + +#if !defined(__NR_membarrier) +#define __NR_membarrier 283 +#endif + +#if !defined(__NR_mlock2) +#define __NR_mlock2 284 +#endif + +#if !defined(__NR_copy_file_range) +#define __NR_copy_file_range 285 +#endif + +#if !defined(__NR_preadv2) +#define __NR_preadv2 286 +#endif + +#if !defined(__NR_pwritev2) +#define __NR_pwritev2 287 +#endif + +#if !defined(__NR_pkey_mprotect) +#define __NR_pkey_mprotect 288 +#endif + +#if !defined(__NR_pkey_alloc) +#define __NR_pkey_alloc 289 +#endif + +#if !defined(__NR_pkey_free) +#define __NR_pkey_free 290 +#endif + +#if !defined(__NR_statx) +#define __NR_statx 291 +#endif + +#if !defined(__NR_io_pgetevents) +#define __NR_io_pgetevents 292 +#endif + +#if !defined(__NR_rseq) +#define __NR_rseq 293 +#endif + +#if !defined(__NR_kexec_file_load) +#define __NR_kexec_file_load 294 +#endif + +#if !defined(__NR_pidfd_send_signal) +#define __NR_pidfd_send_signal 424 +#endif + +#if !defined(__NR_io_uring_setup) +#define __NR_io_uring_setup 425 +#endif + +#if !defined(__NR_io_uring_enter) +#define __NR_io_uring_enter 426 +#endif + +#if !defined(__NR_io_uring_register) +#define __NR_io_uring_register 427 +#endif + +#if !defined(__NR_open_tree) +#define __NR_open_tree 428 +#endif + +#if !defined(__NR_move_mount) +#define __NR_move_mount 429 +#endif + +#if !defined(__NR_fsopen) +#define __NR_fsopen 430 +#endif + +#if !defined(__NR_fsconfig) +#define __NR_fsconfig 431 +#endif + +#if !defined(__NR_fsmount) +#define __NR_fsmount 432 +#endif + +#if !defined(__NR_fspick) +#define __NR_fspick 433 +#endif + +#if !defined(__NR_pidfd_open) +#define __NR_pidfd_open 434 +#endif + +#if !defined(__NR_clone3) +#define __NR_clone3 435 +#endif + +#if !defined(__NR_openat2) +#define __NR_openat2 437 +#endif + +#if !defined(__NR_pidfd_getfd) +#define __NR_pidfd_getfd 438 +#endif + +#if !defined(__NR_faccessat2) +#define __NR_faccessat2 439 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ARM64_LINUX_SYSCALLS_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/arm_linux_syscalls.h b/security/sandbox/chromium/sandbox/linux/system_headers/arm_linux_syscalls.h new file mode 100644 index 0000000000..5b7f4e511a --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/arm_linux_syscalls.h @@ -0,0 +1,1623 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* Constructed by running: + * curl -vsSL https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/arch/arm/tools/syscall.tbl?h=v5.8 + * | grep -vE '^#|^$' + * | awk '{ if ($2 != "oabi") { print "#if !defined(__NR_" $3 ")\n#define __NR_" $3 " (__NR_SYSCALL_BASE+" $1 ")\n#endif\n"; } }' + * */ + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_SYSCALLS_H_ + +#if !defined(__arm__) || !defined(__ARM_EABI__) +#error "Including header on wrong architecture" +#endif + +#if !defined(__NR_SYSCALL_BASE) +// On ARM EABI arch, __NR_SYSCALL_BASE is 0. +#define __NR_SYSCALL_BASE 0 +#endif + +// This syscall list has holes, because ARM EABI makes some syscalls obsolete. + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall (__NR_SYSCALL_BASE+0) +#endif + +#if !defined(__NR_exit) +#define __NR_exit (__NR_SYSCALL_BASE+1) +#endif + +#if !defined(__NR_fork) +#define __NR_fork (__NR_SYSCALL_BASE+2) +#endif + +#if !defined(__NR_read) +#define __NR_read (__NR_SYSCALL_BASE+3) +#endif + +#if !defined(__NR_write) +#define __NR_write (__NR_SYSCALL_BASE+4) +#endif + +#if !defined(__NR_open) +#define __NR_open (__NR_SYSCALL_BASE+5) +#endif + +#if !defined(__NR_close) +#define __NR_close (__NR_SYSCALL_BASE+6) +#endif + +#if !defined(__NR_creat) +#define __NR_creat (__NR_SYSCALL_BASE+8) +#endif + +#if !defined(__NR_link) +#define __NR_link (__NR_SYSCALL_BASE+9) +#endif + +#if !defined(__NR_unlink) +#define __NR_unlink (__NR_SYSCALL_BASE+10) +#endif + +#if !defined(__NR_execve) +#define __NR_execve (__NR_SYSCALL_BASE+11) +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir (__NR_SYSCALL_BASE+12) +#endif + +#if !defined(__NR_mknod) +#define __NR_mknod (__NR_SYSCALL_BASE+14) +#endif + +#if !defined(__NR_chmod) +#define __NR_chmod (__NR_SYSCALL_BASE+15) +#endif + +#if !defined(__NR_lchown) +#define __NR_lchown (__NR_SYSCALL_BASE+16) +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek (__NR_SYSCALL_BASE+19) +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid (__NR_SYSCALL_BASE+20) +#endif + +#if !defined(__NR_mount) +#define __NR_mount (__NR_SYSCALL_BASE+21) +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid (__NR_SYSCALL_BASE+23) +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid (__NR_SYSCALL_BASE+24) +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace (__NR_SYSCALL_BASE+26) +#endif + +#if !defined(__NR_pause) +#define __NR_pause (__NR_SYSCALL_BASE+29) +#endif + +#if !defined(__NR_access) +#define __NR_access (__NR_SYSCALL_BASE+33) +#endif + +#if !defined(__NR_nice) +#define __NR_nice (__NR_SYSCALL_BASE+34) +#endif + +#if !defined(__NR_sync) +#define __NR_sync (__NR_SYSCALL_BASE+36) +#endif + +#if !defined(__NR_kill) +#define __NR_kill (__NR_SYSCALL_BASE+37) +#endif + +#if !defined(__NR_rename) +#define __NR_rename (__NR_SYSCALL_BASE+38) +#endif + +#if !defined(__NR_mkdir) +#define __NR_mkdir (__NR_SYSCALL_BASE+39) +#endif + +#if !defined(__NR_rmdir) +#define __NR_rmdir (__NR_SYSCALL_BASE+40) +#endif + +#if !defined(__NR_dup) +#define __NR_dup (__NR_SYSCALL_BASE+41) +#endif + +#if !defined(__NR_pipe) +#define __NR_pipe (__NR_SYSCALL_BASE+42) +#endif + +#if !defined(__NR_times) +#define __NR_times (__NR_SYSCALL_BASE+43) +#endif + +#if !defined(__NR_brk) +#define __NR_brk (__NR_SYSCALL_BASE+45) +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid (__NR_SYSCALL_BASE+46) +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid (__NR_SYSCALL_BASE+47) +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid (__NR_SYSCALL_BASE+49) +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid (__NR_SYSCALL_BASE+50) +#endif + +#if !defined(__NR_acct) +#define __NR_acct (__NR_SYSCALL_BASE+51) +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 (__NR_SYSCALL_BASE+52) +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl (__NR_SYSCALL_BASE+54) +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl (__NR_SYSCALL_BASE+55) +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid (__NR_SYSCALL_BASE+57) +#endif + +#if !defined(__NR_umask) +#define __NR_umask (__NR_SYSCALL_BASE+60) +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot (__NR_SYSCALL_BASE+61) +#endif + +#if !defined(__NR_ustat) +#define __NR_ustat (__NR_SYSCALL_BASE+62) +#endif + +#if !defined(__NR_dup2) +#define __NR_dup2 (__NR_SYSCALL_BASE+63) +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid (__NR_SYSCALL_BASE+64) +#endif + +#if !defined(__NR_getpgrp) +#define __NR_getpgrp (__NR_SYSCALL_BASE+65) +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid (__NR_SYSCALL_BASE+66) +#endif + +#if !defined(__NR_sigaction) +#define __NR_sigaction (__NR_SYSCALL_BASE+67) +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid (__NR_SYSCALL_BASE+70) +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid (__NR_SYSCALL_BASE+71) +#endif + +#if !defined(__NR_sigsuspend) +#define __NR_sigsuspend (__NR_SYSCALL_BASE+72) +#endif + +#if !defined(__NR_sigpending) +#define __NR_sigpending (__NR_SYSCALL_BASE+73) +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname (__NR_SYSCALL_BASE+74) +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit (__NR_SYSCALL_BASE+75) +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage (__NR_SYSCALL_BASE+77) +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday (__NR_SYSCALL_BASE+78) +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday (__NR_SYSCALL_BASE+79) +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups (__NR_SYSCALL_BASE+80) +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups (__NR_SYSCALL_BASE+81) +#endif + +#if !defined(__NR_symlink) +#define __NR_symlink (__NR_SYSCALL_BASE+83) +#endif + +#if !defined(__NR_readlink) +#define __NR_readlink (__NR_SYSCALL_BASE+85) +#endif + +#if !defined(__NR_uselib) +#define __NR_uselib (__NR_SYSCALL_BASE+86) +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon (__NR_SYSCALL_BASE+87) +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot (__NR_SYSCALL_BASE+88) +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap (__NR_SYSCALL_BASE+91) +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate (__NR_SYSCALL_BASE+92) +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate (__NR_SYSCALL_BASE+93) +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod (__NR_SYSCALL_BASE+94) +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown (__NR_SYSCALL_BASE+95) +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority (__NR_SYSCALL_BASE+96) +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority (__NR_SYSCALL_BASE+97) +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs (__NR_SYSCALL_BASE+99) +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs (__NR_SYSCALL_BASE+100) +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog (__NR_SYSCALL_BASE+103) +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer (__NR_SYSCALL_BASE+104) +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer (__NR_SYSCALL_BASE+105) +#endif + +#if !defined(__NR_stat) +#define __NR_stat (__NR_SYSCALL_BASE+106) +#endif + +#if !defined(__NR_lstat) +#define __NR_lstat (__NR_SYSCALL_BASE+107) +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat (__NR_SYSCALL_BASE+108) +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup (__NR_SYSCALL_BASE+111) +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 (__NR_SYSCALL_BASE+114) +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff (__NR_SYSCALL_BASE+115) +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo (__NR_SYSCALL_BASE+116) +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync (__NR_SYSCALL_BASE+118) +#endif + +#if !defined(__NR_sigreturn) +#define __NR_sigreturn (__NR_SYSCALL_BASE+119) +#endif + +#if !defined(__NR_clone) +#define __NR_clone (__NR_SYSCALL_BASE+120) +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname (__NR_SYSCALL_BASE+121) +#endif + +#if !defined(__NR_uname) +#define __NR_uname (__NR_SYSCALL_BASE+122) +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex (__NR_SYSCALL_BASE+124) +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect (__NR_SYSCALL_BASE+125) +#endif + +#if !defined(__NR_sigprocmask) +#define __NR_sigprocmask (__NR_SYSCALL_BASE+126) +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module (__NR_SYSCALL_BASE+128) +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module (__NR_SYSCALL_BASE+129) +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl (__NR_SYSCALL_BASE+131) +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid (__NR_SYSCALL_BASE+132) +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir (__NR_SYSCALL_BASE+133) +#endif + +#if !defined(__NR_bdflush) +#define __NR_bdflush (__NR_SYSCALL_BASE+134) +#endif + +#if !defined(__NR_sysfs) +#define __NR_sysfs (__NR_SYSCALL_BASE+135) +#endif + +#if !defined(__NR_personality) +#define __NR_personality (__NR_SYSCALL_BASE+136) +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid (__NR_SYSCALL_BASE+138) +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid (__NR_SYSCALL_BASE+139) +#endif + +#if !defined(__NR__llseek) +#define __NR__llseek (__NR_SYSCALL_BASE+140) +#endif + +#if !defined(__NR_getdents) +#define __NR_getdents (__NR_SYSCALL_BASE+141) +#endif + +#if !defined(__NR__newselect) +#define __NR__newselect (__NR_SYSCALL_BASE+142) +#endif + +#if !defined(__NR_flock) +#define __NR_flock (__NR_SYSCALL_BASE+143) +#endif + +#if !defined(__NR_msync) +#define __NR_msync (__NR_SYSCALL_BASE+144) +#endif + +#if !defined(__NR_readv) +#define __NR_readv (__NR_SYSCALL_BASE+145) +#endif + +#if !defined(__NR_writev) +#define __NR_writev (__NR_SYSCALL_BASE+146) +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid (__NR_SYSCALL_BASE+147) +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync (__NR_SYSCALL_BASE+148) +#endif + +#if !defined(__NR__sysctl) +#define __NR__sysctl (__NR_SYSCALL_BASE+149) +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock (__NR_SYSCALL_BASE+150) +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock (__NR_SYSCALL_BASE+151) +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall (__NR_SYSCALL_BASE+152) +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall (__NR_SYSCALL_BASE+153) +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam (__NR_SYSCALL_BASE+154) +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam (__NR_SYSCALL_BASE+155) +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler (__NR_SYSCALL_BASE+156) +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler (__NR_SYSCALL_BASE+157) +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield (__NR_SYSCALL_BASE+158) +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max (__NR_SYSCALL_BASE+159) +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min (__NR_SYSCALL_BASE+160) +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval (__NR_SYSCALL_BASE+161) +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep (__NR_SYSCALL_BASE+162) +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap (__NR_SYSCALL_BASE+163) +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid (__NR_SYSCALL_BASE+164) +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid (__NR_SYSCALL_BASE+165) +#endif + +#if !defined(__NR_poll) +#define __NR_poll (__NR_SYSCALL_BASE+168) +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl (__NR_SYSCALL_BASE+169) +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid (__NR_SYSCALL_BASE+170) +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid (__NR_SYSCALL_BASE+171) +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl (__NR_SYSCALL_BASE+172) +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn (__NR_SYSCALL_BASE+173) +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction (__NR_SYSCALL_BASE+174) +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask (__NR_SYSCALL_BASE+175) +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending (__NR_SYSCALL_BASE+176) +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait (__NR_SYSCALL_BASE+177) +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo (__NR_SYSCALL_BASE+178) +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend (__NR_SYSCALL_BASE+179) +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 (__NR_SYSCALL_BASE+180) +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 (__NR_SYSCALL_BASE+181) +#endif + +#if !defined(__NR_chown) +#define __NR_chown (__NR_SYSCALL_BASE+182) +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd (__NR_SYSCALL_BASE+183) +#endif + +#if !defined(__NR_capget) +#define __NR_capget (__NR_SYSCALL_BASE+184) +#endif + +#if !defined(__NR_capset) +#define __NR_capset (__NR_SYSCALL_BASE+185) +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack (__NR_SYSCALL_BASE+186) +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile (__NR_SYSCALL_BASE+187) +#endif + +#if !defined(__NR_vfork) +#define __NR_vfork (__NR_SYSCALL_BASE+190) +#endif + +#if !defined(__NR_ugetrlimit) +#define __NR_ugetrlimit (__NR_SYSCALL_BASE+191) +#endif + +#if !defined(__NR_mmap2) +#define __NR_mmap2 (__NR_SYSCALL_BASE+192) +#endif + +#if !defined(__NR_truncate64) +#define __NR_truncate64 (__NR_SYSCALL_BASE+193) +#endif + +#if !defined(__NR_ftruncate64) +#define __NR_ftruncate64 (__NR_SYSCALL_BASE+194) +#endif + +#if !defined(__NR_stat64) +#define __NR_stat64 (__NR_SYSCALL_BASE+195) +#endif + +#if !defined(__NR_lstat64) +#define __NR_lstat64 (__NR_SYSCALL_BASE+196) +#endif + +#if !defined(__NR_fstat64) +#define __NR_fstat64 (__NR_SYSCALL_BASE+197) +#endif + +#if !defined(__NR_lchown32) +#define __NR_lchown32 (__NR_SYSCALL_BASE+198) +#endif + +#if !defined(__NR_getuid32) +#define __NR_getuid32 (__NR_SYSCALL_BASE+199) +#endif + +#if !defined(__NR_getgid32) +#define __NR_getgid32 (__NR_SYSCALL_BASE+200) +#endif + +#if !defined(__NR_geteuid32) +#define __NR_geteuid32 (__NR_SYSCALL_BASE+201) +#endif + +#if !defined(__NR_getegid32) +#define __NR_getegid32 (__NR_SYSCALL_BASE+202) +#endif + +#if !defined(__NR_setreuid32) +#define __NR_setreuid32 (__NR_SYSCALL_BASE+203) +#endif + +#if !defined(__NR_setregid32) +#define __NR_setregid32 (__NR_SYSCALL_BASE+204) +#endif + +#if !defined(__NR_getgroups32) +#define __NR_getgroups32 (__NR_SYSCALL_BASE+205) +#endif + +#if !defined(__NR_setgroups32) +#define __NR_setgroups32 (__NR_SYSCALL_BASE+206) +#endif + +#if !defined(__NR_fchown32) +#define __NR_fchown32 (__NR_SYSCALL_BASE+207) +#endif + +#if !defined(__NR_setresuid32) +#define __NR_setresuid32 (__NR_SYSCALL_BASE+208) +#endif + +#if !defined(__NR_getresuid32) +#define __NR_getresuid32 (__NR_SYSCALL_BASE+209) +#endif + +#if !defined(__NR_setresgid32) +#define __NR_setresgid32 (__NR_SYSCALL_BASE+210) +#endif + +#if !defined(__NR_getresgid32) +#define __NR_getresgid32 (__NR_SYSCALL_BASE+211) +#endif + +#if !defined(__NR_chown32) +#define __NR_chown32 (__NR_SYSCALL_BASE+212) +#endif + +#if !defined(__NR_setuid32) +#define __NR_setuid32 (__NR_SYSCALL_BASE+213) +#endif + +#if !defined(__NR_setgid32) +#define __NR_setgid32 (__NR_SYSCALL_BASE+214) +#endif + +#if !defined(__NR_setfsuid32) +#define __NR_setfsuid32 (__NR_SYSCALL_BASE+215) +#endif + +#if !defined(__NR_setfsgid32) +#define __NR_setfsgid32 (__NR_SYSCALL_BASE+216) +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 (__NR_SYSCALL_BASE+217) +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root (__NR_SYSCALL_BASE+218) +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore (__NR_SYSCALL_BASE+219) +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise (__NR_SYSCALL_BASE+220) +#endif + +#if !defined(__NR_fcntl64) +#define __NR_fcntl64 (__NR_SYSCALL_BASE+221) +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid (__NR_SYSCALL_BASE+224) +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead (__NR_SYSCALL_BASE+225) +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr (__NR_SYSCALL_BASE+226) +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr (__NR_SYSCALL_BASE+227) +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr (__NR_SYSCALL_BASE+228) +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr (__NR_SYSCALL_BASE+229) +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr (__NR_SYSCALL_BASE+230) +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr (__NR_SYSCALL_BASE+231) +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr (__NR_SYSCALL_BASE+232) +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr (__NR_SYSCALL_BASE+233) +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr (__NR_SYSCALL_BASE+234) +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr (__NR_SYSCALL_BASE+235) +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr (__NR_SYSCALL_BASE+236) +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr (__NR_SYSCALL_BASE+237) +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill (__NR_SYSCALL_BASE+238) +#endif + +#if !defined(__NR_sendfile64) +#define __NR_sendfile64 (__NR_SYSCALL_BASE+239) +#endif + +#if !defined(__NR_futex) +#define __NR_futex (__NR_SYSCALL_BASE+240) +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity (__NR_SYSCALL_BASE+241) +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity (__NR_SYSCALL_BASE+242) +#endif + +#if !defined(__NR_io_setup) +#define __NR_io_setup (__NR_SYSCALL_BASE+243) +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy (__NR_SYSCALL_BASE+244) +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents (__NR_SYSCALL_BASE+245) +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit (__NR_SYSCALL_BASE+246) +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel (__NR_SYSCALL_BASE+247) +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group (__NR_SYSCALL_BASE+248) +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie (__NR_SYSCALL_BASE+249) +#endif + +#if !defined(__NR_epoll_create) +#define __NR_epoll_create (__NR_SYSCALL_BASE+250) +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl (__NR_SYSCALL_BASE+251) +#endif + +#if !defined(__NR_epoll_wait) +#define __NR_epoll_wait (__NR_SYSCALL_BASE+252) +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages (__NR_SYSCALL_BASE+253) +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address (__NR_SYSCALL_BASE+256) +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create (__NR_SYSCALL_BASE+257) +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime (__NR_SYSCALL_BASE+258) +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime (__NR_SYSCALL_BASE+259) +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun (__NR_SYSCALL_BASE+260) +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete (__NR_SYSCALL_BASE+261) +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime (__NR_SYSCALL_BASE+262) +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime (__NR_SYSCALL_BASE+263) +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres (__NR_SYSCALL_BASE+264) +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep (__NR_SYSCALL_BASE+265) +#endif + +#if !defined(__NR_statfs64) +#define __NR_statfs64 (__NR_SYSCALL_BASE+266) +#endif + +#if !defined(__NR_fstatfs64) +#define __NR_fstatfs64 (__NR_SYSCALL_BASE+267) +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill (__NR_SYSCALL_BASE+268) +#endif + +#if !defined(__NR_utimes) +#define __NR_utimes (__NR_SYSCALL_BASE+269) +#endif + +#if !defined(__NR_arm_fadvise64_64) +#define __NR_arm_fadvise64_64 (__NR_SYSCALL_BASE+270) +#endif + +#if !defined(__NR_pciconfig_iobase) +#define __NR_pciconfig_iobase (__NR_SYSCALL_BASE+271) +#endif + +#if !defined(__NR_pciconfig_read) +#define __NR_pciconfig_read (__NR_SYSCALL_BASE+272) +#endif + +#if !defined(__NR_pciconfig_write) +#define __NR_pciconfig_write (__NR_SYSCALL_BASE+273) +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open (__NR_SYSCALL_BASE+274) +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink (__NR_SYSCALL_BASE+275) +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend (__NR_SYSCALL_BASE+276) +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive (__NR_SYSCALL_BASE+277) +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify (__NR_SYSCALL_BASE+278) +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr (__NR_SYSCALL_BASE+279) +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid (__NR_SYSCALL_BASE+280) +#endif + +#if !defined(__NR_socket) +#define __NR_socket (__NR_SYSCALL_BASE+281) +#endif + +#if !defined(__NR_bind) +#define __NR_bind (__NR_SYSCALL_BASE+282) +#endif + +#if !defined(__NR_connect) +#define __NR_connect (__NR_SYSCALL_BASE+283) +#endif + +#if !defined(__NR_listen) +#define __NR_listen (__NR_SYSCALL_BASE+284) +#endif + +#if !defined(__NR_accept) +#define __NR_accept (__NR_SYSCALL_BASE+285) +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname (__NR_SYSCALL_BASE+286) +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername (__NR_SYSCALL_BASE+287) +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair (__NR_SYSCALL_BASE+288) +#endif + +#if !defined(__NR_send) +#define __NR_send (__NR_SYSCALL_BASE+289) +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto (__NR_SYSCALL_BASE+290) +#endif + +#if !defined(__NR_recv) +#define __NR_recv (__NR_SYSCALL_BASE+291) +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom (__NR_SYSCALL_BASE+292) +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown (__NR_SYSCALL_BASE+293) +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt (__NR_SYSCALL_BASE+294) +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt (__NR_SYSCALL_BASE+295) +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg (__NR_SYSCALL_BASE+296) +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg (__NR_SYSCALL_BASE+297) +#endif + +#if !defined(__NR_semop) +#define __NR_semop (__NR_SYSCALL_BASE+298) +#endif + +#if !defined(__NR_semget) +#define __NR_semget (__NR_SYSCALL_BASE+299) +#endif + +#if !defined(__NR_semctl) +#define __NR_semctl (__NR_SYSCALL_BASE+300) +#endif + +#if !defined(__NR_msgsnd) +#define __NR_msgsnd (__NR_SYSCALL_BASE+301) +#endif + +#if !defined(__NR_msgrcv) +#define __NR_msgrcv (__NR_SYSCALL_BASE+302) +#endif + +#if !defined(__NR_msgget) +#define __NR_msgget (__NR_SYSCALL_BASE+303) +#endif + +#if !defined(__NR_msgctl) +#define __NR_msgctl (__NR_SYSCALL_BASE+304) +#endif + +#if !defined(__NR_shmat) +#define __NR_shmat (__NR_SYSCALL_BASE+305) +#endif + +#if !defined(__NR_shmdt) +#define __NR_shmdt (__NR_SYSCALL_BASE+306) +#endif + +#if !defined(__NR_shmget) +#define __NR_shmget (__NR_SYSCALL_BASE+307) +#endif + +#if !defined(__NR_shmctl) +#define __NR_shmctl (__NR_SYSCALL_BASE+308) +#endif + +#if !defined(__NR_add_key) +#define __NR_add_key (__NR_SYSCALL_BASE+309) +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key (__NR_SYSCALL_BASE+310) +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl (__NR_SYSCALL_BASE+311) +#endif + +#if !defined(__NR_semtimedop) +#define __NR_semtimedop (__NR_SYSCALL_BASE+312) +#endif + +#if !defined(__NR_vserver) +#define __NR_vserver (__NR_SYSCALL_BASE+313) +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set (__NR_SYSCALL_BASE+314) +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get (__NR_SYSCALL_BASE+315) +#endif + +#if !defined(__NR_inotify_init) +#define __NR_inotify_init (__NR_SYSCALL_BASE+316) +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch (__NR_SYSCALL_BASE+317) +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch (__NR_SYSCALL_BASE+318) +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind (__NR_SYSCALL_BASE+319) +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy (__NR_SYSCALL_BASE+320) +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy (__NR_SYSCALL_BASE+321) +#endif + +#if !defined(__NR_openat) +#define __NR_openat (__NR_SYSCALL_BASE+322) +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat (__NR_SYSCALL_BASE+323) +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat (__NR_SYSCALL_BASE+324) +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat (__NR_SYSCALL_BASE+325) +#endif + +#if !defined(__NR_futimesat) +#define __NR_futimesat (__NR_SYSCALL_BASE+326) +#endif + +#if !defined(__NR_fstatat64) +#define __NR_fstatat64 (__NR_SYSCALL_BASE+327) +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat (__NR_SYSCALL_BASE+328) +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat (__NR_SYSCALL_BASE+329) +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat (__NR_SYSCALL_BASE+330) +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat (__NR_SYSCALL_BASE+331) +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat (__NR_SYSCALL_BASE+332) +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat (__NR_SYSCALL_BASE+333) +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat (__NR_SYSCALL_BASE+334) +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 (__NR_SYSCALL_BASE+335) +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll (__NR_SYSCALL_BASE+336) +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare (__NR_SYSCALL_BASE+337) +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list (__NR_SYSCALL_BASE+338) +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list (__NR_SYSCALL_BASE+339) +#endif + +#if !defined(__NR_splice) +#define __NR_splice (__NR_SYSCALL_BASE+340) +#endif + +#if !defined(__NR_arm_sync_file_range) +#define __NR_arm_sync_file_range (__NR_SYSCALL_BASE+341) +#endif + +#if !defined(__NR_tee) +#define __NR_tee (__NR_SYSCALL_BASE+342) +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice (__NR_SYSCALL_BASE+343) +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages (__NR_SYSCALL_BASE+344) +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu (__NR_SYSCALL_BASE+345) +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait (__NR_SYSCALL_BASE+346) +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load (__NR_SYSCALL_BASE+347) +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat (__NR_SYSCALL_BASE+348) +#endif + +#if !defined(__NR_signalfd) +#define __NR_signalfd (__NR_SYSCALL_BASE+349) +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create (__NR_SYSCALL_BASE+350) +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd (__NR_SYSCALL_BASE+351) +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate (__NR_SYSCALL_BASE+352) +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime (__NR_SYSCALL_BASE+353) +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime (__NR_SYSCALL_BASE+354) +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 (__NR_SYSCALL_BASE+355) +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 (__NR_SYSCALL_BASE+356) +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 (__NR_SYSCALL_BASE+357) +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 (__NR_SYSCALL_BASE+358) +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 (__NR_SYSCALL_BASE+359) +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 (__NR_SYSCALL_BASE+360) +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv (__NR_SYSCALL_BASE+361) +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev (__NR_SYSCALL_BASE+362) +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363) +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open (__NR_SYSCALL_BASE+364) +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg (__NR_SYSCALL_BASE+365) +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 (__NR_SYSCALL_BASE+366) +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init (__NR_SYSCALL_BASE+367) +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark (__NR_SYSCALL_BASE+368) +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 (__NR_SYSCALL_BASE+369) +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at (__NR_SYSCALL_BASE+370) +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at (__NR_SYSCALL_BASE+371) +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime (__NR_SYSCALL_BASE+372) +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs (__NR_SYSCALL_BASE+373) +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg (__NR_SYSCALL_BASE+374) +#endif + +#if !defined(__NR_setns) +#define __NR_setns (__NR_SYSCALL_BASE+375) +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv (__NR_SYSCALL_BASE+376) +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev (__NR_SYSCALL_BASE+377) +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp (__NR_SYSCALL_BASE+378) +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module (__NR_SYSCALL_BASE+379) +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr (__NR_SYSCALL_BASE+380) +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr (__NR_SYSCALL_BASE+381) +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 (__NR_SYSCALL_BASE+382) +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp (__NR_SYSCALL_BASE+383) +#endif + +#if !defined(__NR_getrandom) +#define __NR_getrandom (__NR_SYSCALL_BASE+384) +#endif + +#if !defined(__NR_memfd_create) +#define __NR_memfd_create (__NR_SYSCALL_BASE+385) +#endif + +#if !defined(__NR_bpf) +#define __NR_bpf (__NR_SYSCALL_BASE+386) +#endif + +#if !defined(__NR_execveat) +#define __NR_execveat (__NR_SYSCALL_BASE+387) +#endif + +#if !defined(__NR_userfaultfd) +#define __NR_userfaultfd (__NR_SYSCALL_BASE+388) +#endif + +#if !defined(__NR_membarrier) +#define __NR_membarrier (__NR_SYSCALL_BASE+389) +#endif + +#if !defined(__NR_mlock2) +#define __NR_mlock2 (__NR_SYSCALL_BASE+390) +#endif + +#if !defined(__NR_copy_file_range) +#define __NR_copy_file_range (__NR_SYSCALL_BASE+391) +#endif + +#if !defined(__NR_preadv2) +#define __NR_preadv2 (__NR_SYSCALL_BASE+392) +#endif + +#if !defined(__NR_pwritev2) +#define __NR_pwritev2 (__NR_SYSCALL_BASE+393) +#endif + +#if !defined(__NR_pkey_mprotect) +#define __NR_pkey_mprotect (__NR_SYSCALL_BASE+394) +#endif + +#if !defined(__NR_pkey_alloc) +#define __NR_pkey_alloc (__NR_SYSCALL_BASE+395) +#endif + +#if !defined(__NR_pkey_free) +#define __NR_pkey_free (__NR_SYSCALL_BASE+396) +#endif + +#if !defined(__NR_statx) +#define __NR_statx (__NR_SYSCALL_BASE+397) +#endif + +#if !defined(__NR_rseq) +#define __NR_rseq (__NR_SYSCALL_BASE+398) +#endif + +#if !defined(__NR_io_pgetevents) +#define __NR_io_pgetevents (__NR_SYSCALL_BASE+399) +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages (__NR_SYSCALL_BASE+400) +#endif + +#if !defined(__NR_kexec_file_load) +#define __NR_kexec_file_load (__NR_SYSCALL_BASE+401) +#endif + +#if !defined(__NR_clock_gettime64) +#define __NR_clock_gettime64 (__NR_SYSCALL_BASE+403) +#endif + +#if !defined(__NR_clock_settime64) +#define __NR_clock_settime64 (__NR_SYSCALL_BASE+404) +#endif + +#if !defined(__NR_clock_adjtime64) +#define __NR_clock_adjtime64 (__NR_SYSCALL_BASE+405) +#endif + +#if !defined(__NR_clock_getres_time64) +#define __NR_clock_getres_time64 (__NR_SYSCALL_BASE+406) +#endif + +#if !defined(__NR_clock_nanosleep_time64) +#define __NR_clock_nanosleep_time64 (__NR_SYSCALL_BASE+407) +#endif + +#if !defined(__NR_timer_gettime64) +#define __NR_timer_gettime64 (__NR_SYSCALL_BASE+408) +#endif + +#if !defined(__NR_timer_settime64) +#define __NR_timer_settime64 (__NR_SYSCALL_BASE+409) +#endif + +#if !defined(__NR_timerfd_gettime64) +#define __NR_timerfd_gettime64 (__NR_SYSCALL_BASE+410) +#endif + +#if !defined(__NR_timerfd_settime64) +#define __NR_timerfd_settime64 (__NR_SYSCALL_BASE+411) +#endif + +#if !defined(__NR_utimensat_time64) +#define __NR_utimensat_time64 (__NR_SYSCALL_BASE+412) +#endif + +#if !defined(__NR_pselect6_time64) +#define __NR_pselect6_time64 (__NR_SYSCALL_BASE+413) +#endif + +#if !defined(__NR_ppoll_time64) +#define __NR_ppoll_time64 (__NR_SYSCALL_BASE+414) +#endif + +#if !defined(__NR_io_pgetevents_time64) +#define __NR_io_pgetevents_time64 (__NR_SYSCALL_BASE+416) +#endif + +#if !defined(__NR_recvmmsg_time64) +#define __NR_recvmmsg_time64 (__NR_SYSCALL_BASE+417) +#endif + +#if !defined(__NR_mq_timedsend_time64) +#define __NR_mq_timedsend_time64 (__NR_SYSCALL_BASE+418) +#endif + +#if !defined(__NR_mq_timedreceive_time64) +#define __NR_mq_timedreceive_time64 (__NR_SYSCALL_BASE+419) +#endif + +#if !defined(__NR_semtimedop_time64) +#define __NR_semtimedop_time64 (__NR_SYSCALL_BASE+420) +#endif + +#if !defined(__NR_rt_sigtimedwait_time64) +#define __NR_rt_sigtimedwait_time64 (__NR_SYSCALL_BASE+421) +#endif + +#if !defined(__NR_futex_time64) +#define __NR_futex_time64 (__NR_SYSCALL_BASE+422) +#endif + +#if !defined(__NR_sched_rr_get_interval_time64) +#define __NR_sched_rr_get_interval_time64 (__NR_SYSCALL_BASE+423) +#endif + +#if !defined(__NR_pidfd_send_signal) +#define __NR_pidfd_send_signal (__NR_SYSCALL_BASE+424) +#endif + +#if !defined(__NR_io_uring_setup) +#define __NR_io_uring_setup (__NR_SYSCALL_BASE+425) +#endif + +#if !defined(__NR_io_uring_enter) +#define __NR_io_uring_enter (__NR_SYSCALL_BASE+426) +#endif + +#if !defined(__NR_io_uring_register) +#define __NR_io_uring_register (__NR_SYSCALL_BASE+427) +#endif + +#if !defined(__NR_open_tree) +#define __NR_open_tree (__NR_SYSCALL_BASE+428) +#endif + +#if !defined(__NR_move_mount) +#define __NR_move_mount (__NR_SYSCALL_BASE+429) +#endif + +#if !defined(__NR_fsopen) +#define __NR_fsopen (__NR_SYSCALL_BASE+430) +#endif + +#if !defined(__NR_fsconfig) +#define __NR_fsconfig (__NR_SYSCALL_BASE+431) +#endif + +#if !defined(__NR_fsmount) +#define __NR_fsmount (__NR_SYSCALL_BASE+432) +#endif + +#if !defined(__NR_fspick) +#define __NR_fspick (__NR_SYSCALL_BASE+433) +#endif + +#if !defined(__NR_pidfd_open) +#define __NR_pidfd_open (__NR_SYSCALL_BASE+434) +#endif + +#if !defined(__NR_clone3) +#define __NR_clone3 (__NR_SYSCALL_BASE+435) +#endif + +#if !defined(__NR_openat2) +#define __NR_openat2 (__NR_SYSCALL_BASE+437) +#endif + +#if !defined(__NR_pidfd_getfd) +#define __NR_pidfd_getfd (__NR_SYSCALL_BASE+438) +#endif + +#if !defined(__NR_faccessat2) +#define __NR_faccessat2 (__NR_SYSCALL_BASE+439) +#endif + +// ARM private syscalls. +#if !defined(__ARM_NR_BASE) +#define __ARM_NR_BASE (__NR_SYSCALL_BASE + 0xF0000) +#endif + +#if !defined(__ARM_NR_breakpoint) +#define __ARM_NR_breakpoint (__ARM_NR_BASE+1) +#endif + +#if !defined(__ARM_NR_cacheflush) +#define __ARM_NR_cacheflush (__ARM_NR_BASE+2) +#endif + +#if !defined(__ARM_NR_usr26) +#define __ARM_NR_usr26 (__ARM_NR_BASE+3) +#endif + +#if !defined(__ARM_NR_usr32) +#define __ARM_NR_usr32 (__ARM_NR_BASE+4) +#endif + +#if !defined(__ARM_NR_set_tls) +#define __ARM_NR_set_tls (__ARM_NR_BASE+5) +#endif + +// ARM kernel private syscall. +#if !defined(__ARM_NR_cmpxchg) +#define __ARM_NR_cmpxchg (__ARM_NR_BASE+0x00fff0) +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_SYSCALLS_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/arm_linux_ucontext.h b/security/sandbox/chromium/sandbox/linux/system_headers/arm_linux_ucontext.h new file mode 100644 index 0000000000..2b2090e392 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/arm_linux_ucontext.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_UCONTEXT_H_ + +#include <stddef.h> + +// In PNaCl toolchain, sigcontext and stack_t is not defined. So here declare +// them. +struct sigcontext { + unsigned long trap_no; + unsigned long error_code; + unsigned long oldmask; + unsigned long arm_r0; + unsigned long arm_r1; + unsigned long arm_r2; + unsigned long arm_r3; + unsigned long arm_r4; + unsigned long arm_r5; + unsigned long arm_r6; + unsigned long arm_r7; + unsigned long arm_r8; + unsigned long arm_r9; + unsigned long arm_r10; + unsigned long arm_fp; + unsigned long arm_ip; + unsigned long arm_sp; + unsigned long arm_lr; + unsigned long arm_pc; + unsigned long arm_cpsr; + unsigned long fault_address; +}; + +typedef struct sigaltstack { + void* ss_sp; + int ss_flags; + size_t ss_size; +} stack_t; + + +// We also need greg_t for the sandbox, include it in this header as well. +typedef unsigned long greg_t; + +// typedef unsigned long sigset_t; +typedef struct ucontext { + unsigned long uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + struct sigcontext uc_mcontext; + sigset_t uc_sigmask; + /* Allow for uc_sigmask growth. Glibc uses a 1024-bit sigset_t. */ + int __not_used[32 - (sizeof(sigset_t) / sizeof(int))]; + /* Last for extensibility. Eight byte aligned because some + coprocessors require eight byte alignment. */ + unsigned long uc_regspace[128] __attribute__((__aligned__(8))); +} ucontext_t; + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ARM_LINUX_UCONTEXT_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/capability.h b/security/sandbox/chromium/sandbox/linux/system_headers/capability.h new file mode 100644 index 0000000000..f91fcf78ac --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/capability.h @@ -0,0 +1,42 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_CAPABILITY_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_CAPABILITY_H_ + +#include <stdint.h> + +// The following macros are taken from linux/capability.h. +// We only support capability version 3, which was introduced in Linux 2.6.26. +#ifndef _LINUX_CAPABILITY_VERSION_3 +#define _LINUX_CAPABILITY_VERSION_3 0x20080522 +#endif +#ifndef _LINUX_CAPABILITY_U32S_3 +#define _LINUX_CAPABILITY_U32S_3 2 +#endif +#ifndef CAP_TO_INDEX +#define CAP_TO_INDEX(x) ((x) >> 5) // 1 << 5 == bits in __u32 +#endif +#ifndef CAP_TO_MASK +#define CAP_TO_MASK(x) (1 << ((x) & 31)) // mask for indexed __u32 +#endif +#ifndef CAP_SYS_CHROOT +#define CAP_SYS_CHROOT 18 +#endif +#ifndef CAP_SYS_ADMIN +#define CAP_SYS_ADMIN 21 +#endif + +struct cap_hdr { + uint32_t version; + int pid; +}; + +struct cap_data { + uint32_t effective; + uint32_t permitted; + uint32_t inheritable; +}; + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_CAPABILITY_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/i386_linux_ucontext.h b/security/sandbox/chromium/sandbox/linux/system_headers/i386_linux_ucontext.h new file mode 100644 index 0000000000..1a7b975de8 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/i386_linux_ucontext.h @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_ANDROID_I386_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_ANDROID_I386_UCONTEXT_H_ + +#include <stddef.h> +#include <stdint.h> + +// This is mostly copied from breakpad (common/android/include/sys/ucontext.h), +// except we do use sigset_t for uc_sigmask instead of a custom type. + +// In PNaCl toolchain, sigcontext is not defined. So here declare it. +typedef struct sigaltstack { + void* ss_sp; + int ss_flags; + size_t ss_size; +} stack_t; + +/* 80-bit floating-point register */ +struct _libc_fpreg { + unsigned short significand[4]; + unsigned short exponent; +}; + +/* Simple floating-point state, see FNSTENV instruction */ +struct _libc_fpstate { + unsigned long cw; + unsigned long sw; + unsigned long tag; + unsigned long ipoff; + unsigned long cssel; + unsigned long dataoff; + unsigned long datasel; + struct _libc_fpreg _st[8]; + unsigned long status; +}; + +typedef uint32_t greg_t; + +typedef struct { + uint32_t gregs[19]; + struct _libc_fpstate* fpregs; + uint32_t oldmask; + uint32_t cr2; +} mcontext_t; + +enum { + REG_GS = 0, + REG_FS, + REG_ES, + REG_DS, + REG_EDI, + REG_ESI, + REG_EBP, + REG_ESP, + REG_EBX, + REG_EDX, + REG_ECX, + REG_EAX, + REG_TRAPNO, + REG_ERR, + REG_EIP, + REG_CS, + REG_EFL, + REG_UESP, + REG_SS, +}; + +typedef struct ucontext { + uint32_t uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; + // Android and PNaCl toolchain's sigset_t has only 32 bits, though Linux + // ABI requires 64 bits. + union { + sigset_t uc_sigmask; + uint32_t kernel_sigmask[2]; + }; + struct _libc_fpstate __fpregs_mem; +} ucontext_t; + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_ANDROID_I386_UCONTEXT_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/linux_filter.h b/security/sandbox/chromium/sandbox/linux/system_headers/linux_filter.h new file mode 100644 index 0000000000..b23b6eb0c1 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/linux_filter.h @@ -0,0 +1,140 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_ + +#include <stdint.h> + +// The following structs and macros are taken from linux/filter.h, +// as some toolchain does not expose them. +struct sock_filter { + uint16_t code; + uint8_t jt; + uint8_t jf; + uint32_t k; +}; + +struct sock_fprog { + uint16_t len; + struct sock_filter *filter; +}; + +#ifndef BPF_CLASS +#define BPF_CLASS(code) ((code) & 0x07) +#endif + +#ifndef BPF_LD +#define BPF_LD 0x00 +#endif + +#ifndef BPF_ALU +#define BPF_ALU 0x04 +#endif + +#ifndef BPF_JMP +#define BPF_JMP 0x05 +#endif + +#ifndef BPF_RET +#define BPF_RET 0x06 +#endif + +#ifndef BPF_SIZE +#define BPF_SIZE(code) ((code) & 0x18) +#endif + +#ifndef BPF_W +#define BPF_W 0x00 +#endif + +#ifndef BPF_MODE +#define BPF_MODE(code) ((code) & 0xe0) +#endif + +#ifndef BPF_ABS +#define BPF_ABS 0x20 +#endif + +#ifndef BPF_OP +#define BPF_OP(code) ((code) & 0xf0) +#endif + +#ifndef BPF_ADD +#define BPF_ADD 0x00 +#endif + +#ifndef BPF_SUB +#define BPF_SUB 0x10 +#endif + +#ifndef BPF_MUL +#define BPF_MUL 0x20 +#endif + +#ifndef BPF_DIV +#define BPF_DIV 0x30 +#endif + +#ifndef BPF_OR +#define BPF_OR 0x40 +#endif + +#ifndef BPF_AND +#define BPF_AND 0x50 +#endif + +#ifndef BPF_LSH +#define BPF_LSH 0x60 +#endif + +#ifndef BPF_RSH +#define BPF_RSH 0x70 +#endif + +#ifndef BPF_NEG +#define BPF_NEG 0x80 +#endif + +#ifndef BPF_MOD +#define BPF_MOD 0x90 +#endif + +#ifndef BPF_XOR +#define BPF_XOR 0xA0 +#endif + +#ifndef BPF_JA +#define BPF_JA 0x00 +#endif + +#ifndef BPF_JEQ +#define BPF_JEQ 0x10 +#endif + +#ifndef BPF_JGT +#define BPF_JGT 0x20 +#endif + +#ifndef BPF_JGE +#define BPF_JGE 0x30 +#endif + +#ifndef BPF_JSET +#define BPF_JSET 0x40 +#endif + +#ifndef BPF_SRC +#define BPF_SRC(code) ((code) & 0x08) +#endif + +#ifndef BPF_K +#define BPF_K 0x00 +#endif + +#ifndef BPF_MAXINSNS +#define BPF_MAXINSNS 4096 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/linux_futex.h b/security/sandbox/chromium/sandbox/linux/system_headers/linux_futex.h new file mode 100644 index 0000000000..4e28403336 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/linux_futex.h @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FUTEX_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FUTEX_H_ + +#if !defined(__native_client_nonsfi__) +#include <linux/futex.h> +#endif // !defined(__native_client_nonsfi__) + +#if !defined(FUTEX_WAIT) +#define FUTEX_WAIT 0 +#endif + +#if !defined(FUTEX_WAKE) +#define FUTEX_WAKE 1 +#endif + +#if !defined(FUTEX_FD) +#define FUTEX_FD 2 +#endif + +#if !defined(FUTEX_REQUEUE) +#define FUTEX_REQUEUE 3 +#endif + +#if !defined(FUTEX_CMP_REQUEUE) +#define FUTEX_CMP_REQUEUE 4 +#endif + +#if !defined(FUTEX_WAKE_OP) +#define FUTEX_WAKE_OP 5 +#endif + +#if !defined(FUTEX_LOCK_PI) +#define FUTEX_LOCK_PI 6 +#endif + +#if !defined(FUTEX_UNLOCK_PI) +#define FUTEX_UNLOCK_PI 7 +#endif + +#if !defined(FUTEX_TRYLOCK_PI) +#define FUTEX_TRYLOCK_PI 8 +#endif + +#if !defined(FUTEX_WAIT_BITSET) +#define FUTEX_WAIT_BITSET 9 +#endif + +#if !defined(FUTEX_WAKE_BITSET) +#define FUTEX_WAKE_BITSET 10 +#endif + +#if !defined(FUTEX_WAIT_REQUEUE_PI) +#define FUTEX_WAIT_REQUEUE_PI 11 +#endif + +#if !defined(FUTEX_CMP_REQUEUE_PI) +#define FUTEX_CMP_REQUEUE_PI 12 +#endif + +#if !defined(FUTEX_PRIVATE_FLAG) +#define FUTEX_PRIVATE_FLAG 128 +#endif + +#if !defined FUTEX_CLOCK_REALTIME +#define FUTEX_CLOCK_REALTIME 256 +#endif + +#if !defined(FUTEX_CMD_MASK) +#define FUTEX_CMD_MASK ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) +#endif + +#if !defined(FUTEX_CMP_REQUEUE_PI_PRIVATE) +#define FUTEX_CMP_REQUEUE_PI_PRIVATE (FUTEX_CMP_REQUEUE_PI | FUTEX_PRIVATE_FLAG) +#endif + +#if !defined(FUTEX_UNLOCK_PI_PRIVATE) +#define FUTEX_UNLOCK_PI_PRIVATE (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG) +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FUTEX_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/linux_seccomp.h b/security/sandbox/chromium/sandbox/linux/system_headers/linux_seccomp.h new file mode 100644 index 0000000000..a60fe2ad3d --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/linux_seccomp.h @@ -0,0 +1,110 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SECCOMP_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SECCOMP_H_ + +// The Seccomp2 kernel ABI is not part of older versions of glibc. +// As we can't break compilation with these versions of the library, +// we explicitly define all missing symbols. +// If we ever decide that we can now rely on system headers, the following +// include files should be enabled: +// #include <linux/audit.h> +// #include <linux/seccomp.h> + +// For audit.h +#ifndef EM_ARM +#define EM_ARM 40 +#endif +#ifndef EM_386 +#define EM_386 3 +#endif +#ifndef EM_X86_64 +#define EM_X86_64 62 +#endif +#ifndef EM_MIPS +#define EM_MIPS 8 +#endif +#ifndef EM_AARCH64 +#define EM_AARCH64 183 +#endif + +#ifndef __AUDIT_ARCH_64BIT +#define __AUDIT_ARCH_64BIT 0x80000000 +#endif +#ifndef __AUDIT_ARCH_LE +#define __AUDIT_ARCH_LE 0x40000000 +#endif +#ifndef AUDIT_ARCH_ARM +#define AUDIT_ARCH_ARM (EM_ARM|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_I386 +#define AUDIT_ARCH_I386 (EM_386|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_X86_64 +#define AUDIT_ARCH_X86_64 (EM_X86_64|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_MIPSEL +#define AUDIT_ARCH_MIPSEL (EM_MIPS|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_MIPSEL64 +#define AUDIT_ARCH_MIPSEL64 (EM_MIPS|__AUDIT_ARCH_64BIT|__AUDIT_ARCH_LE) +#endif +#ifndef AUDIT_ARCH_AARCH64 +#define AUDIT_ARCH_AARCH64 (EM_AARCH64 | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE) +#endif + +// For prctl.h +#ifndef PR_SET_SECCOMP +#define PR_SET_SECCOMP 22 +#define PR_GET_SECCOMP 21 +#endif +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#define PR_GET_NO_NEW_PRIVS 39 +#endif +#ifndef IPC_64 +#define IPC_64 0x0100 +#endif + +// In order to build will older tool chains, we currently have to avoid +// including <linux/seccomp.h>. Until that can be fixed (if ever). Rely on +// our own definitions of the seccomp kernel ABI. +#ifndef SECCOMP_MODE_FILTER +#define SECCOMP_MODE_DISABLED 0 +#define SECCOMP_MODE_STRICT 1 +#define SECCOMP_MODE_FILTER 2 // User user-supplied filter +#endif + +#ifndef SECCOMP_SET_MODE_STRICT +#define SECCOMP_SET_MODE_STRICT 0 +#endif +#ifndef SECCOMP_SET_MODE_FILTER +#define SECCOMP_SET_MODE_FILTER 1 +#endif +#ifndef SECCOMP_FILTER_FLAG_TSYNC +#define SECCOMP_FILTER_FLAG_TSYNC 1 +#endif + +#ifndef SECCOMP_RET_KILL +// Return values supported for BPF filter programs. Please note that the +// "illegal" SECCOMP_RET_INVALID is not supported by the kernel, should only +// ever be used internally, and would result in the kernel killing our process. +#define SECCOMP_RET_KILL 0x00000000U // Kill the task immediately +#define SECCOMP_RET_INVALID 0x00010000U // Illegal return value +#define SECCOMP_RET_TRAP 0x00030000U // Disallow and force a SIGSYS +#define SECCOMP_RET_ERRNO 0x00050000U // Returns an errno +#define SECCOMP_RET_TRACE 0x7ff00000U // Pass to a tracer or disallow +#define SECCOMP_RET_ALLOW 0x7fff0000U // Allow +#define SECCOMP_RET_ACTION 0xffff0000U // Masks for the return value +#define SECCOMP_RET_DATA 0x0000ffffU // sections +#else +#define SECCOMP_RET_INVALID 0x00010000U // Illegal return value +#endif + +#ifndef SYS_SECCOMP +#define SYS_SECCOMP 1 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SECCOMP_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/linux_signal.h b/security/sandbox/chromium/sandbox/linux/system_headers/linux_signal.h new file mode 100644 index 0000000000..f5a7367617 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/linux_signal.h @@ -0,0 +1,150 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SIGNAL_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SIGNAL_H_ + +#include <stdint.h> + +#include "build/build_config.h" + +// NOTE: On some toolchains, signal related ABI is incompatible with Linux's +// (not undefined, but defined different values and in different memory +// layouts). So, fill the gap here. +#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || \ + defined(__aarch64__) + +#define LINUX_SIGHUP 1 +#define LINUX_SIGINT 2 +#define LINUX_SIGQUIT 3 +#define LINUX_SIGABRT 6 +#define LINUX_SIGBUS 7 +#define LINUX_SIGUSR1 10 +#define LINUX_SIGSEGV 11 +#define LINUX_SIGUSR2 12 +#define LINUX_SIGPIPE 13 +#define LINUX_SIGTERM 15 +#define LINUX_SIGCHLD 17 +#define LINUX_SIGSYS 31 + +#define LINUX_SIG_BLOCK 0 +#define LINUX_SIG_UNBLOCK 1 + +#define LINUX_SA_SIGINFO 4 +#define LINUX_SA_NODEFER 0x40000000 +#define LINUX_SA_RESTART 0x10000000 + +#define LINUX_SIG_DFL 0 + +#elif defined(__mips__) + +#define LINUX_SIGHUP 1 +#define LINUX_SIGINT 2 +#define LINUX_SIGQUIT 3 +#define LINUX_SIGABRT 6 +#define LINUX_SIGBUS 10 +#define LINUX_SIGSEGV 11 +#define LINUX_SIGSYS 12 +#define LINUX_SIGPIPE 13 +#define LINUX_SIGTERM 15 +#define LINUX_SIGUSR1 16 +#define LINUX_SIGUSR2 17 +#define LINUX_SIGCHLD 18 + +#define LINUX_SIG_BLOCK 1 +#define LINUX_SIG_UNBLOCK 2 + +#define LINUX_SA_SIGINFO 0x00000008 +#define LINUX_SA_NODEFER 0x40000000 +#define LINUX_SA_RESTART 0x10000000 + +#define LINUX_SIG_DFL 0 + +#else +#error "Unsupported platform" +#endif + +#if defined(__native_client_nonsfi__) +#if !defined(__i386__) && !defined(__arm__) +#error "Unsupported platform" +#endif + +#include <signal.h> + +struct LinuxSigInfo { + int si_signo; + int si_errno; + int si_code; + + // Extra data is followed by the |si_code|. The length depends on the + // signal number. + char _sifields[1]; +}; + +#include "sandbox/linux/system_headers/linux_ucontext.h" + +#else // !defined(__native_client_nonsfi__) + +#include <signal.h> + +static_assert(LINUX_SIGHUP == SIGHUP, "LINUX_SIGHUP == SIGHUP"); +static_assert(LINUX_SIGINT == SIGINT, "LINUX_SIGINT == SIGINT"); +static_assert(LINUX_SIGQUIT == SIGQUIT, "LINUX_SIGQUIT == SIGQUIT"); +static_assert(LINUX_SIGABRT == SIGABRT, "LINUX_SIGABRT == SIGABRT"); +static_assert(LINUX_SIGBUS == SIGBUS, "LINUX_SIGBUS == SIGBUS"); +static_assert(LINUX_SIGUSR1 == SIGUSR1, "LINUX_SIGUSR1 == SIGUSR1"); +static_assert(LINUX_SIGSEGV == SIGSEGV, "LINUX_SIGSEGV == SIGSEGV"); +static_assert(LINUX_SIGUSR2 == SIGUSR2, "LINUX_SIGUSR2 == SIGUSR2"); +static_assert(LINUX_SIGPIPE == SIGPIPE, "LINUX_SIGPIPE == SIGPIPE"); +static_assert(LINUX_SIGTERM == SIGTERM, "LINUX_SIGTERM == SIGTERM"); +static_assert(LINUX_SIGCHLD == SIGCHLD, "LINUX_SIGCHLD == SIGCHLD"); +static_assert(LINUX_SIGSYS == SIGSYS, "LINUX_SIGSYS == SIGSYS"); +static_assert(LINUX_SIG_BLOCK == SIG_BLOCK, "LINUX_SIG_BLOCK == SIG_BLOCK"); +static_assert(LINUX_SIG_UNBLOCK == SIG_UNBLOCK, + "LINUX_SIG_UNBLOCK == SIG_UNBLOCK"); +static_assert(LINUX_SA_SIGINFO == SA_SIGINFO, "LINUX_SA_SIGINFO == SA_SIGINFO"); +static_assert(LINUX_SA_NODEFER == SA_NODEFER, "LINUX_SA_NODEFER == SA_NODEFER"); +static_assert(LINUX_SA_RESTART == SA_RESTART, "LINUX_SA_RESTART == SA_RESTART"); +static_assert(LINUX_SIG_DFL == SIG_DFL, "LINUX_SIG_DFL == SIG_DFL"); + +typedef siginfo_t LinuxSigInfo; + +#endif // !defined(__native_client_nonsfi__) + +// struct sigset_t is different size in PNaCl from the Linux's. +#if (defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS)) +#if !defined(_NSIG_WORDS) +#define _NSIG_WORDS 4 +#endif +struct LinuxSigSet { + unsigned long sig[_NSIG_WORDS]; +}; +#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_64_BITS) +#if !defined(_NSIG_WORDS) +#define _NSIG_WORDS 2 +#endif +struct LinuxSigSet { + unsigned long sig[_NSIG_WORDS]; +}; +#else +typedef uint64_t LinuxSigSet; +#endif + +// struct sigaction is different in PNaCl from the Linux's. +#if defined(__mips__) +struct LinuxSigAction { + unsigned int sa_flags; + void (*kernel_handler)(int); + LinuxSigSet sa_mask; +}; +#else +struct LinuxSigAction { + void (*kernel_handler)(int); + uint32_t sa_flags; + void (*sa_restorer)(void); + LinuxSigSet sa_mask; +}; +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SIGNAL_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/linux_syscalls.h b/security/sandbox/chromium/sandbox/linux/system_headers/linux_syscalls.h new file mode 100644 index 0000000000..2b78a0cc3b --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/linux_syscalls.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header will be kept up to date so that we can compile system-call +// policies even when system headers are old. +// System call numbers are accessible through __NR_syscall_name. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SYSCALLS_H_ + +#include "build/build_config.h" + +#if defined(__x86_64__) +#include "sandbox/linux/system_headers/x86_64_linux_syscalls.h" +#endif + +#if defined(__i386__) +#include "sandbox/linux/system_headers/x86_32_linux_syscalls.h" +#endif + +#if defined(__arm__) && defined(__ARM_EABI__) +#include "sandbox/linux/system_headers/arm_linux_syscalls.h" +#endif + +#if (defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_32_BITS)) +#include "sandbox/linux/system_headers/mips_linux_syscalls.h" +#endif + +#if defined(ARCH_CPU_MIPS_FAMILY) && defined(ARCH_CPU_64_BITS) +#include "sandbox/linux/system_headers/mips64_linux_syscalls.h" +#endif + +#if defined(__aarch64__) +#include "sandbox/linux/system_headers/arm64_linux_syscalls.h" +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_SYSCALLS_H_ + diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/linux_ucontext.h b/security/sandbox/chromium/sandbox/linux/system_headers/linux_ucontext.h new file mode 100644 index 0000000000..22ce780274 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/linux_ucontext.h @@ -0,0 +1,22 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_UCONTEXT_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_UCONTEXT_H_ + +#if defined(__native_client_nonsfi__) + +#if defined(__arm__) +#include "sandbox/linux/system_headers/arm_linux_ucontext.h" +#elif defined(__i386__) +#include "sandbox/linux/system_headers/i386_linux_ucontext.h" +#else +#error "No support for your architecture in PNaCl header" +#endif + +#else // defined(__native_client_nonsfi__) +#error "The header file included on non PNaCl." +#endif // defined(__native_client_nonsfi__) + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_UCONTEXT_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/x86_32_linux_syscalls.h b/security/sandbox/chromium/sandbox/linux/system_headers/x86_32_linux_syscalls.h new file mode 100644 index 0000000000..dc846ee7ad --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/x86_32_linux_syscalls.h @@ -0,0 +1,1731 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* Constructed by running: + * curl -vsSL https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/arch/x86/entry/syscalls/syscall_32.tbl?h=v5.8 + * | grep -vE '^#|^$' + * | awk '{ if ($2 == "i386") { print "#if !defined(__NR_" $3 ")\n#define __NR_" $3 " " $1 "\n#endif\n"; } }' + * */ + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_X86_32_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_X86_32_LINUX_SYSCALLS_H_ + +#if !defined(__i386__) +#error "Including header on wrong architecture" +#endif + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall 0 +#endif + +#if !defined(__NR_exit) +#define __NR_exit 1 +#endif + +#if !defined(__NR_fork) +#define __NR_fork 2 +#endif + +#if !defined(__NR_read) +#define __NR_read 3 +#endif + +#if !defined(__NR_write) +#define __NR_write 4 +#endif + +#if !defined(__NR_open) +#define __NR_open 5 +#endif + +#if !defined(__NR_close) +#define __NR_close 6 +#endif + +#if !defined(__NR_waitpid) +#define __NR_waitpid 7 +#endif + +#if !defined(__NR_creat) +#define __NR_creat 8 +#endif + +#if !defined(__NR_link) +#define __NR_link 9 +#endif + +#if !defined(__NR_unlink) +#define __NR_unlink 10 +#endif + +#if !defined(__NR_execve) +#define __NR_execve 11 +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir 12 +#endif + +#if !defined(__NR_time) +#define __NR_time 13 +#endif + +#if !defined(__NR_mknod) +#define __NR_mknod 14 +#endif + +#if !defined(__NR_chmod) +#define __NR_chmod 15 +#endif + +#if !defined(__NR_lchown) +#define __NR_lchown 16 +#endif + +#if !defined(__NR_break) +#define __NR_break 17 +#endif + +#if !defined(__NR_oldstat) +#define __NR_oldstat 18 +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek 19 +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid 20 +#endif + +#if !defined(__NR_mount) +#define __NR_mount 21 +#endif + +#if !defined(__NR_umount) +#define __NR_umount 22 +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid 23 +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid 24 +#endif + +#if !defined(__NR_stime) +#define __NR_stime 25 +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace 26 +#endif + +#if !defined(__NR_alarm) +#define __NR_alarm 27 +#endif + +#if !defined(__NR_oldfstat) +#define __NR_oldfstat 28 +#endif + +#if !defined(__NR_pause) +#define __NR_pause 29 +#endif + +#if !defined(__NR_utime) +#define __NR_utime 30 +#endif + +#if !defined(__NR_stty) +#define __NR_stty 31 +#endif + +#if !defined(__NR_gtty) +#define __NR_gtty 32 +#endif + +#if !defined(__NR_access) +#define __NR_access 33 +#endif + +#if !defined(__NR_nice) +#define __NR_nice 34 +#endif + +#if !defined(__NR_ftime) +#define __NR_ftime 35 +#endif + +#if !defined(__NR_sync) +#define __NR_sync 36 +#endif + +#if !defined(__NR_kill) +#define __NR_kill 37 +#endif + +#if !defined(__NR_rename) +#define __NR_rename 38 +#endif + +#if !defined(__NR_mkdir) +#define __NR_mkdir 39 +#endif + +#if !defined(__NR_rmdir) +#define __NR_rmdir 40 +#endif + +#if !defined(__NR_dup) +#define __NR_dup 41 +#endif + +#if !defined(__NR_pipe) +#define __NR_pipe 42 +#endif + +#if !defined(__NR_times) +#define __NR_times 43 +#endif + +#if !defined(__NR_prof) +#define __NR_prof 44 +#endif + +#if !defined(__NR_brk) +#define __NR_brk 45 +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid 46 +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid 47 +#endif + +#if !defined(__NR_signal) +#define __NR_signal 48 +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid 49 +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid 50 +#endif + +#if !defined(__NR_acct) +#define __NR_acct 51 +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 52 +#endif + +#if !defined(__NR_lock) +#define __NR_lock 53 +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl 54 +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl 55 +#endif + +#if !defined(__NR_mpx) +#define __NR_mpx 56 +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid 57 +#endif + +#if !defined(__NR_ulimit) +#define __NR_ulimit 58 +#endif + +#if !defined(__NR_oldolduname) +#define __NR_oldolduname 59 +#endif + +#if !defined(__NR_umask) +#define __NR_umask 60 +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot 61 +#endif + +#if !defined(__NR_ustat) +#define __NR_ustat 62 +#endif + +#if !defined(__NR_dup2) +#define __NR_dup2 63 +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid 64 +#endif + +#if !defined(__NR_getpgrp) +#define __NR_getpgrp 65 +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid 66 +#endif + +#if !defined(__NR_sigaction) +#define __NR_sigaction 67 +#endif + +#if !defined(__NR_sgetmask) +#define __NR_sgetmask 68 +#endif + +#if !defined(__NR_ssetmask) +#define __NR_ssetmask 69 +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid 70 +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid 71 +#endif + +#if !defined(__NR_sigsuspend) +#define __NR_sigsuspend 72 +#endif + +#if !defined(__NR_sigpending) +#define __NR_sigpending 73 +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname 74 +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit 75 +#endif + +#if !defined(__NR_getrlimit) +#define __NR_getrlimit 76 +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage 77 +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday 78 +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday 79 +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups 80 +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups 81 +#endif + +#if !defined(__NR_select) +#define __NR_select 82 +#endif + +#if !defined(__NR_symlink) +#define __NR_symlink 83 +#endif + +#if !defined(__NR_oldlstat) +#define __NR_oldlstat 84 +#endif + +#if !defined(__NR_readlink) +#define __NR_readlink 85 +#endif + +#if !defined(__NR_uselib) +#define __NR_uselib 86 +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon 87 +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot 88 +#endif + +#if !defined(__NR_readdir) +#define __NR_readdir 89 +#endif + +#if !defined(__NR_mmap) +#define __NR_mmap 90 +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap 91 +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate 92 +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate 93 +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod 94 +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown 95 +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority 96 +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority 97 +#endif + +#if !defined(__NR_profil) +#define __NR_profil 98 +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs 99 +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs 100 +#endif + +#if !defined(__NR_ioperm) +#define __NR_ioperm 101 +#endif + +#if !defined(__NR_socketcall) +#define __NR_socketcall 102 +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog 103 +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer 104 +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer 105 +#endif + +#if !defined(__NR_stat) +#define __NR_stat 106 +#endif + +#if !defined(__NR_lstat) +#define __NR_lstat 107 +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat 108 +#endif + +#if !defined(__NR_olduname) +#define __NR_olduname 109 +#endif + +#if !defined(__NR_iopl) +#define __NR_iopl 110 +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup 111 +#endif + +#if !defined(__NR_idle) +#define __NR_idle 112 +#endif + +#if !defined(__NR_vm86old) +#define __NR_vm86old 113 +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 114 +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff 115 +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo 116 +#endif + +#if !defined(__NR_ipc) +#define __NR_ipc 117 +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync 118 +#endif + +#if !defined(__NR_sigreturn) +#define __NR_sigreturn 119 +#endif + +#if !defined(__NR_clone) +#define __NR_clone 120 +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname 121 +#endif + +#if !defined(__NR_uname) +#define __NR_uname 122 +#endif + +#if !defined(__NR_modify_ldt) +#define __NR_modify_ldt 123 +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex 124 +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect 125 +#endif + +#if !defined(__NR_sigprocmask) +#define __NR_sigprocmask 126 +#endif + +#if !defined(__NR_create_module) +#define __NR_create_module 127 +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module 128 +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module 129 +#endif + +#if !defined(__NR_get_kernel_syms) +#define __NR_get_kernel_syms 130 +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl 131 +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid 132 +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir 133 +#endif + +#if !defined(__NR_bdflush) +#define __NR_bdflush 134 +#endif + +#if !defined(__NR_sysfs) +#define __NR_sysfs 135 +#endif + +#if !defined(__NR_personality) +#define __NR_personality 136 +#endif + +#if !defined(__NR_afs_syscall) +#define __NR_afs_syscall 137 +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid 138 +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid 139 +#endif + +#if !defined(__NR__llseek) +#define __NR__llseek 140 +#endif + +#if !defined(__NR_getdents) +#define __NR_getdents 141 +#endif + +#if !defined(__NR__newselect) +#define __NR__newselect 142 +#endif + +#if !defined(__NR_flock) +#define __NR_flock 143 +#endif + +#if !defined(__NR_msync) +#define __NR_msync 144 +#endif + +#if !defined(__NR_readv) +#define __NR_readv 145 +#endif + +#if !defined(__NR_writev) +#define __NR_writev 146 +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid 147 +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync 148 +#endif + +#if !defined(__NR__sysctl) +#define __NR__sysctl 149 +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock 150 +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock 151 +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall 152 +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall 153 +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam 154 +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam 155 +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler 156 +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler 157 +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield 158 +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max 159 +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min 160 +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval 161 +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep 162 +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap 163 +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid 164 +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid 165 +#endif + +#if !defined(__NR_vm86) +#define __NR_vm86 166 +#endif + +#if !defined(__NR_query_module) +#define __NR_query_module 167 +#endif + +#if !defined(__NR_poll) +#define __NR_poll 168 +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl 169 +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid 170 +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid 171 +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl 172 +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn 173 +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction 174 +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask 175 +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending 176 +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait 177 +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo 178 +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend 179 +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 180 +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 181 +#endif + +#if !defined(__NR_chown) +#define __NR_chown 182 +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd 183 +#endif + +#if !defined(__NR_capget) +#define __NR_capget 184 +#endif + +#if !defined(__NR_capset) +#define __NR_capset 185 +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack 186 +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile 187 +#endif + +#if !defined(__NR_getpmsg) +#define __NR_getpmsg 188 +#endif + +#if !defined(__NR_putpmsg) +#define __NR_putpmsg 189 +#endif + +#if !defined(__NR_vfork) +#define __NR_vfork 190 +#endif + +#if !defined(__NR_ugetrlimit) +#define __NR_ugetrlimit 191 +#endif + +#if !defined(__NR_mmap2) +#define __NR_mmap2 192 +#endif + +#if !defined(__NR_truncate64) +#define __NR_truncate64 193 +#endif + +#if !defined(__NR_ftruncate64) +#define __NR_ftruncate64 194 +#endif + +#if !defined(__NR_stat64) +#define __NR_stat64 195 +#endif + +#if !defined(__NR_lstat64) +#define __NR_lstat64 196 +#endif + +#if !defined(__NR_fstat64) +#define __NR_fstat64 197 +#endif + +#if !defined(__NR_lchown32) +#define __NR_lchown32 198 +#endif + +#if !defined(__NR_getuid32) +#define __NR_getuid32 199 +#endif + +#if !defined(__NR_getgid32) +#define __NR_getgid32 200 +#endif + +#if !defined(__NR_geteuid32) +#define __NR_geteuid32 201 +#endif + +#if !defined(__NR_getegid32) +#define __NR_getegid32 202 +#endif + +#if !defined(__NR_setreuid32) +#define __NR_setreuid32 203 +#endif + +#if !defined(__NR_setregid32) +#define __NR_setregid32 204 +#endif + +#if !defined(__NR_getgroups32) +#define __NR_getgroups32 205 +#endif + +#if !defined(__NR_setgroups32) +#define __NR_setgroups32 206 +#endif + +#if !defined(__NR_fchown32) +#define __NR_fchown32 207 +#endif + +#if !defined(__NR_setresuid32) +#define __NR_setresuid32 208 +#endif + +#if !defined(__NR_getresuid32) +#define __NR_getresuid32 209 +#endif + +#if !defined(__NR_setresgid32) +#define __NR_setresgid32 210 +#endif + +#if !defined(__NR_getresgid32) +#define __NR_getresgid32 211 +#endif + +#if !defined(__NR_chown32) +#define __NR_chown32 212 +#endif + +#if !defined(__NR_setuid32) +#define __NR_setuid32 213 +#endif + +#if !defined(__NR_setgid32) +#define __NR_setgid32 214 +#endif + +#if !defined(__NR_setfsuid32) +#define __NR_setfsuid32 215 +#endif + +#if !defined(__NR_setfsgid32) +#define __NR_setfsgid32 216 +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root 217 +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore 218 +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise 219 +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 220 +#endif + +#if !defined(__NR_fcntl64) +#define __NR_fcntl64 221 +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid 224 +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead 225 +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr 226 +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr 227 +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr 228 +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr 229 +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr 230 +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr 231 +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr 232 +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr 233 +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr 234 +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr 235 +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr 236 +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr 237 +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill 238 +#endif + +#if !defined(__NR_sendfile64) +#define __NR_sendfile64 239 +#endif + +#if !defined(__NR_futex) +#define __NR_futex 240 +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity 241 +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity 242 +#endif + +#if !defined(__NR_set_thread_area) +#define __NR_set_thread_area 243 +#endif + +#if !defined(__NR_get_thread_area) +#define __NR_get_thread_area 244 +#endif + +#if !defined(__NR_io_setup) +#define __NR_io_setup 245 +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy 246 +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents 247 +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit 248 +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel 249 +#endif + +#if !defined(__NR_fadvise64) +#define __NR_fadvise64 250 +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group 252 +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie 253 +#endif + +#if !defined(__NR_epoll_create) +#define __NR_epoll_create 254 +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl 255 +#endif + +#if !defined(__NR_epoll_wait) +#define __NR_epoll_wait 256 +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages 257 +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address 258 +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create 259 +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime 260 +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime 261 +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun 262 +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete 263 +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime 264 +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime 265 +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres 266 +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep 267 +#endif + +#if !defined(__NR_statfs64) +#define __NR_statfs64 268 +#endif + +#if !defined(__NR_fstatfs64) +#define __NR_fstatfs64 269 +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill 270 +#endif + +#if !defined(__NR_utimes) +#define __NR_utimes 271 +#endif + +#if !defined(__NR_fadvise64_64) +#define __NR_fadvise64_64 272 +#endif + +#if !defined(__NR_vserver) +#define __NR_vserver 273 +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind 274 +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy 275 +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy 276 +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open 277 +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink 278 +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend 279 +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive 280 +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify 281 +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr 282 +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load 283 +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid 284 +#endif + +#if !defined(__NR_add_key) +#define __NR_add_key 286 +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key 287 +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl 288 +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set 289 +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get 290 +#endif + +#if !defined(__NR_inotify_init) +#define __NR_inotify_init 291 +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch 292 +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch 293 +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages 294 +#endif + +#if !defined(__NR_openat) +#define __NR_openat 295 +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat 296 +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat 297 +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat 298 +#endif + +#if !defined(__NR_futimesat) +#define __NR_futimesat 299 +#endif + +#if !defined(__NR_fstatat64) +#define __NR_fstatat64 300 +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat 301 +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat 302 +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat 303 +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat 304 +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat 305 +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat 306 +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat 307 +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 308 +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll 309 +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare 310 +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list 311 +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list 312 +#endif + +#if !defined(__NR_splice) +#define __NR_splice 313 +#endif + +#if !defined(__NR_sync_file_range) +#define __NR_sync_file_range 314 +#endif + +#if !defined(__NR_tee) +#define __NR_tee 315 +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice 316 +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages 317 +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu 318 +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait 319 +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat 320 +#endif + +#if !defined(__NR_signalfd) +#define __NR_signalfd 321 +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create 322 +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd 323 +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate 324 +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime 325 +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime 326 +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 327 +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 328 +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 329 +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 330 +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 331 +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 332 +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv 333 +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev 334 +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo 335 +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open 336 +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg 337 +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init 338 +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark 339 +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 340 +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at 341 +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at 342 +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime 343 +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs 344 +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg 345 +#endif + +#if !defined(__NR_setns) +#define __NR_setns 346 +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv 347 +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev 348 +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp 349 +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module 350 +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr 351 +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr 352 +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 353 +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp 354 +#endif + +#if !defined(__NR_getrandom) +#define __NR_getrandom 355 +#endif + +#if !defined(__NR_memfd_create) +#define __NR_memfd_create 356 +#endif + +#if !defined(__NR_bpf) +#define __NR_bpf 357 +#endif + +#if !defined(__NR_execveat) +#define __NR_execveat 358 +#endif + +#if !defined(__NR_socket) +#define __NR_socket 359 +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair 360 +#endif + +#if !defined(__NR_bind) +#define __NR_bind 361 +#endif + +#if !defined(__NR_connect) +#define __NR_connect 362 +#endif + +#if !defined(__NR_listen) +#define __NR_listen 363 +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 364 +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt 365 +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt 366 +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname 367 +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername 368 +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto 369 +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg 370 +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom 371 +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg 372 +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown 373 +#endif + +#if !defined(__NR_userfaultfd) +#define __NR_userfaultfd 374 +#endif + +#if !defined(__NR_membarrier) +#define __NR_membarrier 375 +#endif + +#if !defined(__NR_mlock2) +#define __NR_mlock2 376 +#endif + +#if !defined(__NR_copy_file_range) +#define __NR_copy_file_range 377 +#endif + +#if !defined(__NR_preadv2) +#define __NR_preadv2 378 +#endif + +#if !defined(__NR_pwritev2) +#define __NR_pwritev2 379 +#endif + +#if !defined(__NR_pkey_mprotect) +#define __NR_pkey_mprotect 380 +#endif + +#if !defined(__NR_pkey_alloc) +#define __NR_pkey_alloc 381 +#endif + +#if !defined(__NR_pkey_free) +#define __NR_pkey_free 382 +#endif + +#if !defined(__NR_statx) +#define __NR_statx 383 +#endif + +#if !defined(__NR_arch_prctl) +#define __NR_arch_prctl 384 +#endif + +#if !defined(__NR_io_pgetevents) +#define __NR_io_pgetevents 385 +#endif + +#if !defined(__NR_rseq) +#define __NR_rseq 386 +#endif + +#if !defined(__NR_semget) +#define __NR_semget 393 +#endif + +#if !defined(__NR_semctl) +#define __NR_semctl 394 +#endif + +#if !defined(__NR_shmget) +#define __NR_shmget 395 +#endif + +#if !defined(__NR_shmctl) +#define __NR_shmctl 396 +#endif + +#if !defined(__NR_shmat) +#define __NR_shmat 397 +#endif + +#if !defined(__NR_shmdt) +#define __NR_shmdt 398 +#endif + +#if !defined(__NR_msgget) +#define __NR_msgget 399 +#endif + +#if !defined(__NR_msgsnd) +#define __NR_msgsnd 400 +#endif + +#if !defined(__NR_msgrcv) +#define __NR_msgrcv 401 +#endif + +#if !defined(__NR_msgctl) +#define __NR_msgctl 402 +#endif + +#if !defined(__NR_clock_gettime64) +#define __NR_clock_gettime64 403 +#endif + +#if !defined(__NR_clock_settime64) +#define __NR_clock_settime64 404 +#endif + +#if !defined(__NR_clock_adjtime64) +#define __NR_clock_adjtime64 405 +#endif + +#if !defined(__NR_clock_getres_time64) +#define __NR_clock_getres_time64 406 +#endif + +#if !defined(__NR_clock_nanosleep_time64) +#define __NR_clock_nanosleep_time64 407 +#endif + +#if !defined(__NR_timer_gettime64) +#define __NR_timer_gettime64 408 +#endif + +#if !defined(__NR_timer_settime64) +#define __NR_timer_settime64 409 +#endif + +#if !defined(__NR_timerfd_gettime64) +#define __NR_timerfd_gettime64 410 +#endif + +#if !defined(__NR_timerfd_settime64) +#define __NR_timerfd_settime64 411 +#endif + +#if !defined(__NR_utimensat_time64) +#define __NR_utimensat_time64 412 +#endif + +#if !defined(__NR_pselect6_time64) +#define __NR_pselect6_time64 413 +#endif + +#if !defined(__NR_ppoll_time64) +#define __NR_ppoll_time64 414 +#endif + +#if !defined(__NR_io_pgetevents_time64) +#define __NR_io_pgetevents_time64 416 +#endif + +#if !defined(__NR_recvmmsg_time64) +#define __NR_recvmmsg_time64 417 +#endif + +#if !defined(__NR_mq_timedsend_time64) +#define __NR_mq_timedsend_time64 418 +#endif + +#if !defined(__NR_mq_timedreceive_time64) +#define __NR_mq_timedreceive_time64 419 +#endif + +#if !defined(__NR_semtimedop_time64) +#define __NR_semtimedop_time64 420 +#endif + +#if !defined(__NR_rt_sigtimedwait_time64) +#define __NR_rt_sigtimedwait_time64 421 +#endif + +#if !defined(__NR_futex_time64) +#define __NR_futex_time64 422 +#endif + +#if !defined(__NR_sched_rr_get_interval_time64) +#define __NR_sched_rr_get_interval_time64 423 +#endif + +#if !defined(__NR_pidfd_send_signal) +#define __NR_pidfd_send_signal 424 +#endif + +#if !defined(__NR_io_uring_setup) +#define __NR_io_uring_setup 425 +#endif + +#if !defined(__NR_io_uring_enter) +#define __NR_io_uring_enter 426 +#endif + +#if !defined(__NR_io_uring_register) +#define __NR_io_uring_register 427 +#endif + +#if !defined(__NR_open_tree) +#define __NR_open_tree 428 +#endif + +#if !defined(__NR_move_mount) +#define __NR_move_mount 429 +#endif + +#if !defined(__NR_fsopen) +#define __NR_fsopen 430 +#endif + +#if !defined(__NR_fsconfig) +#define __NR_fsconfig 431 +#endif + +#if !defined(__NR_fsmount) +#define __NR_fsmount 432 +#endif + +#if !defined(__NR_fspick) +#define __NR_fspick 433 +#endif + +#if !defined(__NR_pidfd_open) +#define __NR_pidfd_open 434 +#endif + +#if !defined(__NR_clone3) +#define __NR_clone3 435 +#endif + +#if !defined(__NR_openat2) +#define __NR_openat2 437 +#endif + +#if !defined(__NR_pidfd_getfd) +#define __NR_pidfd_getfd 438 +#endif + +#if !defined(__NR_faccessat2) +#define __NR_faccessat2 439 +#endif + + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_X86_32_LINUX_SYSCALLS_H_ diff --git a/security/sandbox/chromium/sandbox/linux/system_headers/x86_64_linux_syscalls.h b/security/sandbox/chromium/sandbox/linux/system_headers/x86_64_linux_syscalls.h new file mode 100644 index 0000000000..ab51703464 --- /dev/null +++ b/security/sandbox/chromium/sandbox/linux/system_headers/x86_64_linux_syscalls.h @@ -0,0 +1,1418 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* Constructed by running: + * curl -vsSL https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/arch/x86/entry/syscalls/syscall_64.tbl?h=v5.8 + * | grep -vE '^#|^$' + * | awk '{ if ($2 != "x32") { print "#if !defined(__NR_" $3 ")\n#define __NR_" $3 " " $1 "\n#endif\n"; } }' + * */ + +#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_X86_64_LINUX_SYSCALLS_H_ +#define SANDBOX_LINUX_SYSTEM_HEADERS_X86_64_LINUX_SYSCALLS_H_ + +#if !defined(__x86_64__) +#error "Including header on wrong architecture" +#endif + +#if !defined(__NR_read) +#define __NR_read 0 +#endif + +#if !defined(__NR_write) +#define __NR_write 1 +#endif + +#if !defined(__NR_open) +#define __NR_open 2 +#endif + +#if !defined(__NR_close) +#define __NR_close 3 +#endif + +#if !defined(__NR_stat) +#define __NR_stat 4 +#endif + +#if !defined(__NR_fstat) +#define __NR_fstat 5 +#endif + +#if !defined(__NR_lstat) +#define __NR_lstat 6 +#endif + +#if !defined(__NR_poll) +#define __NR_poll 7 +#endif + +#if !defined(__NR_lseek) +#define __NR_lseek 8 +#endif + +#if !defined(__NR_mmap) +#define __NR_mmap 9 +#endif + +#if !defined(__NR_mprotect) +#define __NR_mprotect 10 +#endif + +#if !defined(__NR_munmap) +#define __NR_munmap 11 +#endif + +#if !defined(__NR_brk) +#define __NR_brk 12 +#endif + +#if !defined(__NR_rt_sigaction) +#define __NR_rt_sigaction 13 +#endif + +#if !defined(__NR_rt_sigprocmask) +#define __NR_rt_sigprocmask 14 +#endif + +#if !defined(__NR_rt_sigreturn) +#define __NR_rt_sigreturn 15 +#endif + +#if !defined(__NR_ioctl) +#define __NR_ioctl 16 +#endif + +#if !defined(__NR_pread64) +#define __NR_pread64 17 +#endif + +#if !defined(__NR_pwrite64) +#define __NR_pwrite64 18 +#endif + +#if !defined(__NR_readv) +#define __NR_readv 19 +#endif + +#if !defined(__NR_writev) +#define __NR_writev 20 +#endif + +#if !defined(__NR_access) +#define __NR_access 21 +#endif + +#if !defined(__NR_pipe) +#define __NR_pipe 22 +#endif + +#if !defined(__NR_select) +#define __NR_select 23 +#endif + +#if !defined(__NR_sched_yield) +#define __NR_sched_yield 24 +#endif + +#if !defined(__NR_mremap) +#define __NR_mremap 25 +#endif + +#if !defined(__NR_msync) +#define __NR_msync 26 +#endif + +#if !defined(__NR_mincore) +#define __NR_mincore 27 +#endif + +#if !defined(__NR_madvise) +#define __NR_madvise 28 +#endif + +#if !defined(__NR_shmget) +#define __NR_shmget 29 +#endif + +#if !defined(__NR_shmat) +#define __NR_shmat 30 +#endif + +#if !defined(__NR_shmctl) +#define __NR_shmctl 31 +#endif + +#if !defined(__NR_dup) +#define __NR_dup 32 +#endif + +#if !defined(__NR_dup2) +#define __NR_dup2 33 +#endif + +#if !defined(__NR_pause) +#define __NR_pause 34 +#endif + +#if !defined(__NR_nanosleep) +#define __NR_nanosleep 35 +#endif + +#if !defined(__NR_getitimer) +#define __NR_getitimer 36 +#endif + +#if !defined(__NR_alarm) +#define __NR_alarm 37 +#endif + +#if !defined(__NR_setitimer) +#define __NR_setitimer 38 +#endif + +#if !defined(__NR_getpid) +#define __NR_getpid 39 +#endif + +#if !defined(__NR_sendfile) +#define __NR_sendfile 40 +#endif + +#if !defined(__NR_socket) +#define __NR_socket 41 +#endif + +#if !defined(__NR_connect) +#define __NR_connect 42 +#endif + +#if !defined(__NR_accept) +#define __NR_accept 43 +#endif + +#if !defined(__NR_sendto) +#define __NR_sendto 44 +#endif + +#if !defined(__NR_recvfrom) +#define __NR_recvfrom 45 +#endif + +#if !defined(__NR_sendmsg) +#define __NR_sendmsg 46 +#endif + +#if !defined(__NR_recvmsg) +#define __NR_recvmsg 47 +#endif + +#if !defined(__NR_shutdown) +#define __NR_shutdown 48 +#endif + +#if !defined(__NR_bind) +#define __NR_bind 49 +#endif + +#if !defined(__NR_listen) +#define __NR_listen 50 +#endif + +#if !defined(__NR_getsockname) +#define __NR_getsockname 51 +#endif + +#if !defined(__NR_getpeername) +#define __NR_getpeername 52 +#endif + +#if !defined(__NR_socketpair) +#define __NR_socketpair 53 +#endif + +#if !defined(__NR_setsockopt) +#define __NR_setsockopt 54 +#endif + +#if !defined(__NR_getsockopt) +#define __NR_getsockopt 55 +#endif + +#if !defined(__NR_clone) +#define __NR_clone 56 +#endif + +#if !defined(__NR_fork) +#define __NR_fork 57 +#endif + +#if !defined(__NR_vfork) +#define __NR_vfork 58 +#endif + +#if !defined(__NR_execve) +#define __NR_execve 59 +#endif + +#if !defined(__NR_exit) +#define __NR_exit 60 +#endif + +#if !defined(__NR_wait4) +#define __NR_wait4 61 +#endif + +#if !defined(__NR_kill) +#define __NR_kill 62 +#endif + +#if !defined(__NR_uname) +#define __NR_uname 63 +#endif + +#if !defined(__NR_semget) +#define __NR_semget 64 +#endif + +#if !defined(__NR_semop) +#define __NR_semop 65 +#endif + +#if !defined(__NR_semctl) +#define __NR_semctl 66 +#endif + +#if !defined(__NR_shmdt) +#define __NR_shmdt 67 +#endif + +#if !defined(__NR_msgget) +#define __NR_msgget 68 +#endif + +#if !defined(__NR_msgsnd) +#define __NR_msgsnd 69 +#endif + +#if !defined(__NR_msgrcv) +#define __NR_msgrcv 70 +#endif + +#if !defined(__NR_msgctl) +#define __NR_msgctl 71 +#endif + +#if !defined(__NR_fcntl) +#define __NR_fcntl 72 +#endif + +#if !defined(__NR_flock) +#define __NR_flock 73 +#endif + +#if !defined(__NR_fsync) +#define __NR_fsync 74 +#endif + +#if !defined(__NR_fdatasync) +#define __NR_fdatasync 75 +#endif + +#if !defined(__NR_truncate) +#define __NR_truncate 76 +#endif + +#if !defined(__NR_ftruncate) +#define __NR_ftruncate 77 +#endif + +#if !defined(__NR_getdents) +#define __NR_getdents 78 +#endif + +#if !defined(__NR_getcwd) +#define __NR_getcwd 79 +#endif + +#if !defined(__NR_chdir) +#define __NR_chdir 80 +#endif + +#if !defined(__NR_fchdir) +#define __NR_fchdir 81 +#endif + +#if !defined(__NR_rename) +#define __NR_rename 82 +#endif + +#if !defined(__NR_mkdir) +#define __NR_mkdir 83 +#endif + +#if !defined(__NR_rmdir) +#define __NR_rmdir 84 +#endif + +#if !defined(__NR_creat) +#define __NR_creat 85 +#endif + +#if !defined(__NR_link) +#define __NR_link 86 +#endif + +#if !defined(__NR_unlink) +#define __NR_unlink 87 +#endif + +#if !defined(__NR_symlink) +#define __NR_symlink 88 +#endif + +#if !defined(__NR_readlink) +#define __NR_readlink 89 +#endif + +#if !defined(__NR_chmod) +#define __NR_chmod 90 +#endif + +#if !defined(__NR_fchmod) +#define __NR_fchmod 91 +#endif + +#if !defined(__NR_chown) +#define __NR_chown 92 +#endif + +#if !defined(__NR_fchown) +#define __NR_fchown 93 +#endif + +#if !defined(__NR_lchown) +#define __NR_lchown 94 +#endif + +#if !defined(__NR_umask) +#define __NR_umask 95 +#endif + +#if !defined(__NR_gettimeofday) +#define __NR_gettimeofday 96 +#endif + +#if !defined(__NR_getrlimit) +#define __NR_getrlimit 97 +#endif + +#if !defined(__NR_getrusage) +#define __NR_getrusage 98 +#endif + +#if !defined(__NR_sysinfo) +#define __NR_sysinfo 99 +#endif + +#if !defined(__NR_times) +#define __NR_times 100 +#endif + +#if !defined(__NR_ptrace) +#define __NR_ptrace 101 +#endif + +#if !defined(__NR_getuid) +#define __NR_getuid 102 +#endif + +#if !defined(__NR_syslog) +#define __NR_syslog 103 +#endif + +#if !defined(__NR_getgid) +#define __NR_getgid 104 +#endif + +#if !defined(__NR_setuid) +#define __NR_setuid 105 +#endif + +#if !defined(__NR_setgid) +#define __NR_setgid 106 +#endif + +#if !defined(__NR_geteuid) +#define __NR_geteuid 107 +#endif + +#if !defined(__NR_getegid) +#define __NR_getegid 108 +#endif + +#if !defined(__NR_setpgid) +#define __NR_setpgid 109 +#endif + +#if !defined(__NR_getppid) +#define __NR_getppid 110 +#endif + +#if !defined(__NR_getpgrp) +#define __NR_getpgrp 111 +#endif + +#if !defined(__NR_setsid) +#define __NR_setsid 112 +#endif + +#if !defined(__NR_setreuid) +#define __NR_setreuid 113 +#endif + +#if !defined(__NR_setregid) +#define __NR_setregid 114 +#endif + +#if !defined(__NR_getgroups) +#define __NR_getgroups 115 +#endif + +#if !defined(__NR_setgroups) +#define __NR_setgroups 116 +#endif + +#if !defined(__NR_setresuid) +#define __NR_setresuid 117 +#endif + +#if !defined(__NR_getresuid) +#define __NR_getresuid 118 +#endif + +#if !defined(__NR_setresgid) +#define __NR_setresgid 119 +#endif + +#if !defined(__NR_getresgid) +#define __NR_getresgid 120 +#endif + +#if !defined(__NR_getpgid) +#define __NR_getpgid 121 +#endif + +#if !defined(__NR_setfsuid) +#define __NR_setfsuid 122 +#endif + +#if !defined(__NR_setfsgid) +#define __NR_setfsgid 123 +#endif + +#if !defined(__NR_getsid) +#define __NR_getsid 124 +#endif + +#if !defined(__NR_capget) +#define __NR_capget 125 +#endif + +#if !defined(__NR_capset) +#define __NR_capset 126 +#endif + +#if !defined(__NR_rt_sigpending) +#define __NR_rt_sigpending 127 +#endif + +#if !defined(__NR_rt_sigtimedwait) +#define __NR_rt_sigtimedwait 128 +#endif + +#if !defined(__NR_rt_sigqueueinfo) +#define __NR_rt_sigqueueinfo 129 +#endif + +#if !defined(__NR_rt_sigsuspend) +#define __NR_rt_sigsuspend 130 +#endif + +#if !defined(__NR_sigaltstack) +#define __NR_sigaltstack 131 +#endif + +#if !defined(__NR_utime) +#define __NR_utime 132 +#endif + +#if !defined(__NR_mknod) +#define __NR_mknod 133 +#endif + +#if !defined(__NR_uselib) +#define __NR_uselib 134 +#endif + +#if !defined(__NR_personality) +#define __NR_personality 135 +#endif + +#if !defined(__NR_ustat) +#define __NR_ustat 136 +#endif + +#if !defined(__NR_statfs) +#define __NR_statfs 137 +#endif + +#if !defined(__NR_fstatfs) +#define __NR_fstatfs 138 +#endif + +#if !defined(__NR_sysfs) +#define __NR_sysfs 139 +#endif + +#if !defined(__NR_getpriority) +#define __NR_getpriority 140 +#endif + +#if !defined(__NR_setpriority) +#define __NR_setpriority 141 +#endif + +#if !defined(__NR_sched_setparam) +#define __NR_sched_setparam 142 +#endif + +#if !defined(__NR_sched_getparam) +#define __NR_sched_getparam 143 +#endif + +#if !defined(__NR_sched_setscheduler) +#define __NR_sched_setscheduler 144 +#endif + +#if !defined(__NR_sched_getscheduler) +#define __NR_sched_getscheduler 145 +#endif + +#if !defined(__NR_sched_get_priority_max) +#define __NR_sched_get_priority_max 146 +#endif + +#if !defined(__NR_sched_get_priority_min) +#define __NR_sched_get_priority_min 147 +#endif + +#if !defined(__NR_sched_rr_get_interval) +#define __NR_sched_rr_get_interval 148 +#endif + +#if !defined(__NR_mlock) +#define __NR_mlock 149 +#endif + +#if !defined(__NR_munlock) +#define __NR_munlock 150 +#endif + +#if !defined(__NR_mlockall) +#define __NR_mlockall 151 +#endif + +#if !defined(__NR_munlockall) +#define __NR_munlockall 152 +#endif + +#if !defined(__NR_vhangup) +#define __NR_vhangup 153 +#endif + +#if !defined(__NR_modify_ldt) +#define __NR_modify_ldt 154 +#endif + +#if !defined(__NR_pivot_root) +#define __NR_pivot_root 155 +#endif + +#if !defined(__NR__sysctl) +#define __NR__sysctl 156 +#endif + +#if !defined(__NR_prctl) +#define __NR_prctl 157 +#endif + +#if !defined(__NR_arch_prctl) +#define __NR_arch_prctl 158 +#endif + +#if !defined(__NR_adjtimex) +#define __NR_adjtimex 159 +#endif + +#if !defined(__NR_setrlimit) +#define __NR_setrlimit 160 +#endif + +#if !defined(__NR_chroot) +#define __NR_chroot 161 +#endif + +#if !defined(__NR_sync) +#define __NR_sync 162 +#endif + +#if !defined(__NR_acct) +#define __NR_acct 163 +#endif + +#if !defined(__NR_settimeofday) +#define __NR_settimeofday 164 +#endif + +#if !defined(__NR_mount) +#define __NR_mount 165 +#endif + +#if !defined(__NR_umount2) +#define __NR_umount2 166 +#endif + +#if !defined(__NR_swapon) +#define __NR_swapon 167 +#endif + +#if !defined(__NR_swapoff) +#define __NR_swapoff 168 +#endif + +#if !defined(__NR_reboot) +#define __NR_reboot 169 +#endif + +#if !defined(__NR_sethostname) +#define __NR_sethostname 170 +#endif + +#if !defined(__NR_setdomainname) +#define __NR_setdomainname 171 +#endif + +#if !defined(__NR_iopl) +#define __NR_iopl 172 +#endif + +#if !defined(__NR_ioperm) +#define __NR_ioperm 173 +#endif + +#if !defined(__NR_create_module) +#define __NR_create_module 174 +#endif + +#if !defined(__NR_init_module) +#define __NR_init_module 175 +#endif + +#if !defined(__NR_delete_module) +#define __NR_delete_module 176 +#endif + +#if !defined(__NR_get_kernel_syms) +#define __NR_get_kernel_syms 177 +#endif + +#if !defined(__NR_query_module) +#define __NR_query_module 178 +#endif + +#if !defined(__NR_quotactl) +#define __NR_quotactl 179 +#endif + +#if !defined(__NR_nfsservctl) +#define __NR_nfsservctl 180 +#endif + +#if !defined(__NR_getpmsg) +#define __NR_getpmsg 181 +#endif + +#if !defined(__NR_putpmsg) +#define __NR_putpmsg 182 +#endif + +#if !defined(__NR_afs_syscall) +#define __NR_afs_syscall 183 +#endif + +#if !defined(__NR_tuxcall) +#define __NR_tuxcall 184 +#endif + +#if !defined(__NR_security) +#define __NR_security 185 +#endif + +#if !defined(__NR_gettid) +#define __NR_gettid 186 +#endif + +#if !defined(__NR_readahead) +#define __NR_readahead 187 +#endif + +#if !defined(__NR_setxattr) +#define __NR_setxattr 188 +#endif + +#if !defined(__NR_lsetxattr) +#define __NR_lsetxattr 189 +#endif + +#if !defined(__NR_fsetxattr) +#define __NR_fsetxattr 190 +#endif + +#if !defined(__NR_getxattr) +#define __NR_getxattr 191 +#endif + +#if !defined(__NR_lgetxattr) +#define __NR_lgetxattr 192 +#endif + +#if !defined(__NR_fgetxattr) +#define __NR_fgetxattr 193 +#endif + +#if !defined(__NR_listxattr) +#define __NR_listxattr 194 +#endif + +#if !defined(__NR_llistxattr) +#define __NR_llistxattr 195 +#endif + +#if !defined(__NR_flistxattr) +#define __NR_flistxattr 196 +#endif + +#if !defined(__NR_removexattr) +#define __NR_removexattr 197 +#endif + +#if !defined(__NR_lremovexattr) +#define __NR_lremovexattr 198 +#endif + +#if !defined(__NR_fremovexattr) +#define __NR_fremovexattr 199 +#endif + +#if !defined(__NR_tkill) +#define __NR_tkill 200 +#endif + +#if !defined(__NR_time) +#define __NR_time 201 +#endif + +#if !defined(__NR_futex) +#define __NR_futex 202 +#endif + +#if !defined(__NR_sched_setaffinity) +#define __NR_sched_setaffinity 203 +#endif + +#if !defined(__NR_sched_getaffinity) +#define __NR_sched_getaffinity 204 +#endif + +#if !defined(__NR_set_thread_area) +#define __NR_set_thread_area 205 +#endif + +#if !defined(__NR_io_setup) +#define __NR_io_setup 206 +#endif + +#if !defined(__NR_io_destroy) +#define __NR_io_destroy 207 +#endif + +#if !defined(__NR_io_getevents) +#define __NR_io_getevents 208 +#endif + +#if !defined(__NR_io_submit) +#define __NR_io_submit 209 +#endif + +#if !defined(__NR_io_cancel) +#define __NR_io_cancel 210 +#endif + +#if !defined(__NR_get_thread_area) +#define __NR_get_thread_area 211 +#endif + +#if !defined(__NR_lookup_dcookie) +#define __NR_lookup_dcookie 212 +#endif + +#if !defined(__NR_epoll_create) +#define __NR_epoll_create 213 +#endif + +#if !defined(__NR_epoll_ctl_old) +#define __NR_epoll_ctl_old 214 +#endif + +#if !defined(__NR_epoll_wait_old) +#define __NR_epoll_wait_old 215 +#endif + +#if !defined(__NR_remap_file_pages) +#define __NR_remap_file_pages 216 +#endif + +#if !defined(__NR_getdents64) +#define __NR_getdents64 217 +#endif + +#if !defined(__NR_set_tid_address) +#define __NR_set_tid_address 218 +#endif + +#if !defined(__NR_restart_syscall) +#define __NR_restart_syscall 219 +#endif + +#if !defined(__NR_semtimedop) +#define __NR_semtimedop 220 +#endif + +#if !defined(__NR_fadvise64) +#define __NR_fadvise64 221 +#endif + +#if !defined(__NR_timer_create) +#define __NR_timer_create 222 +#endif + +#if !defined(__NR_timer_settime) +#define __NR_timer_settime 223 +#endif + +#if !defined(__NR_timer_gettime) +#define __NR_timer_gettime 224 +#endif + +#if !defined(__NR_timer_getoverrun) +#define __NR_timer_getoverrun 225 +#endif + +#if !defined(__NR_timer_delete) +#define __NR_timer_delete 226 +#endif + +#if !defined(__NR_clock_settime) +#define __NR_clock_settime 227 +#endif + +#if !defined(__NR_clock_gettime) +#define __NR_clock_gettime 228 +#endif + +#if !defined(__NR_clock_getres) +#define __NR_clock_getres 229 +#endif + +#if !defined(__NR_clock_nanosleep) +#define __NR_clock_nanosleep 230 +#endif + +#if !defined(__NR_exit_group) +#define __NR_exit_group 231 +#endif + +#if !defined(__NR_epoll_wait) +#define __NR_epoll_wait 232 +#endif + +#if !defined(__NR_epoll_ctl) +#define __NR_epoll_ctl 233 +#endif + +#if !defined(__NR_tgkill) +#define __NR_tgkill 234 +#endif + +#if !defined(__NR_utimes) +#define __NR_utimes 235 +#endif + +#if !defined(__NR_vserver) +#define __NR_vserver 236 +#endif + +#if !defined(__NR_mbind) +#define __NR_mbind 237 +#endif + +#if !defined(__NR_set_mempolicy) +#define __NR_set_mempolicy 238 +#endif + +#if !defined(__NR_get_mempolicy) +#define __NR_get_mempolicy 239 +#endif + +#if !defined(__NR_mq_open) +#define __NR_mq_open 240 +#endif + +#if !defined(__NR_mq_unlink) +#define __NR_mq_unlink 241 +#endif + +#if !defined(__NR_mq_timedsend) +#define __NR_mq_timedsend 242 +#endif + +#if !defined(__NR_mq_timedreceive) +#define __NR_mq_timedreceive 243 +#endif + +#if !defined(__NR_mq_notify) +#define __NR_mq_notify 244 +#endif + +#if !defined(__NR_mq_getsetattr) +#define __NR_mq_getsetattr 245 +#endif + +#if !defined(__NR_kexec_load) +#define __NR_kexec_load 246 +#endif + +#if !defined(__NR_waitid) +#define __NR_waitid 247 +#endif + +#if !defined(__NR_add_key) +#define __NR_add_key 248 +#endif + +#if !defined(__NR_request_key) +#define __NR_request_key 249 +#endif + +#if !defined(__NR_keyctl) +#define __NR_keyctl 250 +#endif + +#if !defined(__NR_ioprio_set) +#define __NR_ioprio_set 251 +#endif + +#if !defined(__NR_ioprio_get) +#define __NR_ioprio_get 252 +#endif + +#if !defined(__NR_inotify_init) +#define __NR_inotify_init 253 +#endif + +#if !defined(__NR_inotify_add_watch) +#define __NR_inotify_add_watch 254 +#endif + +#if !defined(__NR_inotify_rm_watch) +#define __NR_inotify_rm_watch 255 +#endif + +#if !defined(__NR_migrate_pages) +#define __NR_migrate_pages 256 +#endif + +#if !defined(__NR_openat) +#define __NR_openat 257 +#endif + +#if !defined(__NR_mkdirat) +#define __NR_mkdirat 258 +#endif + +#if !defined(__NR_mknodat) +#define __NR_mknodat 259 +#endif + +#if !defined(__NR_fchownat) +#define __NR_fchownat 260 +#endif + +#if !defined(__NR_futimesat) +#define __NR_futimesat 261 +#endif + +#if !defined(__NR_newfstatat) +#define __NR_newfstatat 262 +#endif + +#if !defined(__NR_unlinkat) +#define __NR_unlinkat 263 +#endif + +#if !defined(__NR_renameat) +#define __NR_renameat 264 +#endif + +#if !defined(__NR_linkat) +#define __NR_linkat 265 +#endif + +#if !defined(__NR_symlinkat) +#define __NR_symlinkat 266 +#endif + +#if !defined(__NR_readlinkat) +#define __NR_readlinkat 267 +#endif + +#if !defined(__NR_fchmodat) +#define __NR_fchmodat 268 +#endif + +#if !defined(__NR_faccessat) +#define __NR_faccessat 269 +#endif + +#if !defined(__NR_pselect6) +#define __NR_pselect6 270 +#endif + +#if !defined(__NR_ppoll) +#define __NR_ppoll 271 +#endif + +#if !defined(__NR_unshare) +#define __NR_unshare 272 +#endif + +#if !defined(__NR_set_robust_list) +#define __NR_set_robust_list 273 +#endif + +#if !defined(__NR_get_robust_list) +#define __NR_get_robust_list 274 +#endif + +#if !defined(__NR_splice) +#define __NR_splice 275 +#endif + +#if !defined(__NR_tee) +#define __NR_tee 276 +#endif + +#if !defined(__NR_sync_file_range) +#define __NR_sync_file_range 277 +#endif + +#if !defined(__NR_vmsplice) +#define __NR_vmsplice 278 +#endif + +#if !defined(__NR_move_pages) +#define __NR_move_pages 279 +#endif + +#if !defined(__NR_utimensat) +#define __NR_utimensat 280 +#endif + +#if !defined(__NR_epoll_pwait) +#define __NR_epoll_pwait 281 +#endif + +#if !defined(__NR_signalfd) +#define __NR_signalfd 282 +#endif + +#if !defined(__NR_timerfd_create) +#define __NR_timerfd_create 283 +#endif + +#if !defined(__NR_eventfd) +#define __NR_eventfd 284 +#endif + +#if !defined(__NR_fallocate) +#define __NR_fallocate 285 +#endif + +#if !defined(__NR_timerfd_settime) +#define __NR_timerfd_settime 286 +#endif + +#if !defined(__NR_timerfd_gettime) +#define __NR_timerfd_gettime 287 +#endif + +#if !defined(__NR_accept4) +#define __NR_accept4 288 +#endif + +#if !defined(__NR_signalfd4) +#define __NR_signalfd4 289 +#endif + +#if !defined(__NR_eventfd2) +#define __NR_eventfd2 290 +#endif + +#if !defined(__NR_epoll_create1) +#define __NR_epoll_create1 291 +#endif + +#if !defined(__NR_dup3) +#define __NR_dup3 292 +#endif + +#if !defined(__NR_pipe2) +#define __NR_pipe2 293 +#endif + +#if !defined(__NR_inotify_init1) +#define __NR_inotify_init1 294 +#endif + +#if !defined(__NR_preadv) +#define __NR_preadv 295 +#endif + +#if !defined(__NR_pwritev) +#define __NR_pwritev 296 +#endif + +#if !defined(__NR_rt_tgsigqueueinfo) +#define __NR_rt_tgsigqueueinfo 297 +#endif + +#if !defined(__NR_perf_event_open) +#define __NR_perf_event_open 298 +#endif + +#if !defined(__NR_recvmmsg) +#define __NR_recvmmsg 299 +#endif + +#if !defined(__NR_fanotify_init) +#define __NR_fanotify_init 300 +#endif + +#if !defined(__NR_fanotify_mark) +#define __NR_fanotify_mark 301 +#endif + +#if !defined(__NR_prlimit64) +#define __NR_prlimit64 302 +#endif + +#if !defined(__NR_name_to_handle_at) +#define __NR_name_to_handle_at 303 +#endif + +#if !defined(__NR_open_by_handle_at) +#define __NR_open_by_handle_at 304 +#endif + +#if !defined(__NR_clock_adjtime) +#define __NR_clock_adjtime 305 +#endif + +#if !defined(__NR_syncfs) +#define __NR_syncfs 306 +#endif + +#if !defined(__NR_sendmmsg) +#define __NR_sendmmsg 307 +#endif + +#if !defined(__NR_setns) +#define __NR_setns 308 +#endif + +#if !defined(__NR_getcpu) +#define __NR_getcpu 309 +#endif + +#if !defined(__NR_process_vm_readv) +#define __NR_process_vm_readv 310 +#endif + +#if !defined(__NR_process_vm_writev) +#define __NR_process_vm_writev 311 +#endif + +#if !defined(__NR_kcmp) +#define __NR_kcmp 312 +#endif + +#if !defined(__NR_finit_module) +#define __NR_finit_module 313 +#endif + +#if !defined(__NR_sched_setattr) +#define __NR_sched_setattr 314 +#endif + +#if !defined(__NR_sched_getattr) +#define __NR_sched_getattr 315 +#endif + +#if !defined(__NR_renameat2) +#define __NR_renameat2 316 +#endif + +#if !defined(__NR_seccomp) +#define __NR_seccomp 317 +#endif + +#if !defined(__NR_getrandom) +#define __NR_getrandom 318 +#endif + +#if !defined(__NR_memfd_create) +#define __NR_memfd_create 319 +#endif + +#if !defined(__NR_kexec_file_load) +#define __NR_kexec_file_load 320 +#endif + +#if !defined(__NR_bpf) +#define __NR_bpf 321 +#endif + +#if !defined(__NR_execveat) +#define __NR_execveat 322 +#endif + +#if !defined(__NR_userfaultfd) +#define __NR_userfaultfd 323 +#endif + +#if !defined(__NR_membarrier) +#define __NR_membarrier 324 +#endif + +#if !defined(__NR_mlock2) +#define __NR_mlock2 325 +#endif + +#if !defined(__NR_copy_file_range) +#define __NR_copy_file_range 326 +#endif + +#if !defined(__NR_preadv2) +#define __NR_preadv2 327 +#endif + +#if !defined(__NR_pwritev2) +#define __NR_pwritev2 328 +#endif + +#if !defined(__NR_pkey_mprotect) +#define __NR_pkey_mprotect 329 +#endif + +#if !defined(__NR_pkey_alloc) +#define __NR_pkey_alloc 330 +#endif + +#if !defined(__NR_pkey_free) +#define __NR_pkey_free 331 +#endif + +#if !defined(__NR_statx) +#define __NR_statx 332 +#endif + +#if !defined(__NR_io_pgetevents) +#define __NR_io_pgetevents 333 +#endif + +#if !defined(__NR_rseq) +#define __NR_rseq 334 +#endif + +#if !defined(__NR_pidfd_send_signal) +#define __NR_pidfd_send_signal 424 +#endif + +#if !defined(__NR_io_uring_setup) +#define __NR_io_uring_setup 425 +#endif + +#if !defined(__NR_io_uring_enter) +#define __NR_io_uring_enter 426 +#endif + +#if !defined(__NR_io_uring_register) +#define __NR_io_uring_register 427 +#endif + +#if !defined(__NR_open_tree) +#define __NR_open_tree 428 +#endif + +#if !defined(__NR_move_mount) +#define __NR_move_mount 429 +#endif + +#if !defined(__NR_fsopen) +#define __NR_fsopen 430 +#endif + +#if !defined(__NR_fsconfig) +#define __NR_fsconfig 431 +#endif + +#if !defined(__NR_fsmount) +#define __NR_fsmount 432 +#endif + +#if !defined(__NR_fspick) +#define __NR_fspick 433 +#endif + +#if !defined(__NR_pidfd_open) +#define __NR_pidfd_open 434 +#endif + +#if !defined(__NR_clone3) +#define __NR_clone3 435 +#endif + +#if !defined(__NR_openat2) +#define __NR_openat2 437 +#endif + +#if !defined(__NR_pidfd_getfd) +#define __NR_pidfd_getfd 438 +#endif + +#if !defined(__NR_faccessat2) +#define __NR_faccessat2 439 +#endif + +#endif // SANDBOX_LINUX_SYSTEM_HEADERS_X86_64_LINUX_SYSCALLS_H_ diff --git a/security/sandbox/chromium/sandbox/sandbox_export.h b/security/sandbox/chromium/sandbox/sandbox_export.h new file mode 100644 index 0000000000..35d6a1ba26 --- /dev/null +++ b/security/sandbox/chromium/sandbox/sandbox_export.h @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SANDBOX_EXPORT_H_ +#define SANDBOX_SANDBOX_EXPORT_H_ + +#if defined(WIN32) +#error "sandbox_export.h does not support WIN32." +#endif + +#if defined(COMPONENT_BUILD) + +#if defined(SANDBOX_IMPLEMENTATION) +#define SANDBOX_EXPORT __attribute__((visibility("default"))) +#else +#define SANDBOX_EXPORT +#endif // defined(SANDBOX_IMPLEMENTATION) + +#else // defined(COMPONENT_BUILD) + +#define SANDBOX_EXPORT + +#endif // defined(COMPONENT_BUILD) + +#endif // SANDBOX_SANDBOX_EXPORT_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/acl.cc b/security/sandbox/chromium/sandbox/win/src/acl.cc new file mode 100644 index 0000000000..bd0b181833 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/acl.cc @@ -0,0 +1,171 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/acl.h" + +#include <aclapi.h> +#include <sddl.h> + +#include "base/logging.h" +#include "base/memory/free_deleter.h" + +namespace sandbox { + +bool GetDefaultDacl( + HANDLE token, + std::unique_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter>* default_dacl) { + if (!token) + return false; + + DCHECK(default_dacl); + + unsigned long length = 0; + ::GetTokenInformation(token, TokenDefaultDacl, nullptr, 0, &length); + if (length == 0) { + NOTREACHED(); + return false; + } + + TOKEN_DEFAULT_DACL* acl = + reinterpret_cast<TOKEN_DEFAULT_DACL*>(malloc(length)); + default_dacl->reset(acl); + + if (!::GetTokenInformation(token, TokenDefaultDacl, default_dacl->get(), + length, &length)) + return false; + + return true; +} + +bool AddSidToDacl(const Sid& sid, + ACL* old_dacl, + ACCESS_MODE access_mode, + ACCESS_MASK access, + ACL** new_dacl) { + EXPLICIT_ACCESS new_access = {0}; + new_access.grfAccessMode = access_mode; + new_access.grfAccessPermissions = access; + new_access.grfInheritance = NO_INHERITANCE; + + new_access.Trustee.pMultipleTrustee = nullptr; + new_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + new_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; + new_access.Trustee.ptstrName = reinterpret_cast<LPWSTR>(sid.GetPSID()); + + if (ERROR_SUCCESS != ::SetEntriesInAcl(1, &new_access, old_dacl, new_dacl)) + return false; + + return true; +} + +bool AddSidToDefaultDacl(HANDLE token, + const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access) { + if (!token) + return false; + + std::unique_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter> default_dacl; + if (!GetDefaultDacl(token, &default_dacl)) + return false; + + ACL* new_dacl = nullptr; + if (!AddSidToDacl(sid, default_dacl->DefaultDacl, access_mode, access, + &new_dacl)) + return false; + + TOKEN_DEFAULT_DACL new_token_dacl = {0}; + new_token_dacl.DefaultDacl = new_dacl; + + bool ret = ::SetTokenInformation(token, TokenDefaultDacl, &new_token_dacl, + sizeof(new_token_dacl)); + ::LocalFree(new_dacl); + return ret; +} + +bool RevokeLogonSidFromDefaultDacl(HANDLE token) { + DWORD size = sizeof(TOKEN_GROUPS) + SECURITY_MAX_SID_SIZE; + TOKEN_GROUPS* logon_sid = reinterpret_cast<TOKEN_GROUPS*>(malloc(size)); + + std::unique_ptr<TOKEN_GROUPS, base::FreeDeleter> logon_sid_ptr(logon_sid); + + if (!::GetTokenInformation(token, TokenLogonSid, logon_sid, size, &size)) { + // If no logon sid, there's nothing to revoke. + if (::GetLastError() == ERROR_NOT_FOUND) + return true; + return false; + } + if (logon_sid->GroupCount < 1) { + ::SetLastError(ERROR_INVALID_TOKEN); + return false; + } + return AddSidToDefaultDacl(token, + reinterpret_cast<SID*>(logon_sid->Groups[0].Sid), + REVOKE_ACCESS, 0); +} + +bool AddUserSidToDefaultDacl(HANDLE token, ACCESS_MASK access) { + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(malloc(size)); + + std::unique_ptr<TOKEN_USER, base::FreeDeleter> token_user_ptr(token_user); + + if (!::GetTokenInformation(token, TokenUser, token_user, size, &size)) + return false; + + return AddSidToDefaultDacl(token, + reinterpret_cast<SID*>(token_user->User.Sid), + GRANT_ACCESS, access); +} + +bool AddKnownSidToObject(HANDLE object, + SE_OBJECT_TYPE object_type, + const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access) { + PSECURITY_DESCRIPTOR descriptor = nullptr; + PACL old_dacl = nullptr; + PACL new_dacl = nullptr; + + if (ERROR_SUCCESS != + ::GetSecurityInfo(object, object_type, DACL_SECURITY_INFORMATION, nullptr, + nullptr, &old_dacl, nullptr, &descriptor)) + return false; + + if (!AddSidToDacl(sid, old_dacl, access_mode, access, &new_dacl)) { + ::LocalFree(descriptor); + return false; + } + + DWORD result = + ::SetSecurityInfo(object, object_type, DACL_SECURITY_INFORMATION, nullptr, + nullptr, new_dacl, nullptr); + + ::LocalFree(new_dacl); + ::LocalFree(descriptor); + + if (ERROR_SUCCESS != result) + return false; + + return true; +} + +bool ReplacePackageSidInDacl(HANDLE object, + SE_OBJECT_TYPE object_type, + const Sid& package_sid, + ACCESS_MASK access) { + if (!AddKnownSidToObject(object, object_type, package_sid, REVOKE_ACCESS, + 0)) { + return false; + } + + Sid any_package_sid(::WinBuiltinAnyPackageSid); + if (!AddKnownSidToObject(object, object_type, any_package_sid, GRANT_ACCESS, + access)) { + return false; + } + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/acl.h b/security/sandbox/chromium/sandbox/win/src/acl.h new file mode 100644 index 0000000000..194edb0988 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/acl.h @@ -0,0 +1,64 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_ACL_H_ +#define SANDBOX_SRC_ACL_H_ + +#include <accctrl.h> +#include <windows.h> + +#include <memory> + +#include "base/memory/free_deleter.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +// Returns the default dacl from the token passed in. +bool GetDefaultDacl( + HANDLE token, + std::unique_ptr<TOKEN_DEFAULT_DACL, base::FreeDeleter>* default_dacl); + +// Appends an ACE represented by |sid|, |access_mode|, and |access| to +// |old_dacl|. If the function succeeds, new_dacl contains the new dacl and +// must be freed using LocalFree. +bool AddSidToDacl(const Sid& sid, + ACL* old_dacl, + ACCESS_MODE access_mode, + ACCESS_MASK access, + ACL** new_dacl); + +// Adds an ACE represented by |sid| and |access| with |access_mode| to the +// default dacl present in the token. +bool AddSidToDefaultDacl(HANDLE token, + const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access); + +// Revokes access to the logon SID for the default dacl present in the token. +bool RevokeLogonSidFromDefaultDacl(HANDLE token); + +// Adds an ACE represented by the user sid and |access| to the default dacl +// present in the token. +bool AddUserSidToDefaultDacl(HANDLE token, ACCESS_MASK access); + +// Adds an ACE represented by |known_sid|, |access_mode|, and |access| to +// the dacl of the kernel object referenced by |object| and of |object_type|. +bool AddKnownSidToObject(HANDLE object, + SE_OBJECT_TYPE object_type, + const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access); + +// Replace package SID in DACL to the "any package" SID. It allows Low-IL +// tokens to open the object which is important for warm up when using renderer +// AppContainer. +bool ReplacePackageSidInDacl(HANDLE object, + SE_OBJECT_TYPE object_type, + const Sid& package_sid, + ACCESS_MASK access); + +} // namespace sandbox + +#endif // SANDBOX_SRC_ACL_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/app_container_profile.h b/security/sandbox/chromium/sandbox/win/src/app_container_profile.h new file mode 100644 index 0000000000..c95c68e552 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/app_container_profile.h @@ -0,0 +1,74 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ +#define SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ + +#include <windows.h> + +#include <accctrl.h> + +#include "base/files/file_path.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +class AppContainerProfile { + public: + // Increments the reference count of this object. The reference count must + // be incremented if this interface is given to another component. + virtual void AddRef() = 0; + + // Decrements the reference count of this object. When the reference count + // is zero the object is automatically destroyed. + // Indicates that the caller is done with this interface. After calling + // release no other method should be called. + virtual void Release() = 0; + + // Get a handle to a registry key for this package. + virtual bool GetRegistryLocation(REGSAM desired_access, + base::win::ScopedHandle* key) = 0; + + // Get a folder path to a location for this package. + virtual bool GetFolderPath(base::FilePath* file_path) = 0; + + // Get a pipe name usable by this AC. + virtual bool GetPipePath(const wchar_t* pipe_name, + base::FilePath* pipe_path) = 0; + + // Do an access check based on this profile for a named object. If method + // returns true then access_status reflects whether access was granted and + // granted_access gives the final access rights. The object_type can be one of + // SE_FILE_OBJECT, SE_REGISTRY_KEY, SE_REGISTRY_WOW64_32KEY. See + // ::GetNamedSecurityInfo for more information about how the enumeration is + // used and what format object_name needs to be. + virtual bool AccessCheck(const wchar_t* object_name, + SE_OBJECT_TYPE object_type, + DWORD desired_access, + DWORD* granted_access, + BOOL* access_status) = 0; + + // Adds a capability by name to this profile. + virtual bool AddCapability(const wchar_t* capability_name) = 0; + // Adds a capability from a known list. + virtual bool AddCapability(WellKnownCapabilities capability) = 0; + // Adds a capability from a SID + virtual bool AddCapabilitySddl(const wchar_t* sddl_sid) = 0; + + // Adds an impersonation capability by name to this profile. + virtual bool AddImpersonationCapability(const wchar_t* capability_name) = 0; + // Adds an impersonation capability from a known list. + virtual bool AddImpersonationCapability(WellKnownCapabilities capability) = 0; + // Adds an impersonation capability from a SID + virtual bool AddImpersonationCapabilitySddl(const wchar_t* sddl_sid) = 0; + + // Enable Low Privilege AC. + virtual void SetEnableLowPrivilegeAppContainer(bool enable) = 0; + virtual bool GetEnableLowPrivilegeAppContainer() = 0; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_APP_CONTAINER_PROFILE_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.cc b/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.cc new file mode 100644 index 0000000000..e388a9978e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.cc @@ -0,0 +1,337 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <memory> + +#include <aclapi.h> +#include <userenv.h> + +#include "base/strings/stringprintf.h" +#if !defined(MOZ_SANDBOX) +#include "base/win/scoped_co_mem.h" +#endif +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/app_container_profile_base.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +typedef decltype(::CreateAppContainerProfile) CreateAppContainerProfileFunc; + +typedef decltype(::DeriveAppContainerSidFromAppContainerName) + DeriveAppContainerSidFromAppContainerNameFunc; + +typedef decltype(::DeleteAppContainerProfile) DeleteAppContainerProfileFunc; + +typedef decltype(::GetAppContainerFolderPath) GetAppContainerFolderPathFunc; + +typedef decltype( + ::GetAppContainerRegistryLocation) GetAppContainerRegistryLocationFunc; + +struct FreeSidDeleter { + inline void operator()(void* ptr) const { ::FreeSid(ptr); } +}; + +bool IsValidObjectType(SE_OBJECT_TYPE object_type) { + switch (object_type) { + case SE_FILE_OBJECT: + case SE_REGISTRY_KEY: + return true; + default: + break; + } + return false; +} + +bool GetGenericMappingForType(SE_OBJECT_TYPE object_type, + GENERIC_MAPPING* generic_mapping) { + if (!IsValidObjectType(object_type)) + return false; + if (object_type == SE_FILE_OBJECT) { + generic_mapping->GenericRead = FILE_GENERIC_READ; + generic_mapping->GenericWrite = FILE_GENERIC_WRITE; + generic_mapping->GenericExecute = FILE_GENERIC_EXECUTE; + generic_mapping->GenericAll = FILE_ALL_ACCESS; + } else { + generic_mapping->GenericRead = KEY_READ; + generic_mapping->GenericWrite = KEY_WRITE; + generic_mapping->GenericExecute = KEY_EXECUTE; + generic_mapping->GenericAll = KEY_ALL_ACCESS; + } + return true; +} + +class ScopedImpersonation { + public: + ScopedImpersonation(const base::win::ScopedHandle& token) { + BOOL result = ::ImpersonateLoggedOnUser(token.Get()); + DCHECK(result); + } + + ~ScopedImpersonation() { + BOOL result = ::RevertToSelf(); + DCHECK(result); + } +}; + +} // namespace + +// static +AppContainerProfileBase* AppContainerProfileBase::Create( + const wchar_t* package_name, + const wchar_t* display_name, + const wchar_t* description) { + static auto create_app_container_profile = + reinterpret_cast<CreateAppContainerProfileFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "CreateAppContainerProfile")); + if (!create_app_container_profile) + return nullptr; + + PSID package_sid = nullptr; + HRESULT hr = create_app_container_profile( + package_name, display_name, description, nullptr, 0, &package_sid); + if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)) + return Open(package_name); + + if (FAILED(hr)) + return nullptr; + std::unique_ptr<void, FreeSidDeleter> sid_deleter(package_sid); + return new AppContainerProfileBase(Sid(package_sid)); +} + +// static +AppContainerProfileBase* AppContainerProfileBase::Open( + const wchar_t* package_name) { + static auto derive_app_container_sid = + reinterpret_cast<DeriveAppContainerSidFromAppContainerNameFunc*>( + GetProcAddress(GetModuleHandle(L"userenv"), + "DeriveAppContainerSidFromAppContainerName")); + if (!derive_app_container_sid) + return nullptr; + + PSID package_sid = nullptr; + HRESULT hr = derive_app_container_sid(package_name, &package_sid); + if (FAILED(hr)) + return nullptr; + + std::unique_ptr<void, FreeSidDeleter> sid_deleter(package_sid); + return new AppContainerProfileBase(Sid(package_sid)); +} + +// static +bool AppContainerProfileBase::Delete(const wchar_t* package_name) { + static auto delete_app_container_profile = + reinterpret_cast<DeleteAppContainerProfileFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "DeleteAppContainerProfile")); + if (!delete_app_container_profile) + return false; + + return SUCCEEDED(delete_app_container_profile(package_name)); +} + +AppContainerProfileBase::AppContainerProfileBase(const Sid& package_sid) + : ref_count_(0), + package_sid_(package_sid), + enable_low_privilege_app_container_(false) {} + +AppContainerProfileBase::~AppContainerProfileBase() {} + +void AppContainerProfileBase::AddRef() { + ::InterlockedIncrement(&ref_count_); +} + +void AppContainerProfileBase::Release() { + LONG ref_count = ::InterlockedDecrement(&ref_count_); + if (ref_count == 0) { + delete this; + } +} + +bool AppContainerProfileBase::GetRegistryLocation( + REGSAM desired_access, + base::win::ScopedHandle* key) { + static GetAppContainerRegistryLocationFunc* + get_app_container_registry_location = + reinterpret_cast<GetAppContainerRegistryLocationFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "GetAppContainerRegistryLocation")); + if (!get_app_container_registry_location) + return false; + + base::win::ScopedHandle token; + if (!BuildLowBoxToken(&token)) + return false; + + ScopedImpersonation impersonation(token); + HKEY key_handle; + if (FAILED(get_app_container_registry_location(desired_access, &key_handle))) + return false; + key->Set(key_handle); + return true; +} + +bool AppContainerProfileBase::GetFolderPath(base::FilePath* file_path) { +#if defined(MOZ_SANDBOX) + IMMEDIATE_CRASH(); +#else + static GetAppContainerFolderPathFunc* get_app_container_folder_path = + reinterpret_cast<GetAppContainerFolderPathFunc*>(GetProcAddress( + GetModuleHandle(L"userenv"), "GetAppContainerFolderPath")); + if (!get_app_container_folder_path) + return false; + std::wstring sddl_str; + if (!package_sid_.ToSddlString(&sddl_str)) + return false; + base::win::ScopedCoMem<wchar_t> path_str; + if (FAILED(get_app_container_folder_path(sddl_str.c_str(), &path_str))) + return false; + *file_path = base::FilePath(path_str.get()); + return true; +#endif +} + +bool AppContainerProfileBase::GetPipePath(const wchar_t* pipe_name, + base::FilePath* pipe_path) { +#if defined(MOZ_SANDBOX) + IMMEDIATE_CRASH(); +#else + std::wstring sddl_str; + if (!package_sid_.ToSddlString(&sddl_str)) + return false; + *pipe_path = base::FilePath(base::StringPrintf(L"\\\\.\\pipe\\%ls\\%ls", + sddl_str.c_str(), pipe_name)); + return true; +#endif +} + +bool AppContainerProfileBase::AccessCheck(const wchar_t* object_name, + SE_OBJECT_TYPE object_type, + DWORD desired_access, + DWORD* granted_access, + BOOL* access_status) { + GENERIC_MAPPING generic_mapping; + if (!GetGenericMappingForType(object_type, &generic_mapping)) + return false; + MapGenericMask(&desired_access, &generic_mapping); + PSECURITY_DESCRIPTOR sd = nullptr; + PACL dacl = nullptr; + if (GetNamedSecurityInfo( + object_name, object_type, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, + nullptr, nullptr, &dacl, nullptr, &sd) != ERROR_SUCCESS) { + return false; + } + + std::unique_ptr<void, LocalFreeDeleter> sd_ptr(sd); + + if (enable_low_privilege_app_container_) { + Sid any_package_sid(::WinBuiltinAnyPackageSid); + // We can't create a LPAC token directly, so modify the DACL to simulate it. + // Set mask for ALL APPLICATION PACKAGE Sid to 0. + for (WORD index = 0; index < dacl->AceCount; ++index) { + PVOID temp_ace; + if (!GetAce(dacl, index, &temp_ace)) + return false; + PACE_HEADER header = static_cast<PACE_HEADER>(temp_ace); + if ((header->AceType != ACCESS_ALLOWED_ACE_TYPE) && + (header->AceType != ACCESS_DENIED_ACE_TYPE)) { + continue; + } + // Allowed and deny aces have the same underlying structure. + PACCESS_ALLOWED_ACE ace = static_cast<PACCESS_ALLOWED_ACE>(temp_ace); + if (!::IsValidSid(&ace->SidStart)) { + continue; + } + if (::EqualSid(&ace->SidStart, any_package_sid.GetPSID())) { + ace->Mask = 0; + } + } + } + + PRIVILEGE_SET priv_set = {}; + DWORD priv_set_length = sizeof(PRIVILEGE_SET); + + base::win::ScopedHandle token; + if (!BuildLowBoxToken(&token)) + return false; + + return !!::AccessCheck(sd, token.Get(), desired_access, &generic_mapping, + &priv_set, &priv_set_length, granted_access, + access_status); +} + +bool AppContainerProfileBase::AddCapability(const wchar_t* capability_name) { + return AddCapability(Sid::FromNamedCapability(capability_name), false); +} + +bool AppContainerProfileBase::AddCapability(WellKnownCapabilities capability) { + return AddCapability(Sid::FromKnownCapability(capability), false); +} + +bool AppContainerProfileBase::AddCapabilitySddl(const wchar_t* sddl_sid) { + return AddCapability(Sid::FromSddlString(sddl_sid), false); +} + +bool AppContainerProfileBase::AddCapability(const Sid& capability_sid, + bool impersonation_only) { + if (!capability_sid.IsValid()) + return false; + if (!impersonation_only) + capabilities_.push_back(capability_sid); + impersonation_capabilities_.push_back(capability_sid); + return true; +} + +bool AppContainerProfileBase::AddImpersonationCapability( + const wchar_t* capability_name) { + return AddCapability(Sid::FromNamedCapability(capability_name), true); +} + +bool AppContainerProfileBase::AddImpersonationCapability( + WellKnownCapabilities capability) { + return AddCapability(Sid::FromKnownCapability(capability), true); +} + +bool AppContainerProfileBase::AddImpersonationCapabilitySddl( + const wchar_t* sddl_sid) { + return AddCapability(Sid::FromSddlString(sddl_sid), true); +} + +const std::vector<Sid>& AppContainerProfileBase::GetCapabilities() { + return capabilities_; +} + +const std::vector<Sid>& +AppContainerProfileBase::GetImpersonationCapabilities() { + return impersonation_capabilities_; +} + +Sid AppContainerProfileBase::GetPackageSid() const { + return package_sid_; +} + +void AppContainerProfileBase::SetEnableLowPrivilegeAppContainer(bool enable) { + enable_low_privilege_app_container_ = enable; +} + +bool AppContainerProfileBase::GetEnableLowPrivilegeAppContainer() { + return enable_low_privilege_app_container_; +} + +std::unique_ptr<SecurityCapabilities> +AppContainerProfileBase::GetSecurityCapabilities() { + return std::unique_ptr<SecurityCapabilities>( + new SecurityCapabilities(package_sid_, capabilities_)); +} + +bool AppContainerProfileBase::BuildLowBoxToken(base::win::ScopedHandle* token) { + return CreateLowBoxToken(nullptr, IMPERSONATION, + GetSecurityCapabilities().get(), nullptr, 0, + token) == ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.h b/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.h new file mode 100644 index 0000000000..35fb4efdf5 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/app_container_profile_base.h @@ -0,0 +1,94 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_APP_CONTAINER_PROFILE_BASE_H_ +#define SANDBOX_SRC_APP_CONTAINER_PROFILE_BASE_H_ + +#include <windows.h> + +#include <accctrl.h> + +#include <memory> +#include <vector> + +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/app_container_profile.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +class AppContainerProfileBase final : public AppContainerProfile { + public: + void AddRef() override; + void Release() override; + bool GetRegistryLocation(REGSAM desired_access, + base::win::ScopedHandle* key) override; + bool GetFolderPath(base::FilePath* file_path) override; + bool GetPipePath(const wchar_t* pipe_name, + base::FilePath* pipe_path) override; + bool AccessCheck(const wchar_t* object_name, + SE_OBJECT_TYPE object_type, + DWORD desired_access, + DWORD* granted_access, + BOOL* access_status) override; + bool AddCapability(const wchar_t* capability_name) override; + bool AddCapability(WellKnownCapabilities capability) override; + bool AddCapabilitySddl(const wchar_t* sddl_sid) override; + bool AddImpersonationCapability(const wchar_t* capability_name) override; + bool AddImpersonationCapability(WellKnownCapabilities capability) override; + bool AddImpersonationCapabilitySddl(const wchar_t* sddl_sid) override; + void SetEnableLowPrivilegeAppContainer(bool enable) override; + bool GetEnableLowPrivilegeAppContainer() override; + + // Get the package SID for this AC. + Sid GetPackageSid() const; + + // Get an allocated SecurityCapabilities object for this App Container. + std::unique_ptr<SecurityCapabilities> GetSecurityCapabilities(); + + // Get a vector of capabilities. + const std::vector<Sid>& GetCapabilities(); + + // Get a vector of impersonation only capabilities. Used if the process needs + // a more privileged token to start. + const std::vector<Sid>& GetImpersonationCapabilities(); + + // Creates a new AppContainerProfile object. This will create a new profile + // if it doesn't already exist. The profile must be deleted manually using + // the Delete method if it's no longer required. + static AppContainerProfileBase* Create(const wchar_t* package_name, + const wchar_t* display_name, + const wchar_t* description); + + // Opens an AppContainerProfile object. No checks will be made on + // whether the package exists or not. + static AppContainerProfileBase* Open(const wchar_t* package_name); + + // Delete a profile based on name. Returns true if successful, or if the + // package doesn't already exist. + static bool Delete(const wchar_t* package_name); + + private: + AppContainerProfileBase(const Sid& package_sid); + ~AppContainerProfileBase(); + + bool BuildLowBoxToken(base::win::ScopedHandle* token); + bool AddCapability(const Sid& capability_sid, bool impersonation_only); + + // Standard object-lifetime reference counter. + volatile LONG ref_count_; + Sid package_sid_; + bool enable_low_privilege_app_container_; + std::vector<Sid> capabilities_; + std::vector<Sid> impersonation_capabilities_; + + DISALLOW_COPY_AND_ASSIGN(AppContainerProfileBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_APP_CONTAINER_PROFILE_BASE_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/app_container_test.cc b/security/sandbox/chromium/sandbox/win/src/app_container_test.cc new file mode 100644 index 0000000000..a6c0948a94 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/app_container_test.cc @@ -0,0 +1,342 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> + +#include <sddl.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/rand_util.h" +#include "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/app_container_profile_base.h" +#include "sandbox/win/src/sync_policy_test.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "sandbox/win/tests/common/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +const wchar_t kAppContainerSid[] = + L"S-1-15-2-3251537155-1984446955-2931258699-841473695-1938553385-" + L"924012148-2839372144"; + +std::wstring GenerateRandomPackageName() { + return base::StringPrintf(L"%016lX%016lX", base::RandUint64(), + base::RandUint64()); +} + +const char* TokenTypeToName(TOKEN_TYPE token_type) { + return token_type == ::TokenPrimary ? "Primary Token" : "Impersonation Token"; +} + +void CheckToken(HANDLE token, + TOKEN_TYPE token_type, + PSECURITY_CAPABILITIES security_capabilities, + BOOL restricted) { + ASSERT_EQ(restricted, ::IsTokenRestricted(token)) + << TokenTypeToName(token_type); + + DWORD appcontainer; + DWORD return_length; + ASSERT_TRUE(::GetTokenInformation(token, ::TokenIsAppContainer, &appcontainer, + sizeof(appcontainer), &return_length)) + << TokenTypeToName(token_type); + ASSERT_TRUE(appcontainer) << TokenTypeToName(token_type); + TOKEN_TYPE token_type_real; + ASSERT_TRUE(::GetTokenInformation(token, ::TokenType, &token_type_real, + sizeof(token_type_real), &return_length)) + << TokenTypeToName(token_type); + ASSERT_EQ(token_type_real, token_type) << TokenTypeToName(token_type); + if (token_type == ::TokenImpersonation) { + SECURITY_IMPERSONATION_LEVEL imp_level; + ASSERT_TRUE(::GetTokenInformation(token, ::TokenImpersonationLevel, + &imp_level, sizeof(imp_level), + &return_length)) + << TokenTypeToName(token_type); + ASSERT_EQ(imp_level, ::SecurityImpersonation) + << TokenTypeToName(token_type); + } + + std::unique_ptr<Sid> package_sid; + ASSERT_TRUE(GetTokenAppContainerSid(token, &package_sid)) + << TokenTypeToName(token_type); + EXPECT_TRUE(::EqualSid(security_capabilities->AppContainerSid, + package_sid->GetPSID())) + << TokenTypeToName(token_type); + + std::vector<SidAndAttributes> capabilities; + ASSERT_TRUE(GetTokenGroups(token, ::TokenCapabilities, &capabilities)) + << TokenTypeToName(token_type); + + ASSERT_EQ(capabilities.size(), security_capabilities->CapabilityCount) + << TokenTypeToName(token_type); + for (size_t index = 0; index < capabilities.size(); ++index) { + EXPECT_EQ(capabilities[index].GetAttributes(), + security_capabilities->Capabilities[index].Attributes) + << TokenTypeToName(token_type); + EXPECT_TRUE(::EqualSid(capabilities[index].GetPSID(), + security_capabilities->Capabilities[index].Sid)) + << TokenTypeToName(token_type); + } +} + +void CheckProcessToken(HANDLE process, + PSECURITY_CAPABILITIES security_capabilities, + bool restricted) { + HANDLE token_handle; + ASSERT_TRUE(::OpenProcessToken(process, TOKEN_ALL_ACCESS, &token_handle)); + base::win::ScopedHandle token(token_handle); + CheckToken(token_handle, ::TokenPrimary, security_capabilities, restricted); +} + +void CheckThreadToken(HANDLE thread, + PSECURITY_CAPABILITIES security_capabilities, + bool restricted) { + HANDLE token_handle; + ASSERT_TRUE(::OpenThreadToken(thread, TOKEN_ALL_ACCESS, TRUE, &token_handle)); + base::win::ScopedHandle token(token_handle); + CheckToken(token_handle, ::TokenImpersonation, security_capabilities, + restricted); +} + +// Check for LPAC using an access check. We could query for a security attribute +// but that's undocumented and has the potential to change. +void CheckLpacToken(HANDLE process) { + HANDLE token_handle; + ASSERT_TRUE(::OpenProcessToken(process, TOKEN_ALL_ACCESS, &token_handle)); + base::win::ScopedHandle token(token_handle); + ASSERT_TRUE( + ::DuplicateToken(token.Get(), ::SecurityImpersonation, &token_handle)); + token.Set(token_handle); + PSECURITY_DESCRIPTOR security_desc_ptr; + // AC is AllPackages, S-1-15-2-2 is AllRestrictedPackages. An LPAC token + // will get granted access of 2, where as a normal AC token will get 3. + ASSERT_TRUE(::ConvertStringSecurityDescriptorToSecurityDescriptor( + L"O:SYG:SYD:(A;;0x3;;;WD)(A;;0x1;;;AC)(A;;0x2;;;S-1-15-2-2)", + SDDL_REVISION_1, &security_desc_ptr, nullptr)); + std::unique_ptr<void, LocalFreeDeleter> security_desc(security_desc_ptr); + GENERIC_MAPPING generic_mapping = {}; + PRIVILEGE_SET priv_set = {}; + DWORD priv_set_length = sizeof(PRIVILEGE_SET); + DWORD granted_access; + BOOL access_status; + ASSERT_TRUE(::AccessCheck(security_desc_ptr, token.Get(), MAXIMUM_ALLOWED, + &generic_mapping, &priv_set, &priv_set_length, + &granted_access, &access_status)); + ASSERT_TRUE(access_status); + ASSERT_EQ(DWORD{2}, granted_access); +} + +class AppContainerProfileTest : public ::testing::Test { + public: + void SetUp() override { + if (base::win::GetVersion() < base::win::Version::WIN8) + return; + package_name_ = GenerateRandomPackageName(); + broker_services_ = GetBroker(); + policy_ = broker_services_->CreatePolicy(); + ASSERT_EQ(SBOX_ALL_OK, + policy_->SetProcessMitigations(MITIGATION_HEAP_TERMINATE)); + ASSERT_EQ(SBOX_ALL_OK, + policy_->AddAppContainerProfile(package_name_.c_str(), true)); + // For testing purposes we known the base class so cast directly. + profile_ = static_cast<AppContainerProfileBase*>( + policy_->GetAppContainerProfile().get()); + } + + void TearDown() override { + if (scoped_process_info_.IsValid()) + ::TerminateProcess(scoped_process_info_.process_handle(), 0); + if (profile_) + AppContainerProfileBase::Delete(package_name_.c_str()); + } + + protected: + void CreateProcess() { + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH] = {}; + ASSERT_NE(DWORD{0}, ::GetModuleFileNameW(nullptr, prog_name, MAX_PATH)); + + PROCESS_INFORMATION process_info = {}; + ResultCode last_warning = SBOX_ALL_OK; + DWORD last_error = 0; + ResultCode result = broker_services_->SpawnTarget( + prog_name, prog_name, policy_, &last_warning, &last_error, + &process_info); + ASSERT_EQ(SBOX_ALL_OK, result) << "Last Error: " << last_error; + scoped_process_info_.Set(process_info); + } + + std::wstring package_name_; + BrokerServices* broker_services_; + scoped_refptr<AppContainerProfileBase> profile_; + scoped_refptr<TargetPolicy> policy_; + base::win::ScopedProcessInformation scoped_process_info_; +}; + +} // namespace + + +TEST(AppContainerTest, DenyOpenEventForLowBox) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return; + + TestRunner runner(JOB_UNPROTECTED, USER_UNPROTECTED, USER_UNPROTECTED); + + EXPECT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetLowBox(kAppContainerSid)); + // Run test once, this ensures the app container directory exists, we + // ignore the result. + runner.RunTest(L"Event_Open f test"); + std::wstring event_name = L"AppContainerNamedObjects\\"; + event_name += kAppContainerSid; + event_name += L"\\test"; + + base::win::ScopedHandle event( + ::CreateEvent(nullptr, false, false, event_name.c_str())); + ASSERT_TRUE(event.IsValid()); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test")); +} + +TEST_F(AppContainerProfileTest, CheckIncompatibleOptions) { + if (!profile_) + return; + EXPECT_EQ(SBOX_ERROR_BAD_PARAMS, + policy_->SetIntegrityLevel(INTEGRITY_LEVEL_UNTRUSTED)); + EXPECT_EQ(SBOX_ERROR_BAD_PARAMS, policy_->SetLowBox(kAppContainerSid)); + + MitigationFlags expected_mitigations = 0; + MitigationFlags expected_delayed = MITIGATION_HEAP_TERMINATE; + sandbox::ResultCode expected_result = SBOX_ERROR_BAD_PARAMS; + + if (base::win::GetVersion() >= base::win::Version::WIN10_RS5) { + expected_mitigations = MITIGATION_HEAP_TERMINATE; + expected_delayed = 0; + expected_result = SBOX_ALL_OK; + } + + EXPECT_EQ(expected_mitigations, policy_->GetProcessMitigations()); + EXPECT_EQ(expected_delayed, policy_->GetDelayedProcessMitigations()); + EXPECT_EQ(expected_result, + policy_->SetProcessMitigations(MITIGATION_HEAP_TERMINATE)); +} + +TEST_F(AppContainerProfileTest, NoCapabilities) { + if (!profile_) + return; + + policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), FALSE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), FALSE); +} + +TEST_F(AppContainerProfileTest, NoCapabilitiesRestricted) { + if (!profile_) + return; + + policy_->SetTokenLevel(USER_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), TRUE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), TRUE); +} + +TEST_F(AppContainerProfileTest, WithCapabilities) { + if (!profile_) + return; + + profile_->AddCapability(kInternetClient); + profile_->AddCapability(kInternetClientServer); + policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), FALSE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), FALSE); +} + +TEST_F(AppContainerProfileTest, WithCapabilitiesRestricted) { + if (!profile_) + return; + + profile_->AddCapability(kInternetClient); + profile_->AddCapability(kInternetClientServer); + policy_->SetTokenLevel(USER_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), TRUE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), TRUE); +} + +TEST_F(AppContainerProfileTest, WithImpersonationCapabilities) { + if (!profile_) + return; + + profile_->AddCapability(kInternetClient); + profile_->AddCapability(kInternetClientServer); + profile_->AddImpersonationCapability(kPrivateNetworkClientServer); + profile_->AddImpersonationCapability(kPicturesLibrary); + policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), FALSE); + SecurityCapabilities impersonation_security_capabilities( + profile_->GetPackageSid(), profile_->GetImpersonationCapabilities()); + CheckThreadToken(scoped_process_info_.thread_handle(), + &impersonation_security_capabilities, FALSE); +} + +TEST_F(AppContainerProfileTest, NoCapabilitiesLPAC) { + if (base::win::GetVersion() < base::win::Version::WIN10_RS1) + return; + + profile_->SetEnableLowPrivilegeAppContainer(true); + policy_->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + policy_->SetJobLevel(JOB_NONE, 0); + + CreateProcess(); + auto security_capabilities = profile_->GetSecurityCapabilities(); + + CheckProcessToken(scoped_process_info_.process_handle(), + security_capabilities.get(), FALSE); + CheckThreadToken(scoped_process_info_.thread_handle(), + security_capabilities.get(), FALSE); + CheckLpacToken(scoped_process_info_.process_handle()); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/broker_services.cc b/security/sandbox/chromium/sandbox/win/src/broker_services.cc new file mode 100644 index 0000000000..15ed321071 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/broker_services.cc @@ -0,0 +1,745 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/broker_services.h" + +#include <aclapi.h> + +#include <stddef.h> + +#include <utility> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/threading/platform_thread.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/startup_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/app_container_profile.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_policy_base.h" +#include "sandbox/win/src/sandbox_policy_diagnostic.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/win2k_threadpool.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Utility function to associate a completion port to a job object. +bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) { + JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = {key, port}; + return ::SetInformationJobObject(job, + JobObjectAssociateCompletionPortInformation, + &job_acp, sizeof(job_acp)) + ? true + : false; +} + +// Utility function to do the cleanup necessary when something goes wrong +// while in SpawnTarget and we must terminate the target process. +sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target) { + target->Terminate(); + delete target; + return sandbox::SBOX_ERROR_GENERIC; +} + +// the different commands that you can send to the worker thread that +// executes TargetEventsThread(). +enum { + THREAD_CTRL_NONE, + THREAD_CTRL_NEW_JOB_TRACKER, + THREAD_CTRL_NEW_PROCESS_TRACKER, + THREAD_CTRL_PROCESS_SIGNALLED, + THREAD_CTRL_GET_POLICY_INFO, + THREAD_CTRL_QUIT, + THREAD_CTRL_LAST, +}; + +// Helper structure that allows the Broker to associate a job notification +// with a job object and with a policy. +struct JobTracker { + JobTracker(base::win::ScopedHandle job, + scoped_refptr<sandbox::PolicyBase> policy, + DWORD process_id) + : job(std::move(job)), policy(policy), process_id(process_id) {} + ~JobTracker() { FreeResources(); } + + // Releases the Job and notifies the associated Policy object to release its + // resources as well. + void FreeResources(); + + base::win::ScopedHandle job; + scoped_refptr<sandbox::PolicyBase> policy; + DWORD process_id; +}; + +void JobTracker::FreeResources() { + if (policy) { + bool res = ::TerminateJobObject(job.Get(), sandbox::SBOX_ALL_OK); + DCHECK(res); + // Closing the job causes the target process to be destroyed so this needs + // to happen before calling OnJobEmpty(). + HANDLE stale_job_handle = job.Get(); + job.Close(); + + // In OnJobEmpty() we don't actually use the job handle directly. + policy->OnJobEmpty(stale_job_handle); + policy = nullptr; + } +} + +// tracks processes that are not in jobs +struct ProcessTracker { + ProcessTracker(scoped_refptr<sandbox::PolicyBase> policy, + DWORD process_id, + base::win::ScopedHandle process) + : policy(policy), process_id(process_id), process(std::move(process)) {} + ~ProcessTracker() { FreeResources(); } + + void FreeResources(); + + scoped_refptr<sandbox::PolicyBase> policy; + DWORD process_id; + base::win::ScopedHandle process; + // Used to UnregisterWait. Not a real handle so cannot CloseHandle(). + HANDLE wait_handle; + // IOCP that is tracking this non-job process + HANDLE iocp; +}; + +void ProcessTracker::FreeResources() { + if (policy) { + policy->OnJobEmpty(nullptr); + policy = nullptr; + } +} + +// Helper redispatches process events to tracker thread. +void WINAPI ProcessEventCallback(PVOID param, BOOLEAN ignored) { + // This callback should do very little, and must be threadpool safe. + ProcessTracker* tracker = reinterpret_cast<ProcessTracker*>(param); + // If this fails we can do nothing... we will leak the policy. + ::PostQueuedCompletionStatus(tracker->iocp, 0, THREAD_CTRL_PROCESS_SIGNALLED, + reinterpret_cast<LPOVERLAPPED>(tracker)); +} + +// Helper class to send policy lists +class PolicyDiagnosticList final : public sandbox::PolicyList { + public: + PolicyDiagnosticList() {} + ~PolicyDiagnosticList() override {} + void push_back(std::unique_ptr<sandbox::PolicyInfo> info) { + internal_list_.push_back(std::move(info)); + } + std::vector<std::unique_ptr<sandbox::PolicyInfo>>::iterator begin() override { + return internal_list_.begin(); + } + std::vector<std::unique_ptr<sandbox::PolicyInfo>>::iterator end() override { + return internal_list_.end(); + } + size_t size() const override { return internal_list_.size(); } + + private: + std::vector<std::unique_ptr<sandbox::PolicyInfo>> internal_list_; +}; + +} // namespace + +namespace sandbox { + +BrokerServicesBase::BrokerServicesBase() {} + +// The broker uses a dedicated worker thread that services the job completion +// port to perform policy notifications and associated cleanup tasks. +ResultCode BrokerServicesBase::Init() { + if (job_port_.IsValid() || thread_pool_) + return SBOX_ERROR_UNEXPECTED_CALL; + + ::InitializeCriticalSection(&lock_); + + job_port_.Set(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0)); + if (!job_port_.IsValid()) + return SBOX_ERROR_CANNOT_INIT_BROKERSERVICES; + + no_targets_.Set(::CreateEventW(nullptr, true, false, nullptr)); + + job_thread_.Set(::CreateThread(nullptr, 0, // Default security and stack. + TargetEventsThread, this, 0, nullptr)); + if (!job_thread_.IsValid()) + return SBOX_ERROR_CANNOT_INIT_BROKERSERVICES; + + return SBOX_ALL_OK; +} + +// The destructor should only be called when the Broker process is terminating. +// Since BrokerServicesBase is a singleton, this is called from the CRT +// termination handlers, if this code lives on a DLL it is called during +// DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot +// wait for threads here. +BrokerServicesBase::~BrokerServicesBase() { + // If there is no port Init() was never called successfully. + if (!job_port_.IsValid()) + return; + + // Closing the port causes, that no more Job notifications are delivered to + // the worker thread and also causes the thread to exit. This is what we + // want to do since we are going to close all outstanding Jobs and notifying + // the policy objects ourselves. + ::PostQueuedCompletionStatus(job_port_.Get(), 0, THREAD_CTRL_QUIT, nullptr); + + if (job_thread_.IsValid() && + WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_.Get(), 1000)) { + // Cannot clean broker services. + NOTREACHED(); + return; + } + thread_pool_.reset(); + ::DeleteCriticalSection(&lock_); +} + +scoped_refptr<TargetPolicy> BrokerServicesBase::CreatePolicy() { + // If you change the type of the object being created here you must also + // change the downcast to it in SpawnTarget(). + scoped_refptr<TargetPolicy> policy(new PolicyBase); + // PolicyBase starts with refcount 1. + policy->Release(); + return policy; +} + +// The worker thread stays in a loop waiting for asynchronous notifications +// from the job objects. Right now we only care about knowing when the last +// process on a job terminates, but in general this is the place to tell +// the policy about events. +DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { + if (!param) + return 1; + + base::PlatformThread::SetName("BrokerEvent"); + + BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param); + HANDLE port = broker->job_port_.Get(); + HANDLE no_targets = broker->no_targets_.Get(); + + std::set<DWORD> child_process_ids; + std::list<std::unique_ptr<JobTracker>> jobs; + std::list<std::unique_ptr<ProcessTracker>> processes; + int target_counter = 0; + int untracked_target_counter = 0; + ::ResetEvent(no_targets); + + while (true) { + DWORD events = 0; + ULONG_PTR key = 0; + LPOVERLAPPED ovl = nullptr; + + if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) { + // this call fails if the port has been closed before we have a + // chance to service the last packet which is 'exit' anyway so + // this is not an error. + return 1; + } + + if (key > THREAD_CTRL_LAST) { + // The notification comes from a job object. There are nine notifications + // that jobs can send and some of them depend on the job attributes set. + JobTracker* tracker = reinterpret_cast<JobTracker*>(key); + + // Processes may be added to a job after the process count has + // reached zero, leading us to manipulate a freed JobTracker + // object or job handle (as the key is no longer valid). We + // therefore check if the tracker has already been deleted. + if (std::find_if(jobs.begin(), jobs.end(), [&](auto&& p) -> bool { + return p.get() == tracker; + }) == jobs.end()) { + CHECK(false); + } + + switch (events) { + case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: { + // The job object has signaled that the last process associated + // with it has terminated. It is safe to free the tracker + // and release its reference to the associated policy object + // which will Close the job handle. + HANDLE job_handle = tracker->job.Get(); + + // Erase by comparing with the job handle. + jobs.erase(std::remove_if(jobs.begin(), jobs.end(), + [&](auto&& p) -> bool { + return p->job.Get() == job_handle; + }), + jobs.end()); + break; + } + + case JOB_OBJECT_MSG_NEW_PROCESS: { + // Child process created from sandboxed process. + DWORD process_id = + static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)); + size_t count = child_process_ids.count(process_id); + if (count == 0) + untracked_target_counter++; + ++target_counter; + if (1 == target_counter) { + ::ResetEvent(no_targets); + } + break; + } + + case JOB_OBJECT_MSG_EXIT_PROCESS: + case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: { + { + AutoLock lock(&broker->lock_); + broker->active_targets_.erase( + static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl))); + } + size_t erase_result = child_process_ids.erase( + static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl))); + if (erase_result != 1U) { + // The process was untracked e.g. a child process of the target. + --untracked_target_counter; + DCHECK(untracked_target_counter >= 0); + } + --target_counter; + if (0 == target_counter) + ::SetEvent(no_targets); + + DCHECK(target_counter >= 0); + break; + } + + case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: { + // A child process attempted and failed to create a child process. + // Windows does not reveal the process id. + untracked_target_counter++; + target_counter++; + break; + } + + case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: { + bool res = ::TerminateJobObject(tracker->job.Get(), + SBOX_FATAL_MEMORY_EXCEEDED); + DCHECK(res); + break; + } + + default: { + NOTREACHED(); + break; + } + } + } else if (THREAD_CTRL_NEW_JOB_TRACKER == key) { + std::unique_ptr<JobTracker> tracker; + tracker.reset(reinterpret_cast<JobTracker*>(ovl)); + DCHECK(tracker->job.IsValid()); + + child_process_ids.insert(tracker->process_id); + jobs.push_back(std::move(tracker)); + + } else if (THREAD_CTRL_NEW_PROCESS_TRACKER == key) { + std::unique_ptr<ProcessTracker> tracker; + tracker.reset(reinterpret_cast<ProcessTracker*>(ovl)); + + if (child_process_ids.empty()) { + ::SetEvent(broker->no_targets_.Get()); + } + + tracker->iocp = port; + if (!::RegisterWaitForSingleObject(&(tracker->wait_handle), + tracker->process.Get(), + ProcessEventCallback, tracker.get(), + INFINITE, WT_EXECUTEONLYONCE)) { + // Failed. Invalidate the wait_handle and store anyway. + tracker->wait_handle = INVALID_HANDLE_VALUE; + } + processes.push_back(std::move(tracker)); + + } else if (THREAD_CTRL_PROCESS_SIGNALLED == key) { + ProcessTracker* tracker = + static_cast<ProcessTracker*>(reinterpret_cast<void*>(ovl)); + + { + AutoLock lock(&broker->lock_); + broker->active_targets_.erase(tracker->process_id); + } + + ::UnregisterWait(tracker->wait_handle); + tracker->wait_handle = INVALID_HANDLE_VALUE; + // Copy process_id so that we can legally reference it even after we have + // found the ProcessTracker object to delete. + const DWORD process_id = tracker->process_id; + // PID is unique until the process handle is closed in dtor. + processes.erase(std::remove_if(processes.begin(), processes.end(), + [&](auto&& p) -> bool { + return p->process_id == process_id; + }), + processes.end()); + } else if (THREAD_CTRL_GET_POLICY_INFO == key) { + // Clone the policies for sandbox diagnostics. + std::unique_ptr<PolicyDiagnosticsReceiver> receiver; + receiver.reset(static_cast<PolicyDiagnosticsReceiver*>( + reinterpret_cast<void*>(ovl))); + // The PollicyInfo ctor copies essential information from the trackers. + auto policy_list = std::make_unique<PolicyDiagnosticList>(); + for (auto&& process_tracker : processes) { + if (process_tracker->policy) { + policy_list->push_back(std::make_unique<PolicyDiagnostic>( + process_tracker->policy.get())); + } + } + for (auto&& job_tracker : jobs) { + if (job_tracker->policy) { + policy_list->push_back( + std::make_unique<PolicyDiagnostic>(job_tracker->policy.get())); + } + } + // Receiver should return quickly. + receiver->ReceiveDiagnostics(std::move(policy_list)); + + } else if (THREAD_CTRL_QUIT == key) { + // The broker object is being destroyed so the thread needs to exit. + for (auto&& tracker : processes) { + ::UnregisterWait(tracker->wait_handle); + tracker->wait_handle = INVALID_HANDLE_VALUE; + } + // After this point, so further calls to ProcessEventCallback can + // occur. Other tracked objects are destroyed as this thread ends. + return 0; + } else { + // We have not implemented more commands. + NOTREACHED(); + } + } + + NOTREACHED(); + return 0; +} + +// SpawnTarget does all the interesting sandbox setup and creates the target +// process inside the sandbox. +ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + base::EnvironmentMap& env_map, + scoped_refptr<TargetPolicy> policy, + ResultCode* last_warning, + DWORD* last_error, + PROCESS_INFORMATION* target_info) { + if (!exe_path) + return SBOX_ERROR_BAD_PARAMS; + + if (!policy) + return SBOX_ERROR_BAD_PARAMS; + + // Even though the resources touched by SpawnTarget can be accessed in + // multiple threads, the method itself cannot be called from more than one + // thread. This is to protect the global variables used while setting up the + // child process, and to make sure launcher thread mitigations are applied + // correctly. + static DWORD thread_id = ::GetCurrentThreadId(); + DCHECK(thread_id == ::GetCurrentThreadId()); + *last_warning = SBOX_ALL_OK; + + // Launcher thread only needs to be opted out of ACG once. Do this on the + // first child process being spawned. + static bool launcher_thread_opted_out = false; + + if (!launcher_thread_opted_out) { + // Soft fail this call. It will fail if ACG is not enabled for this process. + sandbox::ApplyMitigationsToCurrentThread( + sandbox::MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD); + launcher_thread_opted_out = true; + } + + // This downcast is safe as long as we control CreatePolicy() + scoped_refptr<PolicyBase> policy_base(static_cast<PolicyBase*>(policy.get())); + + // Construct the tokens and the job object that we are going to associate + // with the soon to be created target process. + base::win::ScopedHandle initial_token; + base::win::ScopedHandle lockdown_token; + base::win::ScopedHandle lowbox_token; + ResultCode result = SBOX_ALL_OK; + + result = + policy_base->MakeTokens(&initial_token, &lockdown_token, &lowbox_token); + if (SBOX_ALL_OK != result) + return result; + if (lowbox_token.IsValid() && + base::win::GetVersion() < base::win::Version::WIN8) { + // We don't allow lowbox_token below Windows 8. + return SBOX_ERROR_BAD_PARAMS; + } + + base::win::ScopedHandle job; + result = policy_base->MakeJobObject(&job); + if (SBOX_ALL_OK != result) + return result; + + // Initialize the startup information from the policy. + base::win::StartupInformation startup_info; + + // We don't want any child processes causing the IDC_APPSTARTING cursor. + startup_info.startup_info()->dwFlags |= STARTF_FORCEOFFFEEDBACK; + + // The liftime of |mitigations|, |inherit_handle_list| and + // |child_process_creation| have to be at least as long as + // |startup_info| because |UpdateProcThreadAttribute| requires that + // its |lpValue| parameter persist until |DeleteProcThreadAttributeList| is + // called; StartupInformation's destructor makes such a call. + DWORD64 mitigations[2]; + std::vector<HANDLE> inherited_handle_list; + DWORD child_process_creation = PROCESS_CREATION_CHILD_PROCESS_RESTRICTED; + + std::wstring desktop = policy_base->GetAlternateDesktop(); + if (!desktop.empty()) { + startup_info.startup_info()->lpDesktop = + const_cast<wchar_t*>(desktop.c_str()); + } + + bool inherit_handles = false; + + int attribute_count = 0; + + size_t mitigations_size; + ConvertProcessMitigationsToPolicy(policy_base->GetProcessMitigations(), + &mitigations[0], &mitigations_size); + if (mitigations[0] || mitigations[1]) + ++attribute_count; + + bool restrict_child_process_creation = false; + if (base::win::GetVersion() >= base::win::Version::WIN10_TH2 && + policy_base->GetJobLevel() <= JOB_LIMITED_USER) { + restrict_child_process_creation = true; + ++attribute_count; + } + + HANDLE stdout_handle = policy_base->GetStdoutHandle(); + HANDLE stderr_handle = policy_base->GetStderrHandle(); + + if (stdout_handle != INVALID_HANDLE_VALUE) + inherited_handle_list.push_back(stdout_handle); + + // Handles in the list must be unique. + if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE) + inherited_handle_list.push_back(stderr_handle); + + const auto& policy_handle_list = policy_base->GetHandlesBeingShared(); + + for (HANDLE handle : policy_handle_list) + inherited_handle_list.push_back(handle); + + if (inherited_handle_list.size()) + ++attribute_count; + + scoped_refptr<AppContainerProfileBase> profile = + policy_base->GetAppContainerProfileBase(); + if (profile) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return SBOX_ERROR_BAD_PARAMS; + ++attribute_count; + if (profile->GetEnableLowPrivilegeAppContainer()) { + // LPAC first supported in RS1. + if (base::win::GetVersion() < base::win::Version::WIN10_RS1) + return SBOX_ERROR_BAD_PARAMS; + ++attribute_count; + } + } + + if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + + if (mitigations[0] || mitigations[1]) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations[0], + mitigations_size)) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + } + + if (restrict_child_process_creation) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_CHILD_PROCESS_POLICY, &child_process_creation, + sizeof(child_process_creation))) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + } + + if (inherited_handle_list.size()) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &inherited_handle_list[0], + sizeof(HANDLE) * inherited_handle_list.size())) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES; + startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE; + startup_info.startup_info()->hStdOutput = stdout_handle; + startup_info.startup_info()->hStdError = stderr_handle; + // Allowing inheritance of handles is only secure now that we + // have limited which handles will be inherited. + inherit_handles = true; + } + + // Declared here to ensure they stay in scope until after process creation. + std::unique_ptr<SecurityCapabilities> security_capabilities; + DWORD all_applications_package_policy = + PROCESS_CREATION_ALL_APPLICATION_PACKAGES_OPT_OUT; + + if (profile) { + security_capabilities = profile->GetSecurityCapabilities(); + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, + security_capabilities.get(), sizeof(SECURITY_CAPABILITIES))) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + if (profile->GetEnableLowPrivilegeAppContainer()) { + if (!startup_info.UpdateProcThreadAttribute( + PROC_THREAD_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY, + &all_applications_package_policy, + sizeof(all_applications_package_policy))) { + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; + } + } + } + + // Construct the thread pool here in case it is expensive. + // The thread pool is shared by all the targets + if (!thread_pool_) + thread_pool_ = std::make_unique<Win2kThreadPool>(); + + // Create the TargetProcess object and spawn the target suspended. Note that + // Brokerservices does not own the target object. It is owned by the Policy. + base::win::ScopedProcessInformation process_info; + TargetProcess* target = new TargetProcess( + std::move(initial_token), std::move(lockdown_token), job.Get(), + thread_pool_.get(), + profile ? profile->GetImpersonationCapabilities() : std::vector<Sid>()); + + result = target->Create(exe_path, command_line, inherit_handles, startup_info, + &process_info, env_map, last_error); + + if (result != SBOX_ALL_OK) { + SpawnCleanup(target); + return result; + } + + if (lowbox_token.IsValid()) { + *last_warning = target->AssignLowBoxToken(lowbox_token); + // If this fails we continue, but report the error as a warning. + // This is due to certain configurations causing the setting of the + // token to fail post creation, and we'd rather continue if possible. + if (*last_warning != SBOX_ALL_OK) + *last_error = ::GetLastError(); + } + + // Now the policy is the owner of the target. + result = policy_base->AddTarget(target); + + if (result != SBOX_ALL_OK) { + *last_error = ::GetLastError(); + SpawnCleanup(target); + return result; + } + + if (job.IsValid()) { + JobTracker* tracker = + new JobTracker(std::move(job), policy_base, process_info.process_id()); + + // Post the tracker to the tracking thread, then associate the job with + // the tracker. The worker thread takes ownership of these objects. + CHECK(::PostQueuedCompletionStatus( + job_port_.Get(), 0, THREAD_CTRL_NEW_JOB_TRACKER, + reinterpret_cast<LPOVERLAPPED>(tracker))); + // There is no obvious recovery after failure here. Previous version with + // SpawnCleanup() caused deletion of TargetProcess twice. crbug.com/480639 + CHECK( + AssociateCompletionPort(tracker->job.Get(), job_port_.Get(), tracker)); + + AutoLock lock(&lock_); + active_targets_.insert(process_info.process_id()); + } else { + result = AddTargetPeerInternal(process_info.process_handle(), + process_info.process_id(), + policy_base, last_error); + if (result != SBOX_ALL_OK) { + // This may fail in the same way as Job associated processes. + // crbug.com/480639. + target->Terminate(); + return result; + } + } + + *target_info = process_info.Take(); + return result; +} + +ResultCode BrokerServicesBase::WaitForAllTargets() { + ::WaitForSingleObject(no_targets_.Get(), INFINITE); + return SBOX_ALL_OK; +} + +bool BrokerServicesBase::IsSafeDuplicationTarget(DWORD process_id) { + AutoLock lock(&lock_); + return active_targets_.find(process_id) != active_targets_.end(); +} + +ResultCode BrokerServicesBase::AddTargetPeerInternal( + HANDLE peer_process_handle, + DWORD peer_process_id, + scoped_refptr<PolicyBase> policy_base, + DWORD* last_error) { + // Duplicate the process handle to give the tracking machinery + // something valid to wait on in the tracking thread. + HANDLE tmp_process_handle = INVALID_HANDLE_VALUE; + if (!::DuplicateHandle(::GetCurrentProcess(), peer_process_handle, + ::GetCurrentProcess(), &tmp_process_handle, + SYNCHRONIZE, false, 0 /*no options*/)) { + *last_error = ::GetLastError(); + return SBOX_ERROR_CANNOT_DUPLICATE_PROCESS_HANDLE; + } + base::win::ScopedHandle dup_process_handle(tmp_process_handle); + ProcessTracker* tracker = new ProcessTracker( + policy_base, peer_process_id, std::move(dup_process_handle)); + // The tracker and policy will leak if this call fails. + ::PostQueuedCompletionStatus(job_port_.Get(), 0, + THREAD_CTRL_NEW_PROCESS_TRACKER, + reinterpret_cast<LPOVERLAPPED>(tracker)); + + AutoLock lock(&lock_); + active_targets_.insert(peer_process_id); + + return SBOX_ALL_OK; +} + +ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) { + DWORD last_error; + return AddTargetPeerInternal(peer_process, ::GetProcessId(peer_process), + nullptr, &last_error); +} + +ResultCode BrokerServicesBase::GetPolicyDiagnostics( + std::unique_ptr<PolicyDiagnosticsReceiver> receiver) { + CHECK(job_thread_.IsValid()); + // Post to the job thread. + if (!::PostQueuedCompletionStatus( + job_port_.Get(), 0, THREAD_CTRL_GET_POLICY_INFO, + reinterpret_cast<LPOVERLAPPED>(receiver.get()))) { + receiver->OnError(SBOX_ERROR_GENERIC); + return SBOX_ERROR_GENERIC; + } + + // Ownership has passed to tracker thread. + receiver.release(); + return SBOX_ALL_OK; +} + +bool BrokerServicesBase::DeriveCapabilitySidFromName(const wchar_t* name, + PSID derived_sid, + DWORD sid_buffer_length) { + return ::CopySid(sid_buffer_length, derived_sid, + Sid::FromNamedCapability(name).GetPSID()); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/broker_services.h b/security/sandbox/chromium/sandbox/win/src/broker_services.h new file mode 100644 index 0000000000..64dc6d66e5 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/broker_services.h @@ -0,0 +1,105 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_BROKER_SERVICES_H_ +#define SANDBOX_WIN_SRC_BROKER_SERVICES_H_ + +#include <list> +#include <map> +#include <memory> +#include <set> +#include <utility> + +#include "base/compiler_specific.h" +#include "base/environment.h" +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_policy_base.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "sandbox/win/src/win2k_threadpool.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +// BrokerServicesBase --------------------------------------------------------- +// Broker implementation version 0 +// +// This is an implementation of the interface BrokerServices and +// of the associated TargetProcess interface. In this implementation +// TargetProcess is a friend of BrokerServices where the later manages a +// collection of the former. +class BrokerServicesBase final : public BrokerServices, + public SingletonBase<BrokerServicesBase> { + public: + BrokerServicesBase(); + + ~BrokerServicesBase(); + + // BrokerServices interface. + ResultCode Init() override; + scoped_refptr<TargetPolicy> CreatePolicy() override; + ResultCode SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + base::EnvironmentMap& env_map, + scoped_refptr<TargetPolicy> policy, + ResultCode* last_warning, + DWORD* last_error, + PROCESS_INFORMATION* target) override; + ResultCode WaitForAllTargets() override; + ResultCode AddTargetPeer(HANDLE peer_process) override; + + // Checks if the supplied process ID matches one of the broker's active + // target processes. We use this method for the specific purpose of + // checking if we can safely duplicate a handle to the supplied process + // in DuplicateHandleProxyAction. + bool IsSafeDuplicationTarget(DWORD process_id); + + ResultCode GetPolicyDiagnostics( + std::unique_ptr<PolicyDiagnosticsReceiver> receiver) override; + + bool DeriveCapabilitySidFromName(const wchar_t* name, PSID derived_sid, + DWORD sid_buffer_length) override; + + private: + // The routine that the worker thread executes. It is in charge of + // notifications and cleanup-related tasks. + static DWORD WINAPI TargetEventsThread(PVOID param); + + // The completion port used by the job objects to communicate events to + // the worker thread. + base::win::ScopedHandle job_port_; + + // Handle to a manual-reset event that is signaled when the total target + // process count reaches zero. + base::win::ScopedHandle no_targets_; + + // Handle to the worker thread that reacts to job notifications. + base::win::ScopedHandle job_thread_; + + // Provides a pool of threads that are used to wait on the IPC calls. + std::unique_ptr<ThreadProvider> thread_pool_; + + // The set representing the broker's active target processes including + // both sandboxed and unsandboxed peer processes. + std::set<DWORD> active_targets_; + + // Lock used to protect active_targets_ from being simultaneously accessed + // by multiple threads. + CRITICAL_SECTION lock_; + + ResultCode AddTargetPeerInternal(HANDLE peer_process_handle, + DWORD peer_process_id, + scoped_refptr<PolicyBase> policy_base, + DWORD* last_error); + + DISALLOW_COPY_AND_ASSIGN(BrokerServicesBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_BROKER_SERVICES_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_client.h b/security/sandbox/chromium/sandbox/win/src/crosscall_client.h new file mode 100644 index 0000000000..a346aeb753 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_client.h @@ -0,0 +1,509 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_CROSSCALL_CLIENT_H_ +#define SANDBOX_SRC_CROSSCALL_CLIENT_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/sandbox.h" + +// This header defines the CrossCall(..) family of templated functions +// Their purpose is to simulate the syntax of regular call but to generate +// and IPC from the client-side. +// +// The basic pattern is to +// 1) use template argument deduction to compute the size of each +// parameter and the appropriate copy method +// 2) pack the parameters in the appropriate ActualCallParams< > object +// 3) call the IPC interface IPCProvider::DoCall( ) +// +// The general interface of CrossCall is: +// ResultCode CrossCall(IPCProvider& ipc_provider, +// uint32_t tag, +// const Par1& p1, const Par2& p2,...pn +// CrossCallReturn* answer) +// +// where: +// ipc_provider: is a specific implementation of the ipc transport see +// sharedmem_ipc_server.h for an example. +// tag : is the unique id for this IPC call. Is used to route the call to +// the appropriate service. +// p1, p2,.. pn : The input parameters of the IPC. Use only simple types +// and wide strings (can add support for others). +// answer : If the IPC was successful. The server-side answer is here. The +// interpretation of the answer is private to client and server. +// +// The return value is ALL_OK if the IPC was delivered to the server, other +// return codes indicate that the IPC transport failed to deliver it. +namespace sandbox { + +enum class IpcTag; + +// The copy helper uses templates to deduce the appropriate copy function to +// copy the input parameters in the buffer that is going to be send across the +// IPC. These template facility can be made more sophisticated as need arises. + +// The default copy helper. It catches the general case where no other +// specialized template matches better. We set the type to UINT32_TYPE, so this +// only works with objects whose size is 32 bits. +template <typename T> +class CopyHelper { + public: + CopyHelper(const T& t) : t_(t) {} + + // Returns the pointer to the start of the input. + const void* GetStart() const { return &t_; } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the input in bytes. + uint32_t GetSize() const { return sizeof(T); } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return false; } + + // Returns this object's type. + ArgType GetType() { + static_assert(sizeof(T) == sizeof(uint32_t), "specialization needed"); + return UINT32_TYPE; + } + + private: + const T& t_; +}; + +// This copy helper template specialization if for the void pointer +// case both 32 and 64 bit. +template <> +class CopyHelper<void*> { + public: + CopyHelper(void* t) : t_(t) {} + + // Returns the pointer to the start of the input. + const void* GetStart() const { return &t_; } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the input in bytes. + uint32_t GetSize() const { return sizeof(t_); } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return false; } + + // Returns this object's type. + ArgType GetType() { return VOIDPTR_TYPE; } + + private: + const void* t_; +}; + +// This copy helper template specialization catches the cases where the +// parameter is a pointer to a string. +template <> +class CopyHelper<const wchar_t*> { + public: + CopyHelper(const wchar_t* t) : t_(t) {} + + // Returns the pointer to the start of the string. + const void* GetStart() const { return t_; } + + // Update the stored value with the value in the buffer. This is not + // supported for this type. + bool Update(void* buffer) { + // Not supported; + return true; + } + + // Returns the size of the string in bytes. We define a nullptr string to + // be of zero length. + uint32_t GetSize() const { + __try { + return (!t_) ? 0 + : static_cast<uint32_t>(StringLength(t_) * sizeof(t_[0])); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return UINT32_MAX; + } + } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return false; } + + ArgType GetType() { return WCHAR_TYPE; } + + private: + // We provide our not very optimized version of wcslen(), since we don't + // want to risk having the linker use the version in the CRT since the CRT + // might not be present when we do an early IPC call. + static size_t CDECL StringLength(const wchar_t* wcs) { + const wchar_t* eos = wcs; + while (*eos++) + ; + return static_cast<size_t>(eos - wcs - 1); + } + + const wchar_t* t_; +}; + +// Specialization for non-const strings. We just reuse the implementation of the +// const string specialization. +template <> +class CopyHelper<wchar_t*> : public CopyHelper<const wchar_t*> { + public: + typedef CopyHelper<const wchar_t*> Base; + CopyHelper(wchar_t* t) : Base(t) {} + + const void* GetStart() const { return Base::GetStart(); } + + bool Update(void* buffer) { return Base::Update(buffer); } + + uint32_t GetSize() const { return Base::GetSize(); } + + bool IsInOut() { return Base::IsInOut(); } + + ArgType GetType() { return Base::GetType(); } +}; + +// Specialization for wchar_t arrays strings. We just reuse the implementation +// of the const string specialization. +template <size_t n> +class CopyHelper<const wchar_t[n]> : public CopyHelper<const wchar_t*> { + public: + typedef const wchar_t array[n]; + typedef CopyHelper<const wchar_t*> Base; + CopyHelper(array t) : Base(t) {} + + const void* GetStart() const { return Base::GetStart(); } + + bool Update(void* buffer) { return Base::Update(buffer); } + + uint32_t GetSize() const { return Base::GetSize(); } + + bool IsInOut() { return Base::IsInOut(); } + + ArgType GetType() { return Base::GetType(); } +}; + +// Generic encapsulation class containing a pointer to a buffer and the +// size of the buffer. It is used by the IPC to be able to pass in/out +// parameters. +class InOutCountedBuffer : public CountedBuffer { + public: + InOutCountedBuffer(void* buffer, uint32_t size) + : CountedBuffer(buffer, size) {} +}; + +// This copy helper template specialization catches the cases where the +// parameter is a an input buffer. +template <> +class CopyHelper<CountedBuffer> { + public: + CopyHelper(const CountedBuffer t) : t_(t) {} + + // Returns the pointer to the start of the string. + const void* GetStart() const { return t_.Buffer(); } + + // Update not required so just return true; + bool Update(void* buffer) { return true; } + + // Returns the size of the string in bytes. We define a nullptr string to + // be of zero length. + uint32_t GetSize() const { return t_.Size(); } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return false; } + + ArgType GetType() { return INPTR_TYPE; } + + private: + const CountedBuffer t_; +}; + +// This copy helper template specialization catches the cases where the +// parameter is a an input/output buffer. +template <> +class CopyHelper<InOutCountedBuffer> { + public: + CopyHelper(const InOutCountedBuffer t) : t_(t) {} + + // Returns the pointer to the start of the string. + const void* GetStart() const { return t_.Buffer(); } + + // Updates the buffer with the value from the new buffer in parameter. + bool Update(void* buffer) { + // We are touching user memory, this has to be done from inside a try + // except. + __try { + memcpy_wrapper(t_.Buffer(), buffer, t_.Size()); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } + return true; + } + + // Returns the size of the string in bytes. We define a nullptr string to + // be of zero length. + uint32_t GetSize() const { return t_.Size(); } + + // Returns true if the current type is used as an In or InOut parameter. + bool IsInOut() { return true; } + + ArgType GetType() { return INOUTPTR_TYPE; } + + private: + const InOutCountedBuffer t_; +}; + +// The following two macros make it less error prone the generation +// of CrossCall functions with ever more input parameters. + +#define XCALL_GEN_PARAMS_OBJ(num, params) \ + typedef ActualCallParams<num, kIPCChannelSize> ActualParams; \ + void* raw_mem = ipc_provider.GetBuffer(); \ + if (!raw_mem) \ + return SBOX_ERROR_NO_SPACE; \ + ActualParams* params = new (raw_mem) ActualParams(tag); + +#define XCALL_GEN_COPY_PARAM(num, params) \ + static_assert(kMaxIpcParams >= num, "too many parameters"); \ + CopyHelper<Par##num> ch##num(p##num); \ + if (!params->CopyParamIn(num - 1, ch##num.GetStart(), ch##num.GetSize(), \ + ch##num.IsInOut(), ch##num.GetType())) \ + return SBOX_ERROR_NO_SPACE; + +#define XCALL_GEN_UPDATE_PARAM(num, params) \ + if (!ch##num.Update(params->GetParamPtr(num - 1))) { \ + ipc_provider.FreeBuffer(raw_mem); \ + return SBOX_ERROR_BAD_PARAMS; \ + } + +#define XCALL_GEN_FREE_CHANNEL() ipc_provider.FreeBuffer(raw_mem); + +// CrossCall template with one input parameter +template <typename IPCProvider, typename Par1> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(1, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + + return result; +} + +// CrossCall template with two input parameters. +template <typename IPCProvider, typename Par1, typename Par2> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(2, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with three input parameters. +template <typename IPCProvider, typename Par1, typename Par2, typename Par3> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(3, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with four input parameters. +template <typename IPCProvider, + typename Par1, + typename Par2, + typename Par3, + typename Par4> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + const Par4& p4, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(4, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with five input parameters. +template <typename IPCProvider, + typename Par1, + typename Par2, + typename Par3, + typename Par4, + typename Par5> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + const Par4& p4, + const Par5& p5, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(5, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with six input parameters. +template <typename IPCProvider, + typename Par1, + typename Par2, + typename Par3, + typename Par4, + typename Par5, + typename Par6> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + const Par4& p4, + const Par5& p5, + const Par6& p6, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(6, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + XCALL_GEN_COPY_PARAM(6, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_UPDATE_PARAM(6, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} + +// CrossCall template with seven input parameters. +template <typename IPCProvider, + typename Par1, + typename Par2, + typename Par3, + typename Par4, + typename Par5, + typename Par6, + typename Par7> +ResultCode CrossCall(IPCProvider& ipc_provider, + IpcTag tag, + const Par1& p1, + const Par2& p2, + const Par3& p3, + const Par4& p4, + const Par5& p5, + const Par6& p6, + const Par7& p7, + CrossCallReturn* answer) { + XCALL_GEN_PARAMS_OBJ(7, call_params); + XCALL_GEN_COPY_PARAM(1, call_params); + XCALL_GEN_COPY_PARAM(2, call_params); + XCALL_GEN_COPY_PARAM(3, call_params); + XCALL_GEN_COPY_PARAM(4, call_params); + XCALL_GEN_COPY_PARAM(5, call_params); + XCALL_GEN_COPY_PARAM(6, call_params); + XCALL_GEN_COPY_PARAM(7, call_params); + + ResultCode result = ipc_provider.DoCall(call_params, answer); + + if (SBOX_ERROR_CHANNEL_ERROR != result) { + XCALL_GEN_UPDATE_PARAM(1, call_params); + XCALL_GEN_UPDATE_PARAM(2, call_params); + XCALL_GEN_UPDATE_PARAM(3, call_params); + XCALL_GEN_UPDATE_PARAM(4, call_params); + XCALL_GEN_UPDATE_PARAM(5, call_params); + XCALL_GEN_UPDATE_PARAM(6, call_params); + XCALL_GEN_UPDATE_PARAM(7, call_params); + XCALL_GEN_FREE_CHANNEL(); + } + return result; +} +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_CLIENT_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_params.h b/security/sandbox/chromium/sandbox/win/src/crosscall_params.h new file mode 100644 index 0000000000..6971b55cb7 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_params.h @@ -0,0 +1,315 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_CROSSCALL_PARAMS_H__ +#define SANDBOX_SRC_CROSSCALL_PARAMS_H__ + +#if !defined(SANDBOX_FUZZ_TARGET) +#include <windows.h> + +#include <lmaccess.h> +#else +#include "sandbox/win/fuzzer/fuzzer_types.h" +#endif + +#include <stddef.h> +#include <stdint.h> + +#include <memory> + +#include "base/macros.h" +#include "sandbox/win/src/internal_types.h" +#if !defined(SANDBOX_FUZZ_TARGET) +#include "sandbox/win/src/sandbox_nt_types.h" +#endif +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_types.h" + +// This header is part of CrossCall: the sandbox inter-process communication. +// This header defines the basic types used both in the client IPC and in the +// server IPC code. CrossCallParams and ActualCallParams model the input +// parameters of an IPC call and CrossCallReturn models the output params and +// the return value. +// +// An IPC call is defined by its 'tag' which is a (uint32_t) unique identifier +// that is used to route the IPC call to the proper server. Every tag implies +// a complete call signature including the order and type of each parameter. +// +// Like most IPC systems. CrossCall is designed to take as inputs 'simple' +// types such as integers and strings. Classes, generic arrays or pointers to +// them are not supported. +// +// Another limitation of CrossCall is that the return value and output +// parameters can only be uint32_t integers. Returning complex structures or +// strings is not supported. + +namespace sandbox { + +// this is the assumed channel size. This can be overridden in a given +// IPC implementation. +const uint32_t kIPCChannelSize = 1024; + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +namespace { + +// Increases |value| until there is no need for padding given an int64_t +// alignment. Returns the increased value. +inline uint32_t Align(uint32_t value) { + uint32_t alignment = sizeof(int64_t); + return ((value + alignment - 1) / alignment) * alignment; +} + +inline void* memcpy_wrapper(void* dest, const void* src, size_t count) { + if (g_nt.memcpy) + return g_nt.memcpy(dest, src, count); + return memcpy(dest, src, count); +} + +} // namespace + +// max number of extended return parameters. See CrossCallReturn +const size_t kExtendedReturnCount = 8; + +// Union of multiple types to be used as extended results +// in the CrossCallReturn. +union MultiType { + uint32_t unsigned_int; + void* pointer; + HANDLE handle; + ULONG_PTR ulong_ptr; +}; + +// Maximum number of IPC parameters currently supported. +// To increase this value, we have to: +// - Add another Callback typedef to Dispatcher. +// - Add another case to the switch on SharedMemIPCServer::InvokeCallback. +// - Add another case to the switch in GetActualAndMaxBufferSize +// - Add another case to the switch in GetMinDeclaredActualCallParamsSize +const int kMaxIpcParams = 9; + +// Contains the information about a parameter in the ipc buffer. +struct ParamInfo { + ArgType type_; + uint32_t offset_; + uint32_t size_; +}; + +// Models the return value and the return parameters of an IPC call +// currently limited to one status code and eight generic return values +// which cannot be pointers to other data. For x64 ports this structure +// might have to use other integer types. +struct CrossCallReturn { + // the IPC tag. It should match the original IPC tag. + uint32_t tag; + // The result of the IPC operation itself. + ResultCode call_outcome; + // the result of the IPC call as executed in the server. The interpretation + // of this value depends on the specific service. + union { + NTSTATUS nt_status; + DWORD win32_result; + }; + // Number of extended return values. + uint32_t extended_count; + // for calls that should return a windows handle. It is found here. + HANDLE handle; + // The array of extended values. + MultiType extended[kExtendedReturnCount]; +}; + +// CrossCallParams base class that models the input params all packed in a +// single compact memory blob. The representation can vary but in general a +// given child of this class is meant to represent all input parameters +// necessary to make a IPC call. +// +// This class cannot have virtual members because its assumed the IPC +// parameters start from the 'this' pointer to the end, which is defined by +// one of the subclasses +// +// Objects of this class cannot be constructed directly. Only derived +// classes have the proper knowledge to construct it. +class CrossCallParams { + public: + // Returns the tag (ipc unique id) associated with this IPC. + IpcTag GetTag() const { return tag_; } + + // Returns the beggining of the buffer where the IPC params can be stored. + // prior to an IPC call + const void* GetBuffer() const { return this; } + + // Returns how many parameter this IPC call should have. + uint32_t GetParamsCount() const { return params_count_; } + + // Returns a pointer to the CrossCallReturn structure. + CrossCallReturn* GetCallReturn() { return &call_return; } + + // Returns true if this call contains InOut parameters. + bool IsInOut() const { return (1 == is_in_out_); } + + // Tells the CrossCall object if it contains InOut parameters. + void SetIsInOut(bool value) { + if (value) + is_in_out_ = 1; + else + is_in_out_ = 0; + } + + protected: + // constructs the IPC call params. Called only from the derived classes + CrossCallParams(IpcTag tag, uint32_t params_count) + : tag_(tag), is_in_out_(0), params_count_(params_count) {} + + private: + IpcTag tag_; + uint32_t is_in_out_; + CrossCallReturn call_return; + const uint32_t params_count_; + DISALLOW_COPY_AND_ASSIGN(CrossCallParams); +}; + +// ActualCallParams models an specific IPC call parameters with respect to the +// storage allocation that the packed parameters should need. +// NUMBER_PARAMS: the number of parameters, valid from 1 to N +// BLOCK_SIZE: the total storage that the NUMBER_PARAMS parameters can take, +// typically the block size is defined by the channel size of the underlying +// ipc mechanism. +// In practice this class is used to levergage C++ capacity to properly +// calculate sizes and displacements given the possibility of the packed params +// blob to be complex. +// +// As is, this class assumes that the layout of the blob is as follows. Assume +// that NUMBER_PARAMS = 2 and a 32-bit build: +// +// [ tag 4 bytes] +// [ IsOnOut 4 bytes] +// [ call return 52 bytes] +// [ params count 4 bytes] +// [ parameter 0 type 4 bytes] +// [ parameter 0 offset 4 bytes] ---delta to ---\ +// [ parameter 0 size 4 bytes] | +// [ parameter 1 type 4 bytes] | +// [ parameter 1 offset 4 bytes] ---------------|--\ +// [ parameter 1 size 4 bytes] | | +// [ parameter 2 type 4 bytes] | | +// [ parameter 2 offset 4 bytes] ----------------------\ +// [ parameter 2 size 4 bytes] | | | +// |---------------------------| | | | +// | value 0 (x bytes) | <--------------/ | | +// | value 1 (y bytes) | <-----------------/ | +// | | | +// | end of buffer | <---------------------/ +// |---------------------------| +// +// Note that the actual number of params is NUMBER_PARAMS + 1 +// so that the size of each actual param can be computed from the difference +// between one parameter and the next down. The offset of the last param +// points to the end of the buffer and the type and size are undefined. +// +template <size_t NUMBER_PARAMS, size_t BLOCK_SIZE> +class ActualCallParams : public CrossCallParams { + public: + // constructor. Pass the ipc unique tag as input + explicit ActualCallParams(IpcTag tag) : CrossCallParams(tag, NUMBER_PARAMS) { + param_info_[0].offset_ = + static_cast<uint32_t>(parameters_ - reinterpret_cast<char*>(this)); + } + + // Testing-only constructor. Allows setting the |number_params| to a + // wrong value. + ActualCallParams(IpcTag tag, uint32_t number_params) + : CrossCallParams(tag, number_params) { + param_info_[0].offset_ = + static_cast<uint32_t>(parameters_ - reinterpret_cast<char*>(this)); + } + + static constexpr size_t MaxParamsSize() { + return sizeof( + ActualCallParams<NUMBER_PARAMS, kIPCChannelSize>::parameters_); + } + + // Testing-only method. Allows setting the apparent size to a wrong value. + // returns the previous size. + uint32_t OverrideSize(uint32_t new_size) { + uint32_t previous_size = param_info_[NUMBER_PARAMS].offset_; + param_info_[NUMBER_PARAMS].offset_ = new_size; + return previous_size; + } + + // Copies each paramter into the internal buffer. For each you must supply: + // index: 0 for the first param, 1 for the next an so on + bool CopyParamIn(uint32_t index, + const void* parameter_address, + uint32_t size, + bool is_in_out, + ArgType type) { + if (index >= NUMBER_PARAMS) { + return false; + } + + if (UINT32_MAX == size) { + // Memory error while getting the size. + return false; + } + + if (size && !parameter_address) { + return false; + } + + if ((size > sizeof(*this)) || + (param_info_[index].offset_ > (sizeof(*this) - size))) { + // It does not fit, abort copy. + return false; + } + + char* dest = reinterpret_cast<char*>(this) + param_info_[index].offset_; + + // We might be touching user memory, this has to be done from inside a try + // except. + __try { + memcpy_wrapper(dest, parameter_address, size); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } + + // Set the flag to tell the broker to update the buffer once the call is + // made. + if (is_in_out) + SetIsInOut(true); + + param_info_[index + 1].offset_ = Align(param_info_[index].offset_ + size); + param_info_[index].size_ = size; + param_info_[index].type_ = type; + return true; + } + + // Returns a pointer to a parameter in the memory section. + void* GetParamPtr(size_t index) { + return reinterpret_cast<char*>(this) + param_info_[index].offset_; + } + + // Returns the total size of the buffer. Only valid once all the paramters + // have been copied in with CopyParamIn. + uint32_t GetSize() const { return param_info_[NUMBER_PARAMS].offset_; } + + protected: + ActualCallParams() : CrossCallParams(IpcTag::UNUSED, NUMBER_PARAMS) {} + + private: + ParamInfo param_info_[NUMBER_PARAMS + 1]; + char parameters_[BLOCK_SIZE - sizeof(CrossCallParams) - + sizeof(ParamInfo) * (NUMBER_PARAMS + 1)]; + DISALLOW_COPY_AND_ASSIGN(ActualCallParams); + + friend uint32_t GetMinDeclaredActualCallParamsSize(uint32_t param_count); +}; + +static_assert(sizeof(ActualCallParams<1, 1024>) == 1024, "bad size buffer"); +static_assert(sizeof(ActualCallParams<2, 1024>) == 1024, "bad size buffer"); +static_assert(sizeof(ActualCallParams<3, 1024>) == 1024, "bad size buffer"); + +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_PARAMS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc b/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc new file mode 100644 index 0000000000..27bfdfbd65 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_server.cc @@ -0,0 +1,345 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/crosscall_server.h" + +#include <stddef.h> +#include <stdint.h> + +#include <atomic> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_params.h" + +// See comment in atomicops.h. This is needed any time windows.h is included +// after atomicops.h. +#undef MemoryBarrier + +// This code performs the ipc message validation. Potential security flaws +// on the ipc are likelier to be found in this code than in the rest of +// the ipc code. + +namespace { + +// The buffer for a message must match the max channel size. +const size_t kMaxBufferSize = sandbox::kIPCChannelSize; + +} // namespace + +namespace sandbox { + +// The template types are used to calculate the maximum expected size. +typedef ActualCallParams<0, kMaxBufferSize> ActualCP0; +typedef ActualCallParams<1, kMaxBufferSize> ActualCP1; +typedef ActualCallParams<2, kMaxBufferSize> ActualCP2; +typedef ActualCallParams<3, kMaxBufferSize> ActualCP3; +typedef ActualCallParams<4, kMaxBufferSize> ActualCP4; +typedef ActualCallParams<5, kMaxBufferSize> ActualCP5; +typedef ActualCallParams<6, kMaxBufferSize> ActualCP6; +typedef ActualCallParams<7, kMaxBufferSize> ActualCP7; +typedef ActualCallParams<8, kMaxBufferSize> ActualCP8; +typedef ActualCallParams<9, kMaxBufferSize> ActualCP9; + +// Returns the actual size for the parameters in an IPC buffer. Returns +// zero if the |param_count| is zero or too big. +uint32_t GetActualBufferSize(uint32_t param_count, void* buffer_base) { + // Retrieve the actual size and the maximum size of the params buffer. + switch (param_count) { + case 0: + return 0; + case 1: + return reinterpret_cast<ActualCP1*>(buffer_base)->GetSize(); + case 2: + return reinterpret_cast<ActualCP2*>(buffer_base)->GetSize(); + case 3: + return reinterpret_cast<ActualCP3*>(buffer_base)->GetSize(); + case 4: + return reinterpret_cast<ActualCP4*>(buffer_base)->GetSize(); + case 5: + return reinterpret_cast<ActualCP5*>(buffer_base)->GetSize(); + case 6: + return reinterpret_cast<ActualCP6*>(buffer_base)->GetSize(); + case 7: + return reinterpret_cast<ActualCP7*>(buffer_base)->GetSize(); + case 8: + return reinterpret_cast<ActualCP8*>(buffer_base)->GetSize(); + case 9: + return reinterpret_cast<ActualCP9*>(buffer_base)->GetSize(); + default: + return 0; + } +} + +// Returns the minimum size for the parameters in an IPC buffer. Returns +// zero if the |param_count| is less than zero or too big. +uint32_t GetMinDeclaredActualCallParamsSize(uint32_t param_count) { + switch (param_count) { + case 0: + return offsetof(ActualCP0, parameters_); + case 1: + return offsetof(ActualCP1, parameters_); + case 2: + return offsetof(ActualCP2, parameters_); + case 3: + return offsetof(ActualCP3, parameters_); + case 4: + return offsetof(ActualCP4, parameters_); + case 5: + return offsetof(ActualCP5, parameters_); + case 6: + return offsetof(ActualCP6, parameters_); + case 7: + return offsetof(ActualCP7, parameters_); + case 8: + return offsetof(ActualCP8, parameters_); + case 9: + return offsetof(ActualCP9, parameters_); + default: + return 0; + } +} + +// Verifies that the declared sizes of an IPC buffer are within range. +bool IsSizeWithinRange(uint32_t buffer_size, + uint32_t min_declared_size, + uint32_t declared_size) { + if ((buffer_size < min_declared_size) || + (sizeof(CrossCallParamsEx) > min_declared_size)) { + // Minimal computed size bigger than existing buffer or param_count + // integer overflow. + return false; + } + + if ((declared_size > buffer_size) || (declared_size < min_declared_size)) { + // Declared size is bigger than buffer or smaller than computed size + // or param_count is equal to 0 or bigger than 9. + return false; + } + + return true; +} + +CrossCallParamsEx::CrossCallParamsEx() : CrossCallParams(IpcTag::UNUSED, 0) {} + +// We override the delete operator because the object's backing memory +// is hand allocated in CreateFromBuffer. We don't override the new operator +// because the constructors are private so there is no way to mismatch +// new & delete. +void CrossCallParamsEx::operator delete(void* raw_memory) throw() { + if (!raw_memory) { + // C++ standard allows 'delete 0' behavior. + return; + } + delete[] reinterpret_cast<char*>(raw_memory); +} + +// This function uses a SEH try block so cannot use C++ objects that +// have destructors or else you get Compiler Error C2712. So no DCHECKs +// inside this function. +CrossCallParamsEx* CrossCallParamsEx::CreateFromBuffer(void* buffer_base, + uint32_t buffer_size, + uint32_t* output_size) { + // IMPORTANT: Everything inside buffer_base and derived from it such + // as param_count and declared_size is untrusted. + if (!buffer_base) + return nullptr; + if (buffer_size < sizeof(CrossCallParams)) + return nullptr; + if (buffer_size > kMaxBufferSize) + return nullptr; + + char* backing_mem = nullptr; + uint32_t param_count = 0; + uint32_t declared_size; + uint32_t min_declared_size; + CrossCallParamsEx* copied_params = nullptr; + + // Touching the untrusted buffer is done under a SEH try block. This + // will catch memory access violations so we don't crash. + __try { + CrossCallParams* call_params = + reinterpret_cast<CrossCallParams*>(buffer_base); + + // Check against the minimum size given the number of stated params + // if too small we bail out. + param_count = call_params->GetParamsCount(); + min_declared_size = GetMinDeclaredActualCallParamsSize(param_count); + + // Initial check for the buffer being big enough to determine the actual + // buffer size. + if (buffer_size < min_declared_size) + return nullptr; + + // Retrieve the declared size which if it fails returns 0. + declared_size = GetActualBufferSize(param_count, buffer_base); + + if (!IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) + return nullptr; + + // Now we copy the actual amount of the message. + *output_size = declared_size; + backing_mem = new char[declared_size]; + copied_params = reinterpret_cast<CrossCallParamsEx*>(backing_mem); + memcpy(backing_mem, call_params, declared_size); + + // Avoid compiler optimizations across this point. Any value stored in + // memory should be stored for real, and values previously read from memory + // should be actually read. + std::atomic_thread_fence(std::memory_order_seq_cst); + + min_declared_size = GetMinDeclaredActualCallParamsSize(param_count); + + // Check that the copied buffer is still valid. + if (copied_params->GetParamsCount() != param_count || + GetActualBufferSize(param_count, backing_mem) != declared_size || + !IsSizeWithinRange(buffer_size, min_declared_size, declared_size)) { + delete[] backing_mem; + return nullptr; + } + + } __except (EXCEPTION_EXECUTE_HANDLER) { + // In case of a windows exception we know it occurred while touching the + // untrusted buffer so we bail out as is. + delete[] backing_mem; + return nullptr; + } + + // Here and below we're making use of uintptr_t to have well-defined integer + // overflow when doing pointer arithmetic. + auto backing_mem_ptr = reinterpret_cast<uintptr_t>(backing_mem); + auto last_byte = reinterpret_cast<uintptr_t>(&backing_mem[declared_size]); + auto first_byte = + reinterpret_cast<uintptr_t>(&backing_mem[min_declared_size]); + + // Verify here that all and each parameters make sense. This is done in the + // local copy. + for (uint32_t ix = 0; ix != param_count; ++ix) { + uint32_t size = 0; + ArgType type; + auto address = reinterpret_cast<uintptr_t>( + copied_params->GetRawParameter(ix, &size, &type)); + if ((!address) || // No null params. + (INVALID_TYPE >= type) || (LAST_TYPE <= type) || // Unknown type. + (address < backing_mem_ptr) || // Start cannot point before buffer. + (address < first_byte) || // Start cannot point too low. + (address > last_byte) || // Start cannot point past buffer. + ((address + size) < address) || // Invalid size. + ((address + size) > last_byte)) { // End cannot point past buffer. + // Malformed. + delete[] backing_mem; + return nullptr; + } + } + // The parameter buffer looks good. + return copied_params; +} + +// Accessors to the parameters in the raw buffer. +void* CrossCallParamsEx::GetRawParameter(uint32_t index, + uint32_t* size, + ArgType* type) { + if (index >= GetParamsCount()) + return nullptr; + // The size is always computed from the parameter minus the next + // parameter, this works because the message has an extra parameter slot + *size = param_info_[index].size_; + *type = param_info_[index].type_; + + return param_info_[index].offset_ + reinterpret_cast<char*>(this); +} + +// Covers common case for 32 bit integers. +bool CrossCallParamsEx::GetParameter32(uint32_t index, uint32_t* param) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (!start || (4 != size) || (UINT32_TYPE != type)) + return false; + // Copy the 4 bytes. + *(reinterpret_cast<uint32_t*>(param)) = *(reinterpret_cast<uint32_t*>(start)); + return true; +} + +bool CrossCallParamsEx::GetParameterVoidPtr(uint32_t index, void** param) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (!start || (sizeof(void*) != size) || (VOIDPTR_TYPE != type)) + return false; + *param = *(reinterpret_cast<void**>(start)); + return true; +} + +// Covers the common case of reading a string. Note that the string is not +// scanned for invalid characters. +bool CrossCallParamsEx::GetParameterStr(uint32_t index, std::wstring* string) { + DCHECK(string->empty()); + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + if (WCHAR_TYPE != type) + return false; + + // Check if this is an empty string. + if (size == 0) { + *string = std::wstring(); + return true; + } + + if (!start || ((size % sizeof(wchar_t)) != 0)) + return false; + + string->assign(reinterpret_cast<const wchar_t*>(start), + size / sizeof(wchar_t)); + return true; +} + +bool CrossCallParamsEx::GetParameterPtr(uint32_t index, + uint32_t expected_size, + void** pointer) { + uint32_t size = 0; + ArgType type; + void* start = GetRawParameter(index, &size, &type); + + if ((size != expected_size) || (INOUTPTR_TYPE != type && INPTR_TYPE != type)) + return false; + + if (!start) + return false; + + *pointer = start; + return true; +} + +void SetCallError(ResultCode error, CrossCallReturn* call_return) { + call_return->call_outcome = error; + call_return->extended_count = 0; +} + +void SetCallSuccess(CrossCallReturn* call_return) { + call_return->call_outcome = SBOX_ALL_OK; +} + +Dispatcher* Dispatcher::OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) { + DCHECK(callback); + std::vector<IPCCall>::iterator it = ipc_calls_.begin(); + for (; it != ipc_calls_.end(); ++it) { + if (it->params.Matches(ipc)) { + *callback = it->callback; + return this; + } + } + return nullptr; +} + +Dispatcher::Dispatcher() {} + +Dispatcher::~Dispatcher() {} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/crosscall_server.h b/security/sandbox/chromium/sandbox/win/src/crosscall_server.h new file mode 100644 index 0000000000..aed7f99aae --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/crosscall_server.h @@ -0,0 +1,261 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_CROSSCALL_SERVER_H_ +#define SANDBOX_SRC_CROSSCALL_SERVER_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/ipc_tags.h" + +// This is the IPC server interface for CrossCall: The IPC for the Sandbox +// On the server, CrossCall needs two things: +// 1) threads: Or better said, someone to provide them, that is what the +// ThreadProvider interface is defined for. These thread(s) are +// the ones that will actually execute the IPC data retrieval. +// +// 2) a dispatcher: This interface represents the way to route and process +// an IPC call given the IPC tag. +// +// The other class included here CrossCallParamsEx is the server side version +// of the CrossCallParams class of /sandbox/crosscall_params.h The difference +// is that the sever version is paranoid about the correctness of the IPC +// message and will do all sorts of verifications. +// +// A general diagram of the interaction is as follows: +// +// ------------ +// | | +// ThreadProvider <--(1)Register--| IPC | +// | | Implemen | +// | | -tation | +// (2) | | OnMessage +// IPC fired --callback ------>| |--(3)---> Dispatcher +// | | +// ------------ +// +// The IPC implementation sits as a middleman between the handling of the +// specifics of scheduling a thread to service the IPC and the multiple +// entities that can potentially serve each particular IPC. +namespace sandbox { + +class InterceptionManager; + +// This function signature is required as the callback when an IPC call fires. +// context: a user-defined pointer that was set using ThreadProvider +// reason: 0 if the callback was fired because of a timeout. +// 1 if the callback was fired because of an event. +typedef void(__stdcall* CrossCallIPCCallback)(void* context, + unsigned char reason); + +// ThreadProvider models a thread factory. The idea is to decouple thread +// creation and lifetime from the inner guts of the IPC. The contract is +// simple: +// - the IPC implementation calls RegisterWait with a waitable object that +// becomes signaled when an IPC arrives and needs to be serviced. +// - when the waitable object becomes signaled, the thread provider conjures +// a thread that calls the callback (CrossCallIPCCallback) function +// - the callback function tries its best not to block and return quickly +// and should not assume that the next callback will use the same thread +// - when the callback returns the ThreadProvider owns again the thread +// and can destroy it or keep it around. +class ThreadProvider { + public: + // Registers a waitable object with the thread provider. + // client: A number to associate with all the RegisterWait calls, typically + // this is the address of the caller object. This parameter cannot + // be zero. + // waitable_object : a kernel object that can be waited on + // callback: a function pointer which is the function that will be called + // when the waitable object fires + // context: a user-provider pointer that is passed back to the callback + // when its called + virtual bool RegisterWait(const void* client, + HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) = 0; + + // Removes all the registrations done with the same cookie parameter. + // This frees internal thread pool resources. + virtual bool UnRegisterWaits(void* cookie) = 0; + virtual ~ThreadProvider() {} +}; + +// Models the server-side of the original input parameters. +// Provides IPC buffer validation and it is capable of reading the parameters +// out of the IPC buffer. +class CrossCallParamsEx : public CrossCallParams { + public: + // Factory constructor. Pass an IPCbuffer (and buffer size) that contains a + // pending IPCcall. This constructor will: + // 1) validate the IPC buffer. returns nullptr is the IPCbuffer is malformed. + // 2) make a copy of the IPCbuffer (parameter capture) + static CrossCallParamsEx* CreateFromBuffer(void* buffer_base, + uint32_t buffer_size, + uint32_t* output_size); + + // Provides IPCinput parameter raw access: + // index : the parameter to read; 0 is the first parameter + // returns nullptr if the parameter is non-existent. If it exists it also + // returns the size in *size + void* GetRawParameter(uint32_t index, uint32_t* size, ArgType* type); + + // Gets a parameter that is four bytes in size. + // Returns false if the parameter does not exist or is not 32 bits wide. + bool GetParameter32(uint32_t index, uint32_t* param); + + // Gets a parameter that is void pointer in size. + // Returns false if the parameter does not exist or is not void pointer sized. + bool GetParameterVoidPtr(uint32_t index, void** param); + + // Gets a parameter that is a string. Returns false if the parameter does not + // exist. + bool GetParameterStr(uint32_t index, std::wstring* string); + + // Gets a parameter that is an in/out buffer. Returns false is the parameter + // does not exist or if the size of the actual parameter is not equal to the + // expected size. + bool GetParameterPtr(uint32_t index, uint32_t expected_size, void** pointer); + + // Frees the memory associated with the IPC parameters. + static void operator delete(void* raw_memory) throw(); + + private: + // Only the factory method CreateFromBuffer can construct these objects. + CrossCallParamsEx(); + + ParamInfo param_info_[1]; + DISALLOW_COPY_AND_ASSIGN(CrossCallParamsEx); +}; + +// Simple helper function that sets the members of CrossCallReturn +// to the proper state to signal a basic error. +void SetCallError(ResultCode error, CrossCallReturn* call_return); + +// Sets the internal status of call_return to signify the that IPC call +// completed successfully. +void SetCallSuccess(CrossCallReturn* call_return); + +// Represents the client process that initiated the IPC which boils down to the +// process handle and the job object handle that contains the client process. +struct ClientInfo { + HANDLE process; + DWORD process_id; +}; + +// All IPC-related information to be passed to the IPC handler. +struct IPCInfo { + IpcTag ipc_tag; + const ClientInfo* client_info; + CrossCallReturn return_info; +}; + +// This structure identifies IPC signatures. +struct IPCParams { + IpcTag ipc_tag; + ArgType args[kMaxIpcParams]; + + bool Matches(IPCParams* other) const { + return !memcmp(this, other, sizeof(*other)); + } +}; + +// Models an entity that can process an IPC message or it can route to another +// one that could handle it. When an IPC arrives the IPC implementation will: +// 1) call OnMessageReady() with the tag of the pending IPC. If the dispatcher +// returns nullptr it means that it cannot handle this IPC but if it returns +// non-null, it must be the pointer to a dispatcher that can handle it. +// 2) When the IPC finally obtains a valid Dispatcher the IPC +// implementation creates a CrossCallParamsEx from the raw IPC buffer. +// 3) It calls the returned callback, with the IPC info and arguments. +class Dispatcher { + public: + // Called from the IPC implementation to handle a specific IPC message. + typedef bool (Dispatcher::*CallbackGeneric)(); + typedef bool (Dispatcher::*Callback0)(IPCInfo* ipc); + typedef bool (Dispatcher::*Callback1)(IPCInfo* ipc, void* p1); + typedef bool (Dispatcher::*Callback2)(IPCInfo* ipc, void* p1, void* p2); + typedef bool (Dispatcher::*Callback3)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3); + typedef bool (Dispatcher::*Callback4)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4); + typedef bool (Dispatcher::*Callback5)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5); + typedef bool (Dispatcher::*Callback6)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5, + void* p6); + typedef bool (Dispatcher::*Callback7)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5, + void* p6, + void* p7); + typedef bool (Dispatcher::*Callback8)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5, + void* p6, + void* p7, + void* p8); + typedef bool (Dispatcher::*Callback9)(IPCInfo* ipc, + void* p1, + void* p2, + void* p3, + void* p4, + void* p5, + void* p6, + void* p7, + void* p8, + void* p9); + + // Called from the IPC implementation when an IPC message is ready override + // on a derived class to handle a set of IPC messages. Return nullptr if your + // subclass does not handle the message or return the pointer to the subclass + // that can handle it. + virtual Dispatcher* OnMessageReady(IPCParams* ipc, CallbackGeneric* callback); + + // Called when a target proces is created, to setup the interceptions related + // with the given service (IPC). + virtual bool SetupService(InterceptionManager* manager, IpcTag service) = 0; + + Dispatcher(); + virtual ~Dispatcher(); + + protected: + // Structure that defines an IPC Call with all the parameters and the handler. + struct IPCCall { + IPCParams params; + CallbackGeneric callback; + }; + + // List of IPC Calls supported by the class. + std::vector<IPCCall> ipc_calls_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_CROSSCALL_SERVER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/eat_resolver.cc b/security/sandbox/chromium/sandbox/win/src/eat_resolver.cc new file mode 100644 index 0000000000..f9551ed4b2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/eat_resolver.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/eat_resolver.h" + +#include <stddef.h> + +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS EatResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = + Init(target_module, interceptor_module, target_name, interceptor_name, + interceptor_entry_point, thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + if (!eat_entry_) + return STATUS_INVALID_PARAMETER; + +#if defined(_WIN64) + // We have two thunks, in order: the return path and the forward path. + if (!SetInternalThunk(thunk_storage, storage_bytes, nullptr, target_)) + return STATUS_BUFFER_TOO_SMALL; + + size_t thunk_bytes = GetInternalThunkSize(); + storage_bytes -= thunk_bytes; + thunk_storage = reinterpret_cast<char*>(thunk_storage) + thunk_bytes; +#endif + + if (!SetInternalThunk(thunk_storage, storage_bytes, target_, interceptor_)) + return STATUS_BUFFER_TOO_SMALL; + + AutoProtectMemory memory; + ret = memory.ChangeProtection(eat_entry_, sizeof(DWORD), PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return ret; + + // Perform the patch. + *eat_entry_ = static_cast<DWORD>(reinterpret_cast<uintptr_t>(thunk_storage)) - + static_cast<DWORD>(reinterpret_cast<uintptr_t>(target_module)); + + if (storage_used) + *storage_used = GetThunkSize(); + + return ret; +} + +NTSTATUS EatResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + DCHECK_NT(address); + if (!module) + return STATUS_INVALID_PARAMETER; + + base::win::PEImage pe(module); + if (!pe.VerifyMagic()) + return STATUS_INVALID_IMAGE_FORMAT; + + eat_entry_ = pe.GetExportEntry(function_name); + + if (!eat_entry_) + return STATUS_PROCEDURE_NOT_FOUND; + + *address = pe.RVAToAddr(*eat_entry_); + + return STATUS_SUCCESS; +} + +size_t EatResolverThunk::GetThunkSize() const { +#if defined(_WIN64) + return GetInternalThunkSize() * 2; +#else + return GetInternalThunkSize(); +#endif +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/eat_resolver.h b/security/sandbox/chromium/sandbox/win/src/eat_resolver.h new file mode 100644 index 0000000000..cbc5516f8c --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/eat_resolver.h @@ -0,0 +1,49 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_EAT_RESOLVER_H__ +#define SANDBOX_SRC_EAT_RESOLVER_H__ + +#include <stddef.h> + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/resolver.h" + +namespace sandbox { + +// This is the concrete resolver used to perform exports table interceptions. +class EatResolverThunk : public ResolverThunk { + public: + EatResolverThunk() : eat_entry_(nullptr) {} + ~EatResolverThunk() override {} + + // Implementation of Resolver::Setup. + NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) override; + + // Implementation of Resolver::ResolveTarget. + NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address) override; + + // Implementation of Resolver::GetThunkSize. + size_t GetThunkSize() const override; + + private: + // The entry to patch. + DWORD* eat_entry_; + + DISALLOW_COPY_AND_ASSIGN(EatResolverThunk); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_EAT_RESOLVER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/file_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/file_policy_test.cc new file mode 100644 index 0000000000..4989ab308f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/file_policy_test.cc @@ -0,0 +1,705 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <cctype> + +#include <windows.h> +#include <winioctl.h> + +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "sandbox/win/tests/common/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define BINDNTDLL(name) \ + name##Function name = reinterpret_cast<name##Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +namespace sandbox { + +const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; + +// Creates a file using different desired access. Returns if the call succeeded +// or not. The first argument in argv is the filename. The second argument +// determines the type of access and the dispositino of the file. +SBOX_TESTS_COMMAND int File_Create(int argc, wchar_t** argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring operation(argv[0]); + + if (operation == L"Read") { + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_READ, kSharing, nullptr, OPEN_EXISTING, 0, nullptr)); + base::win::ScopedHandle file2(CreateFile( + argv[1], FILE_EXECUTE, kSharing, nullptr, OPEN_EXISTING, 0, nullptr)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + + } else if (operation == L"Write") { + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_ALL, kSharing, nullptr, OPEN_EXISTING, 0, nullptr)); + base::win::ScopedHandle file2( + CreateFile(argv[1], GENERIC_READ | FILE_WRITE_DATA, kSharing, nullptr, + OPEN_EXISTING, 0, nullptr)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + + } else if (operation == L"ReadCreate") { + base::win::ScopedHandle file2(CreateFile(argv[1], GENERIC_READ, kSharing, + nullptr, CREATE_NEW, 0, nullptr)); + base::win::ScopedHandle file1(CreateFile( + argv[1], GENERIC_READ, kSharing, nullptr, CREATE_ALWAYS, 0, nullptr)); + + if (file1.IsValid() == file2.IsValid()) + return file1.IsValid() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; + return file1.IsValid() ? SBOX_TEST_FIRST_ERROR : SBOX_TEST_SECOND_ERROR; + } + + return SBOX_TEST_INVALID_PARAMETER; +} + +SBOX_TESTS_COMMAND int File_Win32Create(int argc, wchar_t** argv) { + if (argc != 1) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + std::wstring full_path = MakePathToSys(argv[0], false); + if (full_path.empty()) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + + HANDLE file = + ::CreateFileW(full_path.c_str(), GENERIC_READ, kSharing, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (INVALID_HANDLE_VALUE != file) { + ::CloseHandle(file); + return SBOX_TEST_SUCCEEDED; + } else { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } else { + return SBOX_TEST_FAILED; + } + } + return SBOX_TEST_SUCCEEDED; +} + +// Creates the file in parameter using the NtCreateFile api and returns if the +// call succeeded or not. +SBOX_TESTS_COMMAND int File_CreateSys32(int argc, wchar_t** argv) { + BINDNTDLL(NtCreateFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtCreateFile || !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring file(argv[0]); + if (0 != _wcsnicmp(file.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) + file = MakePathToSys(argv[0], true); + + UNICODE_STRING object_name; + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, nullptr, nullptr); + + HANDLE handle; + IO_STATUS_BLOCK io_block = {}; + NTSTATUS status = + NtCreateFile(&handle, FILE_READ_DATA, &obj_attributes, &io_block, nullptr, + 0, kSharing, FILE_OPEN, 0, nullptr, 0); + if (NT_SUCCESS(status)) { + ::CloseHandle(handle); + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status) { + return SBOX_TEST_NOT_FOUND; + } + return SBOX_TEST_FAILED; +} + +// Opens the file in parameter using the NtOpenFile api and returns if the +// call succeeded or not. +SBOX_TESTS_COMMAND int File_OpenSys32(int argc, wchar_t** argv) { + BINDNTDLL(NtOpenFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtOpenFile || !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring file = MakePathToSys(argv[0], true); + UNICODE_STRING object_name; + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, nullptr, nullptr); + + HANDLE handle; + IO_STATUS_BLOCK io_block = {}; + NTSTATUS status = NtOpenFile(&handle, FILE_READ_DATA, &obj_attributes, + &io_block, kSharing, 0); + if (NT_SUCCESS(status)) { + ::CloseHandle(handle); + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status) { + return SBOX_TEST_NOT_FOUND; + } + return SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int File_GetDiskSpace(int argc, wchar_t** argv) { + std::wstring sys_path = MakePathToSys(L"", false); + if (sys_path.empty()) { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + ULARGE_INTEGER free_user = {}; + ULARGE_INTEGER total = {}; + ULARGE_INTEGER free_total = {}; + if (::GetDiskFreeSpaceExW(sys_path.c_str(), &free_user, &total, + &free_total)) { + if ((total.QuadPart != 0) && (free_total.QuadPart != 0)) { + return SBOX_TEST_SUCCEEDED; + } + } else { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } else { + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + } + } + return SBOX_TEST_SUCCEEDED; +} + +// Move a file using the MoveFileEx api and returns if the call succeeded or +// not. +SBOX_TESTS_COMMAND int File_Rename(int argc, wchar_t** argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (::MoveFileEx(argv[0], argv[1], 0)) + return SBOX_TEST_SUCCEEDED; + + if (::GetLastError() != ERROR_ACCESS_DENIED) + return SBOX_TEST_FAILED; + + return SBOX_TEST_DENIED; +} + +// Query the attributes of file in parameter using the NtQueryAttributesFile api +// and NtQueryFullAttributesFile and returns if the call succeeded or not. The +// second argument in argv is "d" or "f" telling if we expect the attributes to +// specify a file or a directory. The expected attribute has to match the real +// attributes for the call to be successful. +SBOX_TESTS_COMMAND int File_QueryAttributes(int argc, wchar_t** argv) { + BINDNTDLL(NtQueryAttributesFile); + BINDNTDLL(NtQueryFullAttributesFile); + BINDNTDLL(RtlInitUnicodeString); + if (!NtQueryAttributesFile || !NtQueryFullAttributesFile || + !RtlInitUnicodeString) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + bool expect_directory = (L'd' == argv[1][0]); + + UNICODE_STRING object_name; + std::wstring file = MakePathToSys(argv[0], true); + RtlInitUnicodeString(&object_name, file.c_str()); + + OBJECT_ATTRIBUTES obj_attributes = {}; + InitializeObjectAttributes(&obj_attributes, &object_name, + OBJ_CASE_INSENSITIVE, nullptr, nullptr); + + FILE_BASIC_INFORMATION info = {}; + FILE_NETWORK_OPEN_INFORMATION full_info = {}; + NTSTATUS status1 = NtQueryAttributesFile(&obj_attributes, &info); + NTSTATUS status2 = NtQueryFullAttributesFile(&obj_attributes, &full_info); + + if (status1 != status2) + return SBOX_TEST_FAILED; + + if (NT_SUCCESS(status1)) { + if (info.FileAttributes != full_info.FileAttributes) + return SBOX_TEST_FAILED; + + bool is_directory1 = (info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + if (expect_directory == is_directory1) + return SBOX_TEST_SUCCEEDED; + } else if (STATUS_ACCESS_DENIED == status1) { + return SBOX_TEST_DENIED; + } else if (STATUS_OBJECT_NAME_NOT_FOUND == status1) { + return SBOX_TEST_NOT_FOUND; + } + + return SBOX_TEST_FAILED; +} + +// Tries to create a backup of calc.exe in system32 folder. This should fail +// with ERROR_ACCESS_DENIED if everything is working as expected. +SBOX_TESTS_COMMAND int File_CopyFile(int argc, wchar_t** argv) { + std::wstring calc_path = MakePathToSys(L"calc.exe", false); + std::wstring calc_backup_path = MakePathToSys(L"calc.exe.bak", false); + + if (::CopyFile(calc_path.c_str(), calc_backup_path.c_str(), FALSE)) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (::GetLastError() != ERROR_ACCESS_DENIED) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +TEST(FilePolicyTest, DenyNtCreateCalc) { + TestRunner runner; + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"calc.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_CreateSys32 calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); +} + +TEST(FilePolicyTest, AllowNtCreateCalc) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"calc.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CreateSys32 calc.exe")); +} + +TEST(FilePolicyTest, AllowNtCreateWithNativePath) { + std::wstring calc = MakePathToSys(L"calc.exe", false); + std::wstring nt_path; + ASSERT_TRUE(GetNtPathFromWin32Path(calc, &nt_path)); + TestRunner runner; + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, nt_path.c_str()); + + wchar_t buff[MAX_PATH]; + ::wsprintfW(buff, L"File_CreateSys32 %s", nt_path.c_str()); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(buff)); + + for (wchar_t& c : nt_path) + c = std::tolower(c); + ::wsprintfW(buff, L"File_CreateSys32 %s", nt_path.c_str()); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(buff)); +} + +TEST(FilePolicyTest, AllowReadOnly) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + EXPECT_TRUE( + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, temp_file_name)); + + wchar_t command_read[MAX_PATH + 20] = {}; + wsprintf(command_read, L"File_Create Read \"%ls\"", temp_file_name); + wchar_t command_read_create[MAX_PATH + 20] = {}; + wsprintf(command_read_create, L"File_Create ReadCreate \"%ls\"", + temp_file_name); + wchar_t command_write[MAX_PATH + 20] = {}; + wsprintf(command_write, L"File_Create Write \"%ls\"", temp_file_name); + + // Verify that we cannot create the file after revert. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_read_create)); + + // Verify that we don't have write access after revert. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_write)); + + // Verify that we have read access after revert. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_read)); + + // Verify that we really have write access to the file. + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write)); + + DeleteFile(temp_file_name); +} + +// Tests support of "\\\\.\\DeviceName" kind of paths. +TEST(FilePolicyTest, AllowImplicitDeviceName) { + TestRunner runner; + + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + std::wstring path(temp_file_name); + EXPECT_TRUE(ConvertToLongPath(&path)); + EXPECT_TRUE(GetNtPathFromWin32Path(path, &path)); + path = path.substr(sandbox::kNTDevicePrefixLen); + + wchar_t command[MAX_PATH + 20] = {}; + wsprintf(command, L"File_Create Read \"\\\\.\\%ls\"", path.c_str()); + path = std::wstring(kNTPrefix) + path; + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, path.c_str())); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command)); + + DeleteFile(temp_file_name); +} + +TEST(FilePolicyTest, AllowWildcard) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + wcscat_s(temp_directory, MAX_PATH, L"*"); + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_directory)); + + wchar_t command_write[MAX_PATH + 20] = {}; + wsprintf(command_write, L"File_Create Write \"%ls\"", temp_file_name); + + // Verify that we have write access after revert. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write)); + + DeleteFile(temp_file_name); +} + +TEST(FilePolicyTest, AllowNtCreatePatternRule) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"App*.dll")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_OpenSys32 apphelp.dll")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_OpenSys32 appwiz.cpl")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_OpenSys32 apphelp.dll")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_OpenSys32 appwiz.cpl")); +} + +TEST(FilePolicyTest, CheckNotFound) { + TestRunner runner; + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"n*.dll")); + + EXPECT_EQ(SBOX_TEST_NOT_FOUND, + runner.RunTest(L"File_OpenSys32 notfound.dll")); +} + +TEST(FilePolicyTest, CheckNoLeak) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_CreateSys32 notfound.exe")); +} + +TEST(FilePolicyTest, TestQueryAttributesFile) { + TestRunner runner; + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"apphelp.dll")); + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"notfound.exe")); + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"drivers")); + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_QUERY, L"ipconfig.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes drivers d")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes apphelp.dll f")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_QueryAttributes ipconfig.exe f")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes ftp.exe f")); + + EXPECT_EQ(SBOX_TEST_NOT_FOUND, + runner.RunTest(L"File_QueryAttributes notfound.exe f")); +} + +// Makes sure that we don't leak information when there is not policy to allow +// a path. +TEST(FilePolicyTest, TestQueryAttributesFileNoPolicy) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes ftp.exe f")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"File_QueryAttributes notfound.exe f")); +} + +TEST(FilePolicyTest, TestRename) { + TestRunner runner; + + // Give access to the temp directory. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name1[MAX_PATH]; + wchar_t temp_file_name2[MAX_PATH]; + wchar_t temp_file_name3[MAX_PATH]; + wchar_t temp_file_name4[MAX_PATH]; + wchar_t temp_file_name5[MAX_PATH]; + wchar_t temp_file_name6[MAX_PATH]; + wchar_t temp_file_name7[MAX_PATH]; + wchar_t temp_file_name8[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name1), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name2), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name3), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name4), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name5), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name6), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name7), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name8), 0u); + + // Add rules to make file1->file2 succeed. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name1)); + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name2)); + + // Add rules to make file3->file4 fail. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name3)); + ASSERT_TRUE( + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, temp_file_name4)); + + // Add rules to make file5->file6 fail. + ASSERT_TRUE( + runner.AddFsRule(TargetPolicy::FILES_ALLOW_READONLY, temp_file_name5)); + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name6)); + + // Add rules to make file7->no_pol_file fail. + ASSERT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, temp_file_name7)); + + // Delete the files where the files are going to be renamed to. + ::DeleteFile(temp_file_name2); + ::DeleteFile(temp_file_name4); + ::DeleteFile(temp_file_name6); + ::DeleteFile(temp_file_name8); + + wchar_t command[MAX_PATH * 2 + 20] = {}; + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name1, + temp_file_name2); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name3, + temp_file_name4); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name5, + temp_file_name6); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + wsprintf(command, L"File_Rename \"%ls\" \"%ls\"", temp_file_name7, + temp_file_name8); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command)); + + // Delete all the files in case they are still there. + ::DeleteFile(temp_file_name1); + ::DeleteFile(temp_file_name2); + ::DeleteFile(temp_file_name3); + ::DeleteFile(temp_file_name4); + ::DeleteFile(temp_file_name5); + ::DeleteFile(temp_file_name6); + ::DeleteFile(temp_file_name7); + ::DeleteFile(temp_file_name8); +} + +TEST(FilePolicyTest, OpenSys32FilesDenyBecauseOfDir) { + TestRunner runner; + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"notepad.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create notepad.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); +} + +TEST(FilePolicyTest, OpenSys32FilesAllowNotepad) { + TestRunner runner; + EXPECT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_ANY, L"notepad.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create calc.exe")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"File_Win32Create notepad.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_Win32Create calc.exe")); +} + +TEST(FilePolicyTest, FileGetDiskSpace) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_GetDiskSpace")); + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + + // Add an 'allow' rule in the windows\system32 such that GetDiskFreeSpaceEx + // succeeds (it does an NtOpenFile) but windows\system32\notepad.exe is + // denied since there is no wild card in the rule. + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"")); + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_GetDiskSpace")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"File_Win32Create notepad.exe")); +} + +TEST(FilePolicyTest, TestReparsePoint) { + TestRunner runner; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t temp_file_name[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, temp_file_name), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(temp_file_name)); + ASSERT_TRUE(::CreateDirectory(temp_file_name, nullptr)); + + // Create a temporary file in the subfolder. + std::wstring subfolder = temp_file_name; + std::wstring temp_file_title = subfolder.substr(subfolder.rfind(L"\\") + 1); + std::wstring temp_file = subfolder + L"\\file_" + temp_file_title; + + HANDLE file = ::CreateFile(temp_file.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + CREATE_ALWAYS, 0, nullptr); + ASSERT_TRUE(INVALID_HANDLE_VALUE != file); + ASSERT_TRUE(::CloseHandle(file)); + + // Create a temporary file in the temp directory. + std::wstring temp_dir = temp_directory; + std::wstring temp_file_in_temp = temp_dir + L"file_" + temp_file_title; + file = ::CreateFile(temp_file_in_temp.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + CREATE_ALWAYS, 0, nullptr); + ASSERT_TRUE(INVALID_HANDLE_VALUE != file); + ASSERT_TRUE(::CloseHandle(file)); + + // Give write access to the temp directory. + std::wstring temp_dir_wildcard = temp_dir + L"*"; + EXPECT_TRUE(runner.AddFsRule(TargetPolicy::FILES_ALLOW_ANY, + temp_dir_wildcard.c_str())); + + // Prepare the command to execute. + std::wstring command_write; + command_write += L"File_Create Write \""; + command_write += temp_file; + command_write += L"\""; + + // Verify that we have write access to the original file + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command_write.c_str())); + + // Replace the subfolder by a reparse point to %temp%. + ::DeleteFile(temp_file.c_str()); + HANDLE dir = ::CreateFile(subfolder.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + EXPECT_TRUE(INVALID_HANDLE_VALUE != dir); + + std::wstring temp_dir_nt; + temp_dir_nt += L"\\??\\"; + temp_dir_nt += temp_dir; + EXPECT_TRUE(SetReparsePoint(dir, temp_dir_nt.c_str())); + EXPECT_TRUE(::CloseHandle(dir)); + + // Try to open the file again. + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(command_write.c_str())); + + // Remove the reparse point. + dir = ::CreateFile(subfolder.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + nullptr); + EXPECT_TRUE(INVALID_HANDLE_VALUE != dir); + EXPECT_TRUE(DeleteReparsePoint(dir)); + EXPECT_TRUE(::CloseHandle(dir)); + + // Cleanup. + EXPECT_TRUE(::DeleteFile(temp_file_in_temp.c_str())); + EXPECT_TRUE(::RemoveDirectory(subfolder.c_str())); +} + +TEST(FilePolicyTest, CheckExistingNTPrefixEscape) { + std::wstring name = L"\\??\\NAME"; + + std::wstring result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), L"\\/?/?\\NAME"); +} + +TEST(FilePolicyTest, CheckEscapedNTPrefixNoEscape) { + std::wstring name = L"\\/?/?\\NAME"; + + std::wstring result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), name.c_str()); +} + +TEST(FilePolicyTest, CheckMissingNTPrefixEscape) { + std::wstring name = L"C:\\NAME"; + + std::wstring result = FixNTPrefixForMatch(name); + + EXPECT_STREQ(result.c_str(), L"\\/?/?\\C:\\NAME"); +} + +TEST(FilePolicyTest, TestCopyFile) { + // Check if the test is running Win8 or newer since + // MITIGATION_STRICT_HANDLE_CHECKS is not supported on older systems. + if (base::win::GetVersion() < base::win::Version::WIN8) + return; + + TestRunner runner; + runner.SetTimeout(2000); + + // Allow read access to calc.exe, this should be on all Windows versions. + ASSERT_TRUE( + runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_READONLY, L"calc.exe")); + + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + // Set proper mitigation. + EXPECT_EQ( + policy->SetDelayedProcessMitigations(MITIGATION_STRICT_HANDLE_CHECKS), + SBOX_ALL_OK); + + ASSERT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"File_CopyFile")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.cc new file mode 100644 index 0000000000..c0f1de07fb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.cc @@ -0,0 +1,302 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/filesystem_dispatcher.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/filesystem_interception.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +FilesystemDispatcher::FilesystemDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::NTCREATEFILE, + {WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&FilesystemDispatcher::NtCreateFile)}; + + static const IPCCall open_file = { + {IpcTag::NTOPENFILE, + {WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&FilesystemDispatcher::NtOpenFile)}; + + static const IPCCall attribs = { + {IpcTag::NTQUERYATTRIBUTESFILE, {WCHAR_TYPE, UINT32_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtQueryAttributesFile)}; + + static const IPCCall full_attribs = { + {IpcTag::NTQUERYFULLATTRIBUTESFILE, + {WCHAR_TYPE, UINT32_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtQueryFullAttributesFile)}; + + static const IPCCall set_info = { + {IpcTag::NTSETINFO_RENAME, + {VOIDPTR_TYPE, INOUTPTR_TYPE, INOUTPTR_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &FilesystemDispatcher::NtSetInformationFile)}; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_file); + ipc_calls_.push_back(attribs); + ipc_calls_.push_back(full_attribs); + ipc_calls_.push_back(set_info); +} + +bool FilesystemDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + switch (service) { + case IpcTag::NTCREATEFILE: + return INTERCEPT_NT(manager, NtCreateFile, CREATE_FILE_ID, 48); + + case IpcTag::NTOPENFILE: + return INTERCEPT_NT(manager, NtOpenFile, OPEN_FILE_ID, 28); + + case IpcTag::NTQUERYATTRIBUTESFILE: + return INTERCEPT_NT(manager, NtQueryAttributesFile, QUERY_ATTRIB_FILE_ID, + 12); + + case IpcTag::NTQUERYFULLATTRIBUTESFILE: + return INTERCEPT_NT(manager, NtQueryFullAttributesFile, + QUERY_FULL_ATTRIB_FILE_ID, 12); + + case IpcTag::NTSETINFO_RENAME: + return INTERCEPT_NT(manager, NtSetInformationFile, SET_INFO_FILE_ID, 24); + + default: + return false; + } +} + +bool FilesystemDispatcher::NtCreateFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options) { + if (!PreProcessName(name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + const wchar_t* filename = name->c_str(); + + uint32_t broker = BROKER_TRUE; + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(filename); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access); + params[OpenFile::DISPOSITION] = ParamPickerMake(create_disposition); + params[OpenFile::OPTIONS] = ParamPickerMake(create_options); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTCREATEFILE, params.GetBase()); + HANDLE handle; + ULONG_PTR io_information = 0; + NTSTATUS nt_status; + if (!FileSystemPolicy::CreateFileAction( + result, *ipc->client_info, *name, attributes, desired_access, + file_attributes, share_access, create_disposition, create_options, + &handle, &nt_status, &io_information)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + // Return operation status on the IPC. + ipc->return_info.extended[0].ulong_ptr = io_information; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool FilesystemDispatcher::NtOpenFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + uint32_t desired_access, + uint32_t share_access, + uint32_t open_options) { + if (!PreProcessName(name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + const wchar_t* filename = name->c_str(); + + uint32_t broker = BROKER_TRUE; + uint32_t create_disposition = FILE_OPEN; + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(filename); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access); + params[OpenFile::DISPOSITION] = ParamPickerMake(create_disposition); + params[OpenFile::OPTIONS] = ParamPickerMake(open_options); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTOPENFILE, params.GetBase()); + HANDLE handle; + ULONG_PTR io_information = 0; + NTSTATUS nt_status; + if (!FileSystemPolicy::OpenFileAction( + result, *ipc->client_info, *name, attributes, desired_access, + share_access, open_options, &handle, &nt_status, &io_information)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + // Return operation status on the IPC. + ipc->return_info.extended[0].ulong_ptr = io_information; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool FilesystemDispatcher::NtQueryAttributesFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + CountedBuffer* info) { + if (sizeof(FILE_BASIC_INFORMATION) != info->Size()) + return false; + + if (!PreProcessName(name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32_t broker = BROKER_TRUE; + const wchar_t* filename = name->c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTQUERYATTRIBUTESFILE, params.GetBase()); + + FILE_BASIC_INFORMATION* information = + reinterpret_cast<FILE_BASIC_INFORMATION*>(info->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::QueryAttributesFileAction(result, *ipc->client_info, + *name, attributes, + information, &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +bool FilesystemDispatcher::NtQueryFullAttributesFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + CountedBuffer* info) { + if (sizeof(FILE_NETWORK_OPEN_INFORMATION) != info->Size()) + return false; + + if (!PreProcessName(name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32_t broker = BROKER_TRUE; + const wchar_t* filename = name->c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = policy_base_->EvalPolicy( + IpcTag::NTQUERYFULLATTRIBUTESFILE, params.GetBase()); + + FILE_NETWORK_OPEN_INFORMATION* information = + reinterpret_cast<FILE_NETWORK_OPEN_INFORMATION*>(info->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::QueryFullAttributesFileAction( + result, *ipc->client_info, *name, attributes, information, + &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +bool FilesystemDispatcher::NtSetInformationFile(IPCInfo* ipc, + HANDLE handle, + CountedBuffer* status, + CountedBuffer* info, + uint32_t length, + uint32_t info_class) { + if (sizeof(IO_STATUS_BLOCK) != status->Size()) + return false; + if (length != info->Size()) + return false; + + FILE_RENAME_INFORMATION* rename_info = + reinterpret_cast<FILE_RENAME_INFORMATION*>(info->Buffer()); + + if (!IsSupportedRenameCall(rename_info, length, info_class)) + return false; + + std::wstring name; + name.assign(rename_info->FileName, + rename_info->FileNameLength / sizeof(rename_info->FileName[0])); + if (!PreProcessName(&name)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + uint32_t broker = BROKER_TRUE; + const wchar_t* filename = name.c_str(); + CountedParameterSet<FileName> params; + params[FileName::NAME] = ParamPickerMake(filename); + params[FileName::BROKER] = ParamPickerMake(broker); + + // To evaluate the policy we need to call back to the policy object. We + // are just middlemen in the operation since is the FileSystemPolicy which + // knows what to do. + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTSETINFO_RENAME, params.GetBase()); + + IO_STATUS_BLOCK* io_status = + reinterpret_cast<IO_STATUS_BLOCK*>(status->Buffer()); + NTSTATUS nt_status; + if (!FileSystemPolicy::SetInformationFileAction( + result, *ipc->client_info, handle, rename_info, length, info_class, + io_status, &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.h new file mode 100644 index 0000000000..5551656e62 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_dispatcher.h @@ -0,0 +1,76 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ +#define SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles file system-related IPC calls. +class FilesystemDispatcher : public Dispatcher { + public: + explicit FilesystemDispatcher(PolicyBase* policy_base); + ~FilesystemDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to NtCreateFile in the target. + bool NtCreateFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options); + + // Processes IPC requests coming from calls to NtOpenFile in the target. + bool NtOpenFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + uint32_t desired_access, + uint32_t share_access, + uint32_t create_options); + + // Processes IPC requests coming from calls to NtQueryAttributesFile in the + // target. + bool NtQueryAttributesFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + CountedBuffer* info); + + // Processes IPC requests coming from calls to NtQueryFullAttributesFile in + // the target. + bool NtQueryFullAttributesFile(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + CountedBuffer* info); + + // Processes IPC requests coming from calls to NtSetInformationFile with the + // rename information class. + bool NtSetInformationFile(IPCInfo* ipc, + HANDLE handle, + CountedBuffer* status, + CountedBuffer* info, + uint32_t length, + uint32_t info_class); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(FilesystemDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_FILESYSTEM_DISPATCHER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_interception.cc b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.cc new file mode 100644 index 0000000000..200dd1f63d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.cc @@ -0,0 +1,412 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/filesystem_interception.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +// This status occurs when trying to access a network share on the machine from +// which it is shared. +#define STATUS_NETWORK_OPEN_RESTRICTION ((NTSTATUS)0xC0000201L) + +namespace sandbox { + +NTSTATUS WINAPI TargetNtCreateFile(NtCreateFileFunction orig_CreateFile, + PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, + ULONG sharing, + ULONG disposition, + ULONG options, + PVOID ea_buffer, + ULONG ea_length) { + // Check if the process can open it first. + NTSTATUS status = orig_CreateFile( + file, desired_access, object_attributes, io_status, allocation_size, + file_attributes, sharing, disposition, options, ea_buffer, ea_length); + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) + return status; + + mozilla::sandboxing::LogBlocked("NtCreateFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(file, sizeof(HANDLE), WRITE)) + break; + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(object_attributes, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t desired_access_uint32 = desired_access; + uint32_t options_uint32 = options; + uint32_t disposition_uint32 = disposition; + uint32_t broker = BROKER_FALSE; + CountedParameterSet<OpenFile> params; + const wchar_t* name_ptr = name.get(); + params[OpenFile::NAME] = ParamPickerMake(name_ptr); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access_uint32); + params[OpenFile::DISPOSITION] = ParamPickerMake(disposition_uint32); + params[OpenFile::OPTIONS] = ParamPickerMake(options_uint32); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTCREATEFILE, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + // The following call must match in the parameters with + // FilesystemDispatcher::ProcessNtCreateFile. + ResultCode code = + CrossCall(ipc, IpcTag::NTCREATEFILE, name.get(), attributes, + desired_access_uint32, file_attributes, sharing, disposition, + options_uint32, &answer); + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + if (!NT_SUCCESS(answer.nt_status)) + break; + + __try { + *file = answer.handle; + io_status->Status = answer.nt_status; + io_status->Information = answer.extended[0].ulong_ptr; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtCreateFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenFile(NtOpenFileFunction orig_OpenFile, + PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + ULONG sharing, + ULONG options) { + // Check if the process can open it first. + NTSTATUS status = orig_OpenFile(file, desired_access, object_attributes, + io_status, sharing, options); + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(file, sizeof(HANDLE), WRITE)) + break; + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes; + NTSTATUS ret = + AllocAndCopyName(object_attributes, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t desired_access_uint32 = desired_access; + uint32_t options_uint32 = options; + uint32_t disposition_uint32 = FILE_OPEN; + uint32_t broker = BROKER_FALSE; + const wchar_t* name_ptr = name.get(); + CountedParameterSet<OpenFile> params; + params[OpenFile::NAME] = ParamPickerMake(name_ptr); + params[OpenFile::ACCESS] = ParamPickerMake(desired_access_uint32); + params[OpenFile::DISPOSITION] = ParamPickerMake(disposition_uint32); + params[OpenFile::OPTIONS] = ParamPickerMake(options_uint32); + params[OpenFile::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTOPENFILE, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = + CrossCall(ipc, IpcTag::NTOPENFILE, name.get(), attributes, + desired_access_uint32, sharing, options_uint32, &answer); + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + if (!NT_SUCCESS(answer.nt_status)) + break; + + __try { + *file = answer.handle; + io_status->Status = answer.nt_status; + io_status->Information = answer.extended[0].ulong_ptr; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtOpenFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI +TargetNtQueryAttributesFile(NtQueryAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes) { + // Check if the process can query it first. + NTSTATUS status = orig_QueryAttributes(object_attributes, file_attributes); + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) + return status; + + mozilla::sandboxing::LogBlocked("NtQueryAttributesFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(file_attributes, sizeof(FILE_BASIC_INFORMATION), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(object_attributes, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + InOutCountedBuffer file_info(file_attributes, + sizeof(FILE_BASIC_INFORMATION)); + + uint32_t broker = BROKER_FALSE; + CountedParameterSet<FileName> params; + const wchar_t* name_ptr = name.get(); + params[FileName::NAME] = ParamPickerMake(name_ptr); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTQUERYATTRIBUTESFILE, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTQUERYATTRIBUTESFILE, name.get(), + attributes, file_info, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + mozilla::sandboxing::LogAllowed("NtQueryAttributesFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtQueryFullAttributesFile( + NtQueryFullAttributesFileFunction orig_QueryFullAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes) { + // Check if the process can query it first. + NTSTATUS status = + orig_QueryFullAttributes(object_attributes, file_attributes); + if (STATUS_ACCESS_DENIED != status && + STATUS_NETWORK_OPEN_RESTRICTION != status) + return status; + + mozilla::sandboxing::LogBlocked("NtQueryFullAttributesFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(file_attributes, sizeof(FILE_NETWORK_OPEN_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(object_attributes, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + InOutCountedBuffer file_info(file_attributes, + sizeof(FILE_NETWORK_OPEN_INFORMATION)); + + uint32_t broker = BROKER_FALSE; + CountedParameterSet<FileName> params; + const wchar_t* name_ptr = name.get(); + params[FileName::NAME] = ParamPickerMake(name_ptr); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTQUERYFULLATTRIBUTESFILE, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTQUERYFULLATTRIBUTESFILE, + name.get(), attributes, file_info, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + + mozilla::sandboxing::LogAllowed("NtQueryFullAttributesFile", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI +TargetNtSetInformationFile(NtSetInformationFileFunction orig_SetInformationFile, + HANDLE file, + PIO_STATUS_BLOCK io_status, + PVOID file_info, + ULONG length, + FILE_INFORMATION_CLASS file_info_class) { + // Check if the process can open it first. + NTSTATUS status = orig_SetInformationFile(file, io_status, file_info, length, + file_info_class); + if (STATUS_ACCESS_DENIED != status) + return status; + + mozilla::sandboxing::LogBlocked("NtSetInformationFile"); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + if (!ValidParameter(io_status, sizeof(IO_STATUS_BLOCK), WRITE)) + break; + + if (!ValidParameter(file_info, length, READ)) + break; + + FILE_RENAME_INFORMATION* file_rename_info = + reinterpret_cast<FILE_RENAME_INFORMATION*>(file_info); + OBJECT_ATTRIBUTES object_attributes; + UNICODE_STRING object_name; + InitializeObjectAttributes(&object_attributes, &object_name, 0, nullptr, + nullptr); + + __try { + if (!IsSupportedRenameCall(file_rename_info, length, file_info_class)) + break; + + object_attributes.RootDirectory = file_rename_info->RootDirectory; + object_name.Buffer = file_rename_info->FileName; + object_name.Length = object_name.MaximumLength = + static_cast<USHORT>(file_rename_info->FileNameLength); + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + NTSTATUS ret = + AllocAndCopyName(&object_attributes, &name, nullptr, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t broker = BROKER_FALSE; + CountedParameterSet<FileName> params; + const wchar_t* name_ptr = name.get(); + params[FileName::NAME] = ParamPickerMake(name_ptr); + params[FileName::BROKER] = ParamPickerMake(broker); + + if (!QueryBroker(IpcTag::NTSETINFO_RENAME, params.GetBase())) + break; + + InOutCountedBuffer io_status_buffer(io_status, sizeof(IO_STATUS_BLOCK)); + // This is actually not an InOut buffer, only In, but using InOut facility + // really helps to simplify the code. + InOutCountedBuffer file_info_buffer(file_info, length); + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = + CrossCall(ipc, IpcTag::NTSETINFO_RENAME, file, io_status_buffer, + file_info_buffer, length, file_info_class, &answer); + + if (SBOX_ALL_OK != code) + break; + + status = answer.nt_status; + mozilla::sandboxing::LogAllowed("NtSetInformationFile"); + } while (false); + + return status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_interception.h b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.h new file mode 100644 index 0000000000..d9d92f7fa4 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_interception.h @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_FILESYSTEM_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_FILESYSTEM_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interception of NtCreateFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateFile(NtCreateFileFunction orig_CreateFile, + PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, + ULONG sharing, + ULONG disposition, + ULONG options, + PVOID ea_buffer, + ULONG ea_length); + +// Interception of NtOpenFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenFile(NtOpenFileFunction orig_OpenFile, + PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + ULONG sharing, + ULONG options); + +// Interception of NtQueryAtttributesFile on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtQueryAttributesFile(NtQueryAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes); + +// Interception of NtQueryFullAtttributesFile on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile( + NtQueryFullAttributesFileFunction orig_QueryAttributes, + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes); + +// Interception of NtSetInformationFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtSetInformationFile(NtSetInformationFileFunction orig_SetInformationFile, + HANDLE file, + PIO_STATUS_BLOCK io_status, + PVOID file_information, + ULONG length, + FILE_INFORMATION_CLASS file_information_class); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_FILESYSTEM_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_policy.cc b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.cc new file mode 100644 index 0000000000..3018cad0cb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.cc @@ -0,0 +1,443 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/filesystem_policy.h" + +#include <stdint.h> + +#include <algorithm> +#include <string> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +NTSTATUS NtCreateFileInTarget(HANDLE* target_file_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + IO_STATUS_BLOCK* io_status_block, + ULONG file_attributes, + ULONG share_access, + ULONG create_disposition, + ULONG create_options, + PVOID ea_buffer, + ULONG ea_length, + HANDLE target_process) { + NtCreateFileFunction NtCreateFile = nullptr; + ResolveNTFunctionPtr("NtCreateFile", &NtCreateFile); + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = + NtCreateFile(&local_handle, desired_access, obj_attributes, + io_status_block, nullptr, file_attributes, share_access, + create_disposition, create_options, ea_buffer, ea_length); + if (!NT_SUCCESS(status)) { + return status; + } + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, target_process, + target_file_handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +// Get an initialized anonymous level Security QOS. +SECURITY_QUALITY_OF_SERVICE GetAnonymousQOS() { + SECURITY_QUALITY_OF_SERVICE security_qos = {0}; + security_qos.Length = sizeof(security_qos); + security_qos.ImpersonationLevel = SecurityAnonymous; + // Set dynamic tracking so that a pipe doesn't capture the broker's token + security_qos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; + security_qos.EffectiveOnly = true; + + return security_qos; +} + +} // namespace. + +namespace sandbox { + +bool FileSystemPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + std::wstring mod_name(name); + if (mod_name.empty()) { + return false; + } + + // Don't pre-process the path name and check for reparse points if it is the + // special case of allowing read access to all paths. + if (!(semantics == TargetPolicy::FILES_ALLOW_READONLY + && mod_name.compare(L"*") == 0) + && !PreProcessName(&mod_name)) { + // The path to be added might contain a reparse point. + NOTREACHED(); + return false; + } + + // TODO(cpu) bug 32224: This prefix add is a hack because we don't have the + // infrastructure to normalize names. In any case we need to escape the + // question marks. + if (_wcsnicmp(mod_name.c_str(), kNTDevicePrefix, kNTDevicePrefixLen)) { + mod_name = FixNTPrefixForMatch(mod_name); + name = mod_name.c_str(); + } + + EvalResult result = ASK_BROKER; + + // List of supported calls for the filesystem. + const unsigned kCallNtCreateFile = 0x1; + const unsigned kCallNtOpenFile = 0x2; + const unsigned kCallNtQueryAttributesFile = 0x4; + const unsigned kCallNtQueryFullAttributesFile = 0x8; + const unsigned kCallNtSetInfoRename = 0x10; + + DWORD rule_to_add = kCallNtOpenFile | kCallNtCreateFile | + kCallNtQueryAttributesFile | + kCallNtQueryFullAttributesFile | kCallNtSetInfoRename; + + PolicyRule create(result); + PolicyRule open(result); + PolicyRule query(result); + PolicyRule query_full(result); + PolicyRule rename(result); + + switch (semantics) { + case TargetPolicy::FILES_ALLOW_DIR_ANY: { + open.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); + create.AddNumberMatch(IF, OpenFile::OPTIONS, FILE_DIRECTORY_FILE, AND); + break; + } + case TargetPolicy::FILES_ALLOW_READONLY: { + // We consider all flags that are not known to be readonly as potentially + // used for write. + DWORD allowed_flags = FILE_READ_DATA | FILE_READ_ATTRIBUTES | + FILE_READ_EA | SYNCHRONIZE | FILE_EXECUTE | + GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL; + DWORD restricted_flags = ~allowed_flags; + open.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); + open.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL); + create.AddNumberMatch(IF_NOT, OpenFile::ACCESS, restricted_flags, AND); + create.AddNumberMatch(IF, OpenFile::DISPOSITION, FILE_OPEN, EQUAL); + + // Read only access don't work for rename. + rule_to_add &= ~kCallNtSetInfoRename; + break; + } + case TargetPolicy::FILES_ALLOW_QUERY: { + // Here we don't want to add policy for the open or the create. + rule_to_add &= + ~(kCallNtOpenFile | kCallNtCreateFile | kCallNtSetInfoRename); + break; + } + case TargetPolicy::FILES_ALLOW_ANY: { + break; + } + default: { + NOTREACHED(); + return false; + } + } + + if ((rule_to_add & kCallNtCreateFile) && + (!create.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTCREATEFILE, &create))) { + return false; + } + + if ((rule_to_add & kCallNtOpenFile) && + (!open.AddStringMatch(IF, OpenFile::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTOPENFILE, &open))) { + return false; + } + + if ((rule_to_add & kCallNtQueryAttributesFile) && + (!query.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &query))) { + return false; + } + + if ((rule_to_add & kCallNtQueryFullAttributesFile) && + (!query_full.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &query_full))) { + return false; + } + + if ((rule_to_add & kCallNtSetInfoRename) && + (!rename.AddStringMatch(IF, FileName::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTSETINFO_RENAME, &rename))) { + return false; + } + + return true; +} + +// Right now we insert two rules, to be evaluated before any user supplied rule: +// - go to the broker if the path doesn't look like the paths that we push on +// the policy (namely \??\something). +// - go to the broker if it looks like this is a short-name path. +// +// It is possible to add a rule to go to the broker in any case; it would look +// something like: +// rule = new PolicyRule(ASK_BROKER); +// rule->AddNumberMatch(IF_NOT, FileName::BROKER, true, AND); +// policy->AddRule(service, rule); +bool FileSystemPolicy::SetInitialRules(LowLevelPolicy* policy) { + PolicyRule format(ASK_BROKER); + PolicyRule short_name(ASK_BROKER); + + bool rv = format.AddNumberMatch(IF_NOT, FileName::BROKER, BROKER_TRUE, AND); + rv &= format.AddStringMatch(IF_NOT, FileName::NAME, L"\\/?/?\\*", + CASE_SENSITIVE); + + rv &= short_name.AddNumberMatch(IF_NOT, FileName::BROKER, BROKER_TRUE, AND); + rv &= short_name.AddStringMatch(IF, FileName::NAME, L"*~*", CASE_SENSITIVE); + + if (!rv || !policy->AddRule(IpcTag::NTCREATEFILE, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTCREATEFILE, &short_name)) + return false; + + if (!policy->AddRule(IpcTag::NTOPENFILE, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTOPENFILE, &short_name)) + return false; + + if (!policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTQUERYATTRIBUTESFILE, &short_name)) + return false; + + if (!policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTQUERYFULLATTRIBUTESFILE, &short_name)) + return false; + + if (!policy->AddRule(IpcTag::NTSETINFO_RENAME, &format)) + return false; + + if (!policy->AddRule(IpcTag::NTSETINFO_RENAME, &short_name)) + return false; + + return true; +} + +bool FileSystemPolicy::CreateFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information) { + *handle = nullptr; + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + IO_STATUS_BLOCK io_block = {}; + UNICODE_STRING uni_name = {}; + OBJECT_ATTRIBUTES obj_attributes = {}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, nullptr, &obj_attributes, &uni_name, + IsPipe(file) ? &security_qos : nullptr); + *nt_status = + NtCreateFileInTarget(handle, desired_access, &obj_attributes, &io_block, + file_attributes, share_access, create_disposition, + create_options, nullptr, 0, client_info.process); + + *io_information = io_block.Information; + return true; +} + +bool FileSystemPolicy::OpenFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + uint32_t desired_access, + uint32_t share_access, + uint32_t open_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information) { + *handle = nullptr; + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + // An NtOpen is equivalent to an NtCreate with FileAttributes = 0 and + // CreateDisposition = FILE_OPEN. + IO_STATUS_BLOCK io_block = {}; + UNICODE_STRING uni_name = {}; + OBJECT_ATTRIBUTES obj_attributes = {}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, nullptr, &obj_attributes, &uni_name, + IsPipe(file) ? &security_qos : nullptr); + *nt_status = NtCreateFileInTarget( + handle, desired_access, &obj_attributes, &io_block, 0, share_access, + FILE_OPEN, open_options, nullptr, 0, client_info.process); + + *io_information = io_block.Information; + return true; +} + +bool FileSystemPolicy::QueryAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + FILE_BASIC_INFORMATION* file_info, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means query the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + NtQueryAttributesFileFunction NtQueryAttributesFile = nullptr; + ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile); + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, nullptr, &obj_attributes, &uni_name, + IsPipe(file) ? &security_qos : nullptr); + *nt_status = NtQueryAttributesFile(&obj_attributes, file_info); + + return true; +} + +bool FileSystemPolicy::QueryFullAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + FILE_NETWORK_OPEN_INFORMATION* file_info, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means query the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + NtQueryFullAttributesFileFunction NtQueryFullAttributesFile = nullptr; + ResolveNTFunctionPtr("NtQueryFullAttributesFile", &NtQueryFullAttributesFile); + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + SECURITY_QUALITY_OF_SERVICE security_qos = GetAnonymousQOS(); + + InitObjectAttribs(file, attributes, nullptr, &obj_attributes, &uni_name, + IsPipe(file) ? &security_qos : nullptr); + *nt_status = NtQueryFullAttributesFile(&obj_attributes, file_info); + + return true; +} + +bool FileSystemPolicy::SetInformationFileAction(EvalResult eval_result, + const ClientInfo& client_info, + HANDLE target_file_handle, + void* file_info, + uint32_t length, + uint32_t info_class, + IO_STATUS_BLOCK* io_block, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + NtSetInformationFileFunction NtSetInformationFile = nullptr; + ResolveNTFunctionPtr("NtSetInformationFile", &NtSetInformationFile); + + HANDLE local_handle = nullptr; + if (!::DuplicateHandle(client_info.process, target_file_handle, + ::GetCurrentProcess(), &local_handle, 0, false, + DUPLICATE_SAME_ACCESS)) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + base::win::ScopedHandle handle(local_handle); + + FILE_INFORMATION_CLASS file_info_class = + static_cast<FILE_INFORMATION_CLASS>(info_class); + *nt_status = NtSetInformationFile(local_handle, io_block, file_info, length, + file_info_class); + + return true; +} + +bool PreProcessName(std::wstring* path) { + // We now allow symbolic links to be opened via the broker, so we can no + // longer rely on the same object check where we checked the path of the + // opened file against the original. We don't specify a root when creating + // OBJECT_ATTRIBUTES from file names for brokering so they must be fully + // qualified and we can just check for the parent directory double dot between + // two backslashes. NtCreateFile doesn't seem to allow it anyway, but this is + // just an extra precaution. It also doesn't seem to allow the forward slash, + // but this is also used for checking policy rules, so we just replace forward + // slashes with backslashes. + std::replace(path->begin(), path->end(), L'/', L'\\'); + if (path->find(L"\\..\\") != std::wstring::npos) { + return false; + } + + ConvertToLongPath(path); + return true; +} + +std::wstring FixNTPrefixForMatch(const std::wstring& name) { + std::wstring mod_name = name; + + // NT prefix escaped for rule matcher + const wchar_t kNTPrefixEscaped[] = L"\\/?/?\\"; + const int kNTPrefixEscapedLen = base::size(kNTPrefixEscaped) - 1; + + if (0 != mod_name.compare(0, kNTPrefixLen, kNTPrefix)) { + if (0 != mod_name.compare(0, kNTPrefixEscapedLen, kNTPrefixEscaped)) { + // TODO(nsylvain): Find a better way to do name resolution. Right now we + // take the name and we expand it. + mod_name.insert(0, kNTPrefixEscaped); + } + } else { + // Start of name matches NT prefix, replace with escaped format + // Fixes bug: 334882 + mod_name.replace(0, kNTPrefixLen, kNTPrefixEscaped); + } + + return mod_name; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/filesystem_policy.h b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.h new file mode 100644 index 0000000000..81dd1d0d1d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/filesystem_policy.h @@ -0,0 +1,112 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_FILESYSTEM_POLICY_H__ +#define SANDBOX_SRC_FILESYSTEM_POLICY_H__ + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum IsBroker { BROKER_FALSE, BROKER_TRUE }; + +// This class centralizes most of the knowledge related to file system policy +class FileSystemPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for File IO, in particular open or create actions. + // 'name' is the file or directory name. + // 'semantics' is the desired semantics for the open or create. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Add basic file system rules. + static bool SetInitialRules(LowLevelPolicy* policy); + + // Performs the desired policy action on a create request with an + // API that is compatible with the IPC-received parameters. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'file' : The target file or directory. + static bool CreateFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + uint32_t desired_access, + uint32_t file_attributes, + uint32_t share_access, + uint32_t create_disposition, + uint32_t create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information); + + // Performs the desired policy action on an open request with an + // API that is compatible with the IPC-received parameters. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'file' : The target file or directory. + static bool OpenFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + uint32_t desired_access, + uint32_t share_access, + uint32_t open_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG_PTR* io_information); + + // Performs the desired policy action on a query request with an + // API that is compatible with the IPC-received parameters. + static bool QueryAttributesFileAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + FILE_BASIC_INFORMATION* file_info, + NTSTATUS* nt_status); + + // Performs the desired policy action on a query request with an + // API that is compatible with the IPC-received parameters. + static bool QueryFullAttributesFileAction( + EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& file, + uint32_t attributes, + FILE_NETWORK_OPEN_INFORMATION* file_info, + NTSTATUS* nt_status); + + // Performs the desired policy action on a set_info request with an + // API that is compatible with the IPC-received parameters. + static bool SetInformationFileAction(EvalResult eval_result, + const ClientInfo& client_info, + HANDLE target_file_handle, + void* file_info, + uint32_t length, + uint32_t info_class, + IO_STATUS_BLOCK* io_block, + NTSTATUS* nt_status); +}; + +// Expands the path and check if it's a reparse point. Returns false if the path +// cannot be trusted. +bool PreProcessName(std::wstring* path); + +// Corrects global paths to have a correctly escaped NT prefix at the +// beginning. If the name has no NT prefix (either normal or escaped) +// add the escaped form to the string +std::wstring FixNTPrefixForMatch(const std::wstring& name); + +} // namespace sandbox + +#endif // SANDBOX_SRC_FILESYSTEM_POLICY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer.cc b/security/sandbox/chromium/sandbox/win/src/handle_closer.cc new file mode 100644 index 0000000000..6751151dcb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer.cc @@ -0,0 +1,185 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_closer.h" + +#include <stddef.h> + +#include <memory> + +#include "base/logging.h" +#include "base/memory/free_deleter.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +template <typename T> +T RoundUpToWordSize(T v) { + if (size_t mod = v % sizeof(size_t)) + v += sizeof(size_t) - mod; + return v; +} + +template <typename T> +T* RoundUpToWordSize(T* v) { + return reinterpret_cast<T*>(RoundUpToWordSize(reinterpret_cast<size_t>(v))); +} + +} // namespace + +namespace sandbox { + +// Memory buffer mapped from the parent, with the list of handles. +SANDBOX_INTERCEPT HandleCloserInfo* g_handles_to_close; + +HandleCloser::HandleCloser() {} + +HandleCloser::~HandleCloser() {} + +ResultCode HandleCloser::AddHandle(const wchar_t* handle_type, + const wchar_t* handle_name) { + if (!handle_type) + return SBOX_ERROR_BAD_PARAMS; + + std::wstring resolved_name; + if (handle_name) { + resolved_name = handle_name; + if (handle_type == std::wstring(L"Key")) + if (!ResolveRegistryName(resolved_name, &resolved_name)) + return SBOX_ERROR_BAD_PARAMS; + } + + HandleMap::iterator names = handles_to_close_.find(handle_type); + if (names == handles_to_close_.end()) { // We have no entries for this type. + std::pair<HandleMap::iterator, bool> result = handles_to_close_.insert( + HandleMap::value_type(handle_type, HandleMap::mapped_type())); + names = result.first; + if (handle_name) + names->second.insert(resolved_name); + } else if (!handle_name) { // Now we need to close all handles of this type. + names->second.clear(); + } else if (!names->second.empty()) { // Add another name for this type. + names->second.insert(resolved_name); + } // If we're already closing all handles of type then we're done. + + return SBOX_ALL_OK; +} + +size_t HandleCloser::GetBufferSize() { + size_t bytes_total = offsetof(HandleCloserInfo, handle_entries); + + for (HandleMap::iterator i = handles_to_close_.begin(); + i != handles_to_close_.end(); ++i) { + size_t bytes_entry = offsetof(HandleListEntry, handle_type) + + (i->first.size() + 1) * sizeof(wchar_t); + for (HandleMap::mapped_type::iterator j = i->second.begin(); + j != i->second.end(); ++j) { + bytes_entry += ((*j).size() + 1) * sizeof(wchar_t); + } + + // Round up to the nearest multiple of word size. + bytes_entry = RoundUpToWordSize(bytes_entry); + bytes_total += bytes_entry; + } + + return bytes_total; +} + +bool HandleCloser::InitializeTargetHandles(TargetProcess* target) { + // Do nothing on an empty list (global pointer already initialized to + // nullptr). + if (handles_to_close_.empty()) + return true; + + size_t bytes_needed = GetBufferSize(); + std::unique_ptr<size_t[]> local_buffer( + new size_t[bytes_needed / sizeof(size_t)]); + + if (!SetupHandleList(local_buffer.get(), bytes_needed)) + return false; + + void* remote_data; + if (!CopyToChildMemory(target->Process(), local_buffer.get(), bytes_needed, + &remote_data)) + return false; + + g_handles_to_close = reinterpret_cast<HandleCloserInfo*>(remote_data); + + ResultCode rc = target->TransferVariable( + "g_handles_to_close", &g_handles_to_close, sizeof(g_handles_to_close)); + + return (SBOX_ALL_OK == rc); +} + +bool HandleCloser::SetupHandleList(void* buffer, size_t buffer_bytes) { + ::ZeroMemory(buffer, buffer_bytes); + HandleCloserInfo* handle_info = reinterpret_cast<HandleCloserInfo*>(buffer); + handle_info->record_bytes = buffer_bytes; + handle_info->num_handle_types = handles_to_close_.size(); + + wchar_t* output = reinterpret_cast<wchar_t*>(&handle_info->handle_entries[0]); + wchar_t* end = reinterpret_cast<wchar_t*>(reinterpret_cast<char*>(buffer) + + buffer_bytes); + for (HandleMap::iterator i = handles_to_close_.begin(); + i != handles_to_close_.end(); ++i) { + if (output >= end) + return false; + HandleListEntry* list_entry = reinterpret_cast<HandleListEntry*>(output); + output = &list_entry->handle_type[0]; + + // Copy the typename and set the offset and count. + i->first.copy(output, i->first.size()); + *(output += i->first.size()) = L'\0'; + output++; + list_entry->offset_to_names = + reinterpret_cast<char*>(output) - reinterpret_cast<char*>(list_entry); + list_entry->name_count = i->second.size(); + + // Copy the handle names. + for (HandleMap::mapped_type::iterator j = i->second.begin(); + j != i->second.end(); ++j) { + output = std::copy((*j).begin(), (*j).end(), output) + 1; + } + + // Round up to the nearest multiple of sizeof(size_t). + output = RoundUpToWordSize(output); + list_entry->record_bytes = + reinterpret_cast<char*>(output) - reinterpret_cast<char*>(list_entry); + } + + DCHECK_EQ(reinterpret_cast<size_t>(output), reinterpret_cast<size_t>(end)); + return output <= end; +} + +bool GetHandleName(HANDLE handle, std::wstring* handle_name) { + static NtQueryObject QueryObject = nullptr; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + ULONG size = MAX_PATH; + std::unique_ptr<UNICODE_STRING, base::FreeDeleter> name; + NTSTATUS result; + + do { + name.reset(static_cast<UNICODE_STRING*>(malloc(size))); + DCHECK(name.get()); + result = + QueryObject(handle, ObjectNameInformation, name.get(), size, &size); + } while (result == STATUS_INFO_LENGTH_MISMATCH || + result == STATUS_BUFFER_OVERFLOW); + + if (NT_SUCCESS(result) && name->Buffer && name->Length) + handle_name->assign(name->Buffer, name->Length / sizeof(wchar_t)); + else + handle_name->clear(); + + return NT_SUCCESS(result); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer.h b/security/sandbox/chromium/sandbox/win/src/handle_closer.h new file mode 100644 index 0000000000..4f023b27b6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer.h @@ -0,0 +1,76 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_CLOSER_H_ +#define SANDBOX_SRC_HANDLE_CLOSER_H_ + +#include <stddef.h> + +#include <map> +#include <set> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/target_process.h" + +namespace sandbox { + +// This is a map of handle-types to names that we need to close in the +// target process. A null set means we need to close all handles of the +// given type. +typedef std::map<const std::wstring, std::set<std::wstring>> HandleMap; + +// Type and set of corresponding handle names to close. +struct HandleListEntry { + size_t record_bytes; // Rounded to sizeof(size_t) bytes. + size_t offset_to_names; // Nul terminated strings of name_count names. + size_t name_count; + wchar_t handle_type[1]; +}; + +// Global parameters and a pointer to the list of entries. +struct HandleCloserInfo { + size_t record_bytes; // Rounded to sizeof(size_t) bytes. + size_t num_handle_types; + struct HandleListEntry handle_entries[1]; +}; + +SANDBOX_INTERCEPT HandleCloserInfo* g_handle_closer_info; + +// Adds handles to close after lockdown. +class HandleCloser { + public: + HandleCloser(); + ~HandleCloser(); + + // Adds a handle that will be closed in the target process after lockdown. + // A nullptr value for handle_name indicates all handles of the specified + // type. An empty string for handle_name indicates the handle is unnamed. + ResultCode AddHandle(const wchar_t* handle_type, const wchar_t* handle_name); + + // Serializes and copies the closer table into the target process. + bool InitializeTargetHandles(TargetProcess* target); + + private: + // Calculates the memory needed to copy the serialized handles list (rounded + // to the nearest machine-word size). + size_t GetBufferSize(); + + // Serializes the handle list into the target process. + bool SetupHandleList(void* buffer, size_t buffer_bytes); + + HandleMap handles_to_close_; + + DISALLOW_COPY_AND_ASSIGN(HandleCloser); +}; + +// Returns the object manager's name associated with a handle +bool GetHandleName(HANDLE handle, std::wstring* handle_name); + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_CLOSER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.cc b/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.cc new file mode 100644 index 0000000000..55fe2d4689 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_closer_agent.h" + +#include <limits.h> +#include <stddef.h> + +#include "base/logging.h" +#include "base/win/static_constants.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Returns type infomation for an NT object. This routine is expected to be +// called for invalid handles so it catches STATUS_INVALID_HANDLE exceptions +// that can be generated when handle tracing is enabled. +NTSTATUS QueryObjectTypeInformation(HANDLE handle, void* buffer, ULONG* size) { + static NtQueryObject QueryObject = nullptr; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + NTSTATUS status = STATUS_UNSUCCESSFUL; + __try { + status = QueryObject(handle, ObjectTypeInformation, buffer, *size, size); + } __except (GetExceptionCode() == STATUS_INVALID_HANDLE + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) { + status = STATUS_INVALID_HANDLE; + } + return status; +} + +} // namespace + +namespace sandbox { + +// Memory buffer mapped from the parent, with the list of handles. +SANDBOX_INTERCEPT HandleCloserInfo* g_handles_to_close = nullptr; + +bool HandleCloserAgent::NeedsHandlesClosed() { + return !!g_handles_to_close; +} + +HandleCloserAgent::HandleCloserAgent() + : dummy_handle_(::CreateEvent(nullptr, false, false, nullptr)) {} + +HandleCloserAgent::~HandleCloserAgent() {} + +// Attempts to stuff |closed_handle| with a duplicated handle for a dummy Event +// with no access. This should allow the handle to be closed, to avoid +// generating EXCEPTION_INVALID_HANDLE on shutdown, but nothing else. For now +// the only supported |type| is Event or File. +bool HandleCloserAgent::AttemptToStuffHandleSlot(HANDLE closed_handle, + const std::wstring& type) { + // Only attempt to stuff Files and Events at the moment. + if (type != L"Event" && type != L"File") { + return true; + } + + if (!dummy_handle_.IsValid()) + return false; + + // This should never happen, as g_dummy is created before closing to_stuff. + DCHECK(dummy_handle_.Get() != closed_handle); + + std::vector<HANDLE> to_close; + + const DWORD original_proc_num = GetCurrentProcessorNumber(); + DWORD proc_num = original_proc_num; + DWORD_PTR original_affinity_mask = + SetThreadAffinityMask(GetCurrentThread(), DWORD_PTR{1} << proc_num); + bool found_handle = false; + BOOL result = FALSE; + + // There is per-processor based free list of handles entries. The free handle + // from current processor's freelist is preferred for reusing, so cycling + // through all possible processors to find closed_handle. + // Start searching from current processor which covers usual cases. + + do { + DWORD_PTR current_mask = DWORD_PTR{1} << proc_num; + + if (original_affinity_mask & current_mask) { + if (proc_num != original_proc_num) { + SetThreadAffinityMask(GetCurrentThread(), current_mask); + } + + HANDLE dup_dummy = nullptr; + size_t count = 16; + + do { + result = + ::DuplicateHandle(::GetCurrentProcess(), dummy_handle_.Get(), + ::GetCurrentProcess(), &dup_dummy, 0, false, 0); + if (!result) { + break; + } + if (dup_dummy != closed_handle) { + to_close.push_back(dup_dummy); + } else { + found_handle = true; + } + } while (count-- && reinterpret_cast<uintptr_t>(dup_dummy) < + reinterpret_cast<uintptr_t>(closed_handle)); + } + + proc_num++; + if (proc_num == sizeof(DWORD_PTR) * 8) { + proc_num = 0; + } + if (proc_num == original_proc_num) { + break; + } + } while (result && !found_handle); + + SetThreadAffinityMask(GetCurrentThread(), original_affinity_mask); + + for (HANDLE h : to_close) + ::CloseHandle(h); + + return found_handle; +} + +// Reads g_handles_to_close and creates the lookup map. +void HandleCloserAgent::InitializeHandlesToClose(bool* is_csrss_connected) { + CHECK(g_handles_to_close); + + // Default to connected state + *is_csrss_connected = true; + + // Grab the header. + HandleListEntry* entry = g_handles_to_close->handle_entries; + for (size_t i = 0; i < g_handles_to_close->num_handle_types; ++i) { + // Set the type name. + wchar_t* input = entry->handle_type; + if (!wcscmp(input, L"ALPC Port")) { + *is_csrss_connected = false; + } + HandleMap::mapped_type& handle_names = handles_to_close_[input]; + input = reinterpret_cast<wchar_t*>(reinterpret_cast<char*>(entry) + + entry->offset_to_names); + // Grab all the handle names. + for (size_t j = 0; j < entry->name_count; ++j) { + std::pair<HandleMap::mapped_type::iterator, bool> name = + handle_names.insert(input); + CHECK(name.second); + input += name.first->size() + 1; + } + + // Move on to the next entry. + entry = reinterpret_cast<HandleListEntry*>(reinterpret_cast<char*>(entry) + + entry->record_bytes); + + DCHECK(reinterpret_cast<wchar_t*>(entry) >= input); + DCHECK(reinterpret_cast<wchar_t*>(entry) - input < + static_cast<ptrdiff_t>(sizeof(size_t) / sizeof(wchar_t))); + } + + // Clean up the memory we copied over. + ::VirtualFree(g_handles_to_close, 0, MEM_RELEASE); + g_handles_to_close = nullptr; +} + +bool HandleCloserAgent::CloseHandles() { + DWORD handle_count = UINT_MAX; + const int kInvalidHandleThreshold = 100; + const size_t kHandleOffset = 4; // Handles are always a multiple of 4. + + if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) + return false; + + // Skip closing these handles when Application Verifier is in use in order to + // avoid invalid-handle exceptions. + if (GetModuleHandleA(base::win::kApplicationVerifierDllName)) + return true; + + // Set up buffers for the type info and the name. + std::vector<BYTE> type_info_buffer(sizeof(OBJECT_TYPE_INFORMATION) + + 32 * sizeof(wchar_t)); + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); + std::wstring handle_name; + HANDLE handle = nullptr; + int invalid_count = 0; + + // Keep incrementing until we hit the number of handles reported by + // GetProcessHandleCount(). If we hit a very long sequence of invalid + // handles we assume that we've run past the end of the table. + while (handle_count && invalid_count < kInvalidHandleThreshold) { + reinterpret_cast<size_t&>(handle) += kHandleOffset; + NTSTATUS rc; + + // Get the type name, reusing the buffer. + ULONG size = static_cast<ULONG>(type_info_buffer.size()); + rc = QueryObjectTypeInformation(handle, type_info, &size); + while (rc == STATUS_INFO_LENGTH_MISMATCH || rc == STATUS_BUFFER_OVERFLOW) { + type_info_buffer.resize(size + sizeof(wchar_t)); + type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); + rc = QueryObjectTypeInformation(handle, type_info, &size); + // Leave padding for the nul terminator. + if (NT_SUCCESS(rc) && size == type_info_buffer.size()) + rc = STATUS_INFO_LENGTH_MISMATCH; + } + if (!NT_SUCCESS(rc) || !type_info->Name.Buffer) { + ++invalid_count; + continue; + } + + --handle_count; + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = L'\0'; + + // Check if we're looking for this type of handle. + HandleMap::iterator result = handles_to_close_.find(type_info->Name.Buffer); + if (result != handles_to_close_.end()) { + HandleMap::mapped_type& names = result->second; + // Empty set means close all handles of this type; otherwise check name. + if (!names.empty()) { + // Move on to the next handle if this name doesn't match. + if (!GetHandleName(handle, &handle_name) || !names.count(handle_name)) + continue; + } + + if (!::SetHandleInformation(handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0)) + return false; + if (!::CloseHandle(handle)) + return false; + // Attempt to stuff this handle with a new dummy Event. + AttemptToStuffHandleSlot(handle, result->first); + } + } + + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.h b/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.h new file mode 100644 index 0000000000..91f8e74c7e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer_agent.h @@ -0,0 +1,46 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ +#define SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ + +#include <string> + +#include "base/macros.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +// Target process code to close the handle list copied over from the broker. +class HandleCloserAgent { + public: + HandleCloserAgent(); + ~HandleCloserAgent(); + + // Reads the serialized list from the broker and creates the lookup map. + // Updates is_csrss_connected based on type of handles closed. + void InitializeHandlesToClose(bool* is_csrss_connected); + + // Closes any handles matching those in the lookup map. + bool CloseHandles(); + + // True if we have handles waiting to be closed. + static bool NeedsHandlesClosed(); + + private: + // Attempt to stuff a closed handle with a dummy Event. + bool AttemptToStuffHandleSlot(HANDLE closed_handle, const std::wstring& type); + + HandleMap handles_to_close_; + base::win::ScopedHandle dummy_handle_; + + DISALLOW_COPY_AND_ASSIGN(HandleCloserAgent); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_CLOSER_AGENT_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/handle_closer_test.cc b/security/sandbox/chromium/sandbox/win/src/handle_closer_test.cc new file mode 100644 index 0000000000..7406f77592 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_closer_test.cc @@ -0,0 +1,297 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <limits.h> +#include <stddef.h> + +#include "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_closer_agent.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const wchar_t* kFileExtensions[] = {L".1", L".2", L".3", L".4"}; + +// Returns a handle to a unique marker file that can be retrieved between runs. +HANDLE GetMarkerFile(const wchar_t* extension) { + wchar_t path_buffer[MAX_PATH + 1]; + CHECK(::GetTempPath(MAX_PATH, path_buffer)); + std::wstring marker_path = path_buffer; + marker_path += L"\\sbox_marker_"; + + // Generate a unique value from the exe's size and timestamp. + CHECK(::GetModuleFileName(nullptr, path_buffer, MAX_PATH)); + base::win::ScopedHandle module( + ::CreateFile(path_buffer, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + CHECK(module.IsValid()); + FILETIME timestamp; + CHECK(::GetFileTime(module.Get(), ×tamp, nullptr, nullptr)); + marker_path += + base::StringPrintf(L"%08x%08x%08x", ::GetFileSize(module.Get(), nullptr), + timestamp.dwLowDateTime, timestamp.dwHighDateTime); + marker_path += extension; + + // Make the file delete-on-close so cleanup is automatic. + return CreateFile(marker_path.c_str(), FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr); +} + +// Returns type infomation for an NT object. This routine is expected to be +// called for invalid handles so it catches STATUS_INVALID_HANDLE exceptions +// that can be generated when handle tracing is enabled. +NTSTATUS QueryObjectTypeInformation(HANDLE handle, void* buffer, ULONG* size) { + static NtQueryObject QueryObject = nullptr; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + NTSTATUS status = STATUS_UNSUCCESSFUL; + __try { + status = QueryObject(handle, ObjectTypeInformation, buffer, *size, size); + } __except (GetExceptionCode() == STATUS_INVALID_HANDLE + ? EXCEPTION_EXECUTE_HANDLER + : EXCEPTION_CONTINUE_SEARCH) { + status = STATUS_INVALID_HANDLE; + } + return status; +} + +// Used by the thread pool tests. +HANDLE finish_event; +const int kWaitCount = 20; + +} // namespace + +namespace sandbox { + +// Checks for the presence of a list of files (in object path form). +// Format: CheckForFileHandle (Y|N) \path\to\file1 [\path\to\file2 ...] +// - Y or N depending if the file should exist or not. +SBOX_TESTS_COMMAND int CheckForFileHandles(int argc, wchar_t** argv) { + if (argc < 2) + return SBOX_TEST_FAILED_TO_RUN_TEST; + bool should_find = argv[0][0] == L'Y'; + if (argv[0][1] != L'\0' || (!should_find && argv[0][0] != L'N')) + return SBOX_TEST_FAILED_TO_RUN_TEST; + + static int state = BEFORE_INIT; + switch (state++) { + case BEFORE_INIT: + // Create a unique marker file that is open while the test is running. + // The handles leak, but it will be closed by the test or on exit. + for (const wchar_t* kExtension : kFileExtensions) + CHECK_NE(GetMarkerFile(kExtension), INVALID_HANDLE_VALUE); + return SBOX_TEST_SUCCEEDED; + + case AFTER_REVERT: { + // Brute force the handle table to find what we're looking for. + DWORD handle_count = UINT_MAX; + const int kInvalidHandleThreshold = 100; + const size_t kHandleOffset = 4; // Handles are always a multiple of 4. + HANDLE handle = nullptr; + int invalid_count = 0; + std::wstring handle_name; + + if (!::GetProcessHandleCount(::GetCurrentProcess(), &handle_count)) + return SBOX_TEST_FAILED_TO_RUN_TEST; + + while (handle_count && invalid_count < kInvalidHandleThreshold) { + reinterpret_cast<size_t&>(handle) += kHandleOffset; + if (GetHandleName(handle, &handle_name)) { + for (int i = 1; i < argc; ++i) { + if (handle_name == argv[i]) + return should_find ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; + } + --handle_count; + } else { + ++invalid_count; + } + } + + return should_find ? SBOX_TEST_FAILED : SBOX_TEST_SUCCEEDED; + } + + default: // Do nothing. + break; + } + + return SBOX_TEST_SUCCEEDED; +} + +// Checks that supplied handle is an Event and it's not waitable. +// Format: CheckForEventHandles +SBOX_TESTS_COMMAND int CheckForEventHandles(int argc, wchar_t** argv) { + static int state = BEFORE_INIT; + static std::vector<HANDLE> to_check; + + switch (state++) { + case BEFORE_INIT: + // Create a unique marker file that is open while the test is running. + for (const wchar_t* kExtension : kFileExtensions) { + HANDLE handle = GetMarkerFile(kExtension); + CHECK_NE(handle, INVALID_HANDLE_VALUE); + to_check.push_back(handle); + } + return SBOX_TEST_SUCCEEDED; + + case AFTER_REVERT: + for (HANDLE handle : to_check) { + // Set up buffers for the type info and the name. + std::vector<BYTE> type_info_buffer(sizeof(OBJECT_TYPE_INFORMATION) + + 32 * sizeof(wchar_t)); + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(&(type_info_buffer[0])); + NTSTATUS rc; + + // Get the type name, reusing the buffer. + ULONG size = static_cast<ULONG>(type_info_buffer.size()); + rc = QueryObjectTypeInformation(handle, type_info, &size); + while (rc == STATUS_INFO_LENGTH_MISMATCH || + rc == STATUS_BUFFER_OVERFLOW) { + type_info_buffer.resize(size + sizeof(wchar_t)); + type_info = reinterpret_cast<OBJECT_TYPE_INFORMATION*>( + &(type_info_buffer[0])); + rc = QueryObjectTypeInformation(handle, type_info, &size); + // Leave padding for the nul terminator. + if (NT_SUCCESS(rc) && size == type_info_buffer.size()) + rc = STATUS_INFO_LENGTH_MISMATCH; + } + + CHECK(NT_SUCCESS(rc)); + CHECK(type_info->Name.Buffer); + + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = + L'\0'; + + // Should be an Event now. + CHECK_EQ(wcslen(type_info->Name.Buffer), 5U); + CHECK_EQ(wcscmp(L"Event", type_info->Name.Buffer), 0); + + // Should not be able to wait. + CHECK_EQ(WaitForSingleObject(handle, INFINITE), WAIT_FAILED); + + // Should be able to close. + CHECK(::CloseHandle(handle)); + } + return SBOX_TEST_SUCCEEDED; + + default: // Do nothing. + break; + } + + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleCloserTest, CheckForMarkerFiles) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + + std::wstring command = std::wstring(L"CheckForFileHandles Y"); + for (const wchar_t* kExtension : kFileExtensions) { + std::wstring handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kExtension)); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + command += (L" "); + command += handle_name; + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str())) + << "Failed: " << command; +} + +TEST(HandleCloserTest, CloseMarkerFiles) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + std::wstring command = std::wstring(L"CheckForFileHandles N"); + for (const wchar_t* kExtension : kFileExtensions) { + std::wstring handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kExtension)); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + CHECK_EQ(policy->AddKernelObjectToClose(L"File", handle_name.c_str()), + SBOX_ALL_OK); + command += (L" "); + command += handle_name; + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str())) + << "Failed: " << command; +} + +TEST(HandleCloserTest, CheckStuffedHandle) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + + for (const wchar_t* kExtension : kFileExtensions) { + std::wstring handle_name; + base::win::ScopedHandle marker(GetMarkerFile(kExtension)); + CHECK(marker.IsValid()); + CHECK(sandbox::GetHandleName(marker.Get(), &handle_name)); + CHECK_EQ(policy->AddKernelObjectToClose(L"File", handle_name.c_str()), + SBOX_ALL_OK); + } + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckForEventHandles")); +} + +void WINAPI ThreadPoolTask(void* event, BOOLEAN timeout) { + static volatile LONG waiters_remaining = kWaitCount; + CHECK(!timeout); + CHECK(::CloseHandle(event)); + if (::InterlockedDecrement(&waiters_remaining) == 0) + CHECK(::SetEvent(finish_event)); +} + +// Run a thread pool inside a sandbox without a CSRSS connection. +SBOX_TESTS_COMMAND int RunThreadPool(int argc, wchar_t** argv) { + HANDLE wait_list[20]; + finish_event = ::CreateEvent(nullptr, true, false, nullptr); + CHECK(finish_event); + + // Set up a bunch of waiters. + HANDLE pool = nullptr; + for (int i = 0; i < kWaitCount; ++i) { + HANDLE event = ::CreateEvent(nullptr, true, false, nullptr); + CHECK(event); + CHECK(::RegisterWaitForSingleObject(&pool, event, ThreadPoolTask, event, + INFINITE, WT_EXECUTEONLYONCE)); + wait_list[i] = event; + } + + // Signal all the waiters. + for (int i = 0; i < kWaitCount; ++i) + CHECK(::SetEvent(wait_list[i])); + + CHECK_EQ(::WaitForSingleObject(finish_event, INFINITE), WAIT_OBJECT_0); + CHECK(::CloseHandle(finish_event)); + + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleCloserTest, RunThreadPool) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(AFTER_REVERT); + + // Sandbox policy will determine which platforms to disconnect CSRSS and when + // to close the CSRSS handle. + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"RunThreadPool")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.cc new file mode 100644 index 0000000000..611e33d2a6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_dispatcher.h" + +#include <stdint.h> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/handle_interception.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" + +namespace sandbox { + +HandleDispatcher::HandleDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall duplicate_handle_proxy = { + {IpcTag::DUPLICATEHANDLEPROXY, + {VOIDPTR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &HandleDispatcher::DuplicateHandleProxy)}; + + ipc_calls_.push_back(duplicate_handle_proxy); +} + +bool HandleDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + // We perform no interceptions for handles right now. + switch (service) { + case IpcTag::DUPLICATEHANDLEPROXY: + return true; + + default: + return false; + } +} + +bool HandleDispatcher::DuplicateHandleProxy(IPCInfo* ipc, + HANDLE source_handle, + uint32_t target_process_id, + uint32_t desired_access, + uint32_t options) { + static NtQueryObject QueryObject = NULL; + if (!QueryObject) + ResolveNTFunctionPtr("NtQueryObject", &QueryObject); + + // Get a copy of the handle for use in the broker process. + HANDLE handle_temp; + if (!::DuplicateHandle(ipc->client_info->process, source_handle, + ::GetCurrentProcess(), &handle_temp, + 0, FALSE, DUPLICATE_SAME_ACCESS | options)) { + ipc->return_info.win32_result = ::GetLastError(); + return false; + } + options &= ~DUPLICATE_CLOSE_SOURCE; + base::win::ScopedHandle handle(handle_temp); + + // Get the object type (32 characters is safe; current max is 14). + BYTE buffer[sizeof(OBJECT_TYPE_INFORMATION) + 32 * sizeof(wchar_t)]; + OBJECT_TYPE_INFORMATION* type_info = + reinterpret_cast<OBJECT_TYPE_INFORMATION*>(buffer); + ULONG size = sizeof(buffer) - sizeof(wchar_t); + NTSTATUS error = + QueryObject(handle.Get(), ObjectTypeInformation, type_info, size, &size); + if (!NT_SUCCESS(error)) { + ipc->return_info.nt_status = error; + return false; + } + type_info->Name.Buffer[type_info->Name.Length / sizeof(wchar_t)] = L'\0'; + + CountedParameterSet<HandleTarget> params; + params[HandleTarget::NAME] = ParamPickerMake(type_info->Name.Buffer); + params[HandleTarget::TARGET] = ParamPickerMake(target_process_id); + + EvalResult eval = policy_base_->EvalPolicy(IpcTag::DUPLICATEHANDLEPROXY, + params.GetBase()); + ipc->return_info.win32_result = + HandlePolicy::DuplicateHandleProxyAction(eval, handle.Get(), + target_process_id, + &ipc->return_info.handle, + desired_access, options); + return true; +} + +} // namespace sandbox + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.h new file mode 100644 index 0000000000..6f9adbc10b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_dispatcher.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_DISPATCHER_H_ +#define SANDBOX_SRC_HANDLE_DISPATCHER_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles handle-related IPC calls. +class HandleDispatcher : public Dispatcher { + public: + explicit HandleDispatcher(PolicyBase* policy_base); + ~HandleDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to + // TargetServices::DuplicateHandle() in the target. + bool DuplicateHandleProxy(IPCInfo* ipc, + HANDLE source_handle, + uint32_t target_process_id, + uint32_t desired_access, + uint32_t options); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(HandleDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_DISPATCHER_H_ + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_inheritance_test.cc b/security/sandbox/chromium/sandbox/win/src/handle_inheritance_test.cc new file mode 100644 index 0000000000..c91903c809 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_inheritance_test.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdio.h> + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int HandleInheritanceTests_PrintToStdout(int argc, + wchar_t** argv) { + printf("Example output to stdout\n"); + return SBOX_TEST_SUCCEEDED; +} + +TEST(HandleInheritanceTests, TestStdoutInheritance) { + base::ScopedTempDir temp_directory; + base::FilePath temp_file_name; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + ASSERT_TRUE( + CreateTemporaryFileInDir(temp_directory.GetPath(), &temp_file_name)); + + SECURITY_ATTRIBUTES attrs = {}; + attrs.nLength = sizeof(attrs); + attrs.bInheritHandle = true; + base::win::ScopedHandle tmp_handle( + CreateFile(temp_file_name.value().c_str(), GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, &attrs, + OPEN_EXISTING, 0, nullptr)); + ASSERT_TRUE(tmp_handle.IsValid()); + + TestRunner runner; + ASSERT_EQ(SBOX_ALL_OK, runner.GetPolicy()->SetStdoutHandle(tmp_handle.Get())); + int result = runner.RunTest(L"HandleInheritanceTests_PrintToStdout"); + ASSERT_EQ(SBOX_TEST_SUCCEEDED, result); + + std::string data; + ASSERT_TRUE(base::ReadFileToString(base::FilePath(temp_file_name), &data)); + // Redirection uses a feature that was added in Windows Vista. + ASSERT_EQ("Example output to stdout\r\n", data); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/handle_interception.cc b/security/sandbox/chromium/sandbox/win/src/handle_interception.cc new file mode 100644 index 0000000000..53db4a8b27 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_interception.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +ResultCode DuplicateHandleProxy(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + *target_handle = NULL; + + void* memory = GetGlobalIPCMemory(); + if (NULL == memory) + return SBOX_ERROR_NO_SPACE; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::DUPLICATEHANDLEPROXY, + source_handle, target_process_id, + desired_access, options, &answer); + if (SBOX_ALL_OK != code) + return code; + + if (answer.win32_result) { + ::SetLastError(answer.win32_result); + mozilla::sandboxing::LogBlocked("DuplicateHandle"); + return SBOX_ERROR_GENERIC; + } + + *target_handle = answer.handle; + mozilla::sandboxing::LogAllowed("DuplicateHandle"); + return SBOX_ALL_OK; +} + +} // namespace sandbox + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_interception.h b/security/sandbox/chromium/sandbox/win/src/handle_interception.h new file mode 100644 index 0000000000..6f60811f17 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_interception.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +#ifndef SANDBOX_SRC_HANDLE_INTERCEPTION_H_ +#define SANDBOX_SRC_HANDLE_INTERCEPTION_H_ + +namespace sandbox { + +// TODO(jschuh) Add an interception to catch dangerous DuplicateHandle calls. + +ResultCode DuplicateHandleProxy(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options); + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_INTERCEPTION_H_ + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_policy.cc b/security/sandbox/chromium/sandbox/win/src/handle_policy.cc new file mode 100644 index 0000000000..fa3295ae3f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_policy.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/handle_policy.h" + +#include <string> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/broker_services.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" + +namespace sandbox { + +bool HandlePolicy::GenerateRules(const wchar_t* type_name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + PolicyRule duplicate_rule(ASK_BROKER); + + switch (semantics) { + case TargetPolicy::HANDLES_DUP_ANY: { + if (!duplicate_rule.AddNumberMatch(IF_NOT, HandleTarget::TARGET, + ::GetCurrentProcessId(), EQUAL)) { + return false; + } + break; + } + + case TargetPolicy::HANDLES_DUP_BROKER: { + if (!duplicate_rule.AddNumberMatch(IF, HandleTarget::TARGET, + ::GetCurrentProcessId(), EQUAL)) { + return false; + } + break; + } + + default: + return false; + } + if (!duplicate_rule.AddStringMatch(IF, HandleTarget::NAME, type_name, + CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IpcTag::DUPLICATEHANDLEPROXY, &duplicate_rule)) { + return false; + } + return true; +} + +DWORD HandlePolicy::DuplicateHandleProxyAction(EvalResult eval_result, + HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + // The only action supported is ASK_BROKER which means duplicate the handle. + if (ASK_BROKER != eval_result) { + return ERROR_ACCESS_DENIED; + } + + base::win::ScopedHandle remote_target_process; + if (target_process_id != ::GetCurrentProcessId()) { + // Sandboxed children are dynamic, so we check that manually. + if (!BrokerServicesBase::GetInstance()->IsSafeDuplicationTarget( + target_process_id)) { + return ERROR_ACCESS_DENIED; + } + + remote_target_process.Set(::OpenProcess(PROCESS_DUP_HANDLE, FALSE, + target_process_id)); + if (!remote_target_process.IsValid()) + return ::GetLastError(); + } + + // If the policy didn't block us and we have no valid target, then the broker + // (this process) is the valid target. + HANDLE target_process = remote_target_process.IsValid() ? + remote_target_process.Get() : ::GetCurrentProcess(); + if (!::DuplicateHandle(::GetCurrentProcess(), source_handle, target_process, + target_handle, desired_access, FALSE, + options)) { + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +} // namespace sandbox + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_policy.h b/security/sandbox/chromium/sandbox/win/src/handle_policy.h new file mode 100644 index 0000000000..29ce5ab666 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_policy.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_HANDLE_POLICY_H_ +#define SANDBOX_SRC_HANDLE_POLICY_H_ + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +enum EvalResult; + +// This class centralizes most of the knowledge related to handle policy. +class HandlePolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for handles, in particular duplicate action. + static bool GenerateRules(const wchar_t* type_name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Processes a 'TargetPolicy::DuplicateHandle()' request from the target. + static DWORD DuplicateHandleProxyAction(EvalResult eval_result, + HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_HANDLE_POLICY_H_ + diff --git a/security/sandbox/chromium/sandbox/win/src/handle_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/handle_policy_test.cc new file mode 100644 index 0000000000..11382da811 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/handle_policy_test.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Just waits for the supplied number of milliseconds. +SBOX_TESTS_COMMAND int Handle_WaitProcess(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + ::Sleep(::wcstoul(argv[0], NULL, 10)); + return SBOX_TEST_TIMED_OUT; +} + +// Attempts to duplicate an event handle into the target process. +SBOX_TESTS_COMMAND int Handle_DuplicateEvent(int argc, wchar_t **argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + // Create a test event to use as a handle. + base::win::ScopedHandle test_event; + test_event.Set(::CreateEvent(NULL, TRUE, TRUE, NULL)); + if (!test_event.IsValid()) + return SBOX_TEST_FIRST_ERROR; + + // Get the target process ID. + DWORD target_process_id = ::wcstoul(argv[0], NULL, 10); + + HANDLE handle = NULL; + ResultCode result = SandboxFactory::GetTargetServices()->DuplicateHandle( + test_event.Get(), target_process_id, &handle, 0, DUPLICATE_SAME_ACCESS); + + return (result == SBOX_ALL_OK) ? SBOX_TEST_SUCCEEDED : SBOX_TEST_DENIED; +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicateHandle) { + TestRunner target; + TestRunner runner; + + // Kick off an asynchronous target process for testing. + target.SetAsynchronous(true); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"Handle_WaitProcess 30000")); + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + target.process_id()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Now successfully open the event after adding a duplicate handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicatePeerHandle) { + TestRunner target; + TestRunner runner; + + // Kick off an asynchronous target process for testing. + target.SetAsynchronous(true); + target.SetUnsandboxed(true); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, target.RunTest(L"Handle_WaitProcess 30000")); + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + target.process_id()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Now successfully open the event after adding a duplicate handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +// Tests that duplicating an object works only when the policy allows it. +TEST(HandlePolicyTest, DuplicateBrokerHandle) { + TestRunner runner; + + // First test that we fail to open the event. + base::string16 cmd_line = base::StringPrintf(L"Handle_DuplicateEvent %d", + ::GetCurrentProcessId()); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + // Add the peer rule and make sure we fail again. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_ANY, + L"Event")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(cmd_line.c_str())); + + + // Now successfully open the event after adding a broker handle rule. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_HANDLES, + TargetPolicy::HANDLES_DUP_BROKER, + L"Event")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(cmd_line.c_str())); +} + +} // namespace sandbox + diff --git a/security/sandbox/chromium/sandbox/win/src/heap_helper.cc b/security/sandbox/chromium/sandbox/win/src/heap_helper.cc new file mode 100644 index 0000000000..b0f4498fea --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/heap_helper.cc @@ -0,0 +1,124 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/heap_helper.h" + +#include <windows.h> + +#include "base/memory/ref_counted.h" +#include "base/win/windows_version.h" + +namespace sandbox { +namespace { +#pragma pack(1) + +// These are undocumented, but readily found on the internet. +constexpr DWORD kHeapClass8 = 0x00008000; // CSR port heap +constexpr DWORD kHeapClassMask = 0x0000f000; + +constexpr DWORD kHeapSegmentSignature = 0xffeeffee; +constexpr DWORD kHeapSignature = 0xeeffeeff; + +typedef struct _HEAP_ENTRY { + PVOID Data1; + PVOID Data2; +} HEAP_ENTRY, *PHEAP_ENTRY; + +// The _HEAP struct is not documented, so char arrays are used to space out the +// struct of the fields that are not relevant. However, this spacing is +// different because of the different pointer widths between 32 and 64-bit. +// So 32 and 64 bit structs are defined. +struct _HEAP_32 { + HEAP_ENTRY HeapEntry; + DWORD SegmentSignature; + DWORD SegmentFlags; + LIST_ENTRY SegmentListEntry; + struct _HEAP_32* Heap; + char Unknown0[0x24]; + // Offset 0x40 + DWORD Flags; + // Offset 0x60 + char Unknown1[0x1c]; + DWORD Signature; + // Other stuff that is not relevant. +}; + +struct _HEAP_64 { + HEAP_ENTRY HeapEntry; + DWORD SegmentSignature; + DWORD SegmentFlags; + LIST_ENTRY SegmentListEntry; + struct _HEAP_64* Heap; + char Unknown0[0x40]; + // Offset 0x70 + DWORD Flags; + // Offset 0x98 + char Unknown1[0x24]; + DWORD Signature; + // Other stuff that is not relevant. +}; + +#if defined(_WIN64) +using _HEAP = _HEAP_64; +#else // defined(_WIN64) +using _HEAP = _HEAP_32; +#endif // defined(_WIN64) + +bool ValidateHeap(_HEAP* heap) { + if (heap->SegmentSignature != kHeapSegmentSignature) + return false; + if (heap->Heap != heap) + return false; + if (heap->Signature != kHeapSignature) + return false; + return true; +} + +} // namespace + +bool HeapFlags(HANDLE handle, DWORD* flags) { + if (!handle || !flags) { + // This is an error. + return false; + } + _HEAP* heap = reinterpret_cast<_HEAP*>(handle); + if (!ValidateHeap(heap)) { + DLOG(ERROR) << "unable to validate heap"; + return false; + } + *flags = heap->Flags; + return true; +} + +HANDLE FindCsrPortHeap() { + if (base::win::GetVersion() < base::win::Version::WIN10) { + // This functionality has not been verified on versions before Win10. + return nullptr; + } + DWORD number_of_heaps = ::GetProcessHeaps(0, nullptr); + std::unique_ptr<HANDLE[]> all_heaps(new HANDLE[number_of_heaps]); + if (::GetProcessHeaps(number_of_heaps, all_heaps.get()) != number_of_heaps) + return nullptr; + + // Search for the CSR port heap handle, identified purely based on flags. + HANDLE csr_port_heap = nullptr; + for (size_t i = 0; i < number_of_heaps; ++i) { + HANDLE handle = all_heaps[i]; + DWORD flags = 0; + if (!HeapFlags(handle, &flags)) { + DLOG(ERROR) << "Unable to get flags for this heap"; + continue; + } + if ((flags & kHeapClassMask) == kHeapClass8) { + if (csr_port_heap) { + DLOG(ERROR) << "Found multiple suitable CSR Port heaps"; + return nullptr; + } + csr_port_heap = handle; + } + } + return csr_port_heap; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/heap_helper.h b/security/sandbox/chromium/sandbox/win/src/heap_helper.h new file mode 100644 index 0000000000..302a3c4e29 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/heap_helper.h @@ -0,0 +1,26 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_HEAP_HELPER_H_ +#define SANDBOX_WIN_SRC_HEAP_HELPER_H_ + +#include <windows.h> + +#include "base/win/windows_version.h" + +namespace sandbox { +// These helper functions are not expected to be used generally, but are exposed +// only to allow direct testing of this functionality. + +// Return the flags for this heap handle. Limited verification of the handle is +// performed. No verification of the flags is performed. +bool HeapFlags(HANDLE handle, DWORD* flags); + +// Return the handle to the CSR Port Heap, return nullptr if none or more than +// one candidate was found. +HANDLE FindCsrPortHeap(); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_HEAP_HELPER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/integrity_level_test.cc b/security/sandbox/chromium/sandbox/win/src/integrity_level_test.cc new file mode 100644 index 0000000000..a4788af028 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/integrity_level_test.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> + +#include <atlsecurity.h> + +#include "base/process/process_info.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int CheckUntrustedIntegrityLevel(int argc, wchar_t** argv) { + return (base::GetCurrentProcessIntegrityLevel() == base::UNTRUSTED_INTEGRITY) + ? SBOX_TEST_SUCCEEDED + : SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int CheckLowIntegrityLevel(int argc, wchar_t** argv) { + return (base::GetCurrentProcessIntegrityLevel() == base::LOW_INTEGRITY) + ? SBOX_TEST_SUCCEEDED + : SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int CheckIntegrityLevel(int argc, wchar_t** argv) { + ATL::CAccessToken token; + if (!token.GetEffectiveToken(TOKEN_READ)) + return SBOX_TEST_FAILED; + + char* buffer[100]; + DWORD buf_size = 100; + if (!::GetTokenInformation(token.GetHandle(), TokenIntegrityLevel, + reinterpret_cast<void*>(buffer), buf_size, + &buf_size)) + return SBOX_TEST_FAILED; + + TOKEN_MANDATORY_LABEL* label = + reinterpret_cast<TOKEN_MANDATORY_LABEL*>(buffer); + + PSID sid_low = nullptr; + if (!::ConvertStringSidToSid(L"S-1-16-4096", &sid_low)) + return SBOX_TEST_FAILED; + + bool is_low_sid = ::EqualSid(label->Label.Sid, sid_low); + + ::LocalFree(sid_low); + + if (is_low_sid) + return SBOX_TEST_SUCCEEDED; + + return SBOX_TEST_DENIED; +} + +TEST(IntegrityLevelTest, TestLowILReal) { + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + runner.GetPolicy()->SetAlternateDesktop(true); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); +} + +TEST(DelayedIntegrityLevelTest, TestLowILDelayed) { + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckIntegrityLevel")); + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"CheckIntegrityLevel")); +} + +TEST(IntegrityLevelTest, TestNoILChange) { + TestRunner runner(JOB_LOCKDOWN, USER_INTERACTIVE, USER_INTERACTIVE); + + runner.SetTimeout(INFINITE); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"CheckIntegrityLevel")); +} + +TEST(IntegrityLevelTest, TestUntrustedIL) { + TestRunner runner(JOB_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_UNTRUSTED); + runner.GetPolicy()->SetLockdownDefaultDacl(); + + runner.SetTimeout(INFINITE); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"CheckUntrustedIntegrityLevel")); +} + +TEST(IntegrityLevelTest, TestLowIL) { + TestRunner runner(JOB_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner.GetPolicy()->SetIntegrityLevel(INTEGRITY_LEVEL_LOW); + runner.GetPolicy()->SetDelayedIntegrityLevel(INTEGRITY_LEVEL_LOW); + runner.GetPolicy()->SetLockdownDefaultDacl(); + + runner.SetTimeout(INFINITE); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"CheckLowIntegrityLevel")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interception.cc b/security/sandbox/chromium/sandbox/win/src/interception.cc new file mode 100644 index 0000000000..b2c0bc47ea --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception.cc @@ -0,0 +1,512 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// For information about interceptions as a whole see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include "sandbox/win/src/interception.h" + +#include <stddef.h> + +#include <memory> +#include <set> +#include <string> + +#include "base/logging.h" +#include "base/scoped_native_library.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_rand.h" +#include "sandbox/win/src/service_resolver.h" +#include "sandbox/win/src/target_interceptions.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +// Standard allocation granularity and page size for Windows. +const size_t kAllocGranularity = 65536; +const size_t kPageSize = 4096; + +} // namespace + +namespace internal { + +// Find a random offset within 64k and aligned to ceil(log2(size)). +size_t GetGranularAlignedRandomOffset(size_t size) { + CHECK_LE(size, kAllocGranularity); + unsigned int offset; + + do { + GetRandom(&offset); + offset &= (kAllocGranularity - 1); + } while (offset > (kAllocGranularity - size)); + + // Find an alignment between 64 and the page size (4096). + size_t align_size = kPageSize; + for (size_t new_size = align_size / 2; new_size >= size; new_size /= 2) { + align_size = new_size; + } + return offset & ~(align_size - 1); +} + +} // namespace internal + +SANDBOX_INTERCEPT SharedMemory* g_interceptions; + +// Table of the unpatched functions that we intercept. Mapped from the parent. +SANDBOX_INTERCEPT OriginalFunctions g_originals = {nullptr}; + +// Magic constant that identifies that this function is not to be patched. +const char kUnloadDLLDummyFunction[] = "@"; + +InterceptionManager::InterceptionData::InterceptionData() {} + +InterceptionManager::InterceptionData::InterceptionData( + const InterceptionData& other) = default; + +InterceptionManager::InterceptionData::~InterceptionData() {} + +InterceptionManager::InterceptionManager(TargetProcess* child_process, + bool relaxed) + : child_(child_process), names_used_(false), relaxed_(relaxed) { + child_->AddRef(); +} +InterceptionManager::~InterceptionManager() { + child_->Release(); +} + +bool InterceptionManager::AddToPatchedFunctions( + const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const void* replacement_code_address, + InterceptorId id) { + InterceptionData function; + function.type = interception_type; + function.id = id; + function.dll = dll_name; + function.function = function_name; + function.interceptor_address = replacement_code_address; + + interceptions_.push_back(function); + return true; +} + +bool InterceptionManager::AddToPatchedFunctions( + const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const char* replacement_function_name, + InterceptorId id) { + InterceptionData function; + function.type = interception_type; + function.id = id; + function.dll = dll_name; + function.function = function_name; + function.interceptor = replacement_function_name; + function.interceptor_address = nullptr; + + interceptions_.push_back(function); + names_used_ = true; + return true; +} + +bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) { + InterceptionData module_to_unload; + module_to_unload.type = INTERCEPTION_UNLOAD_MODULE; + module_to_unload.dll = dll_name; + // The next two are dummy values that make the structures regular, instead + // of having special cases. They should not be used. + module_to_unload.function = kUnloadDLLDummyFunction; + module_to_unload.interceptor_address = reinterpret_cast<void*>(1); + + interceptions_.push_back(module_to_unload); + return true; +} + +ResultCode InterceptionManager::InitializeInterceptions() { + if (interceptions_.empty()) + return SBOX_ALL_OK; // Nothing to do here + + size_t buffer_bytes = GetBufferSize(); + std::unique_ptr<char[]> local_buffer(new char[buffer_bytes]); + + if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes)) + return SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_CONFIG_BUFFER; + + void* remote_buffer; + if (!CopyToChildMemory(child_->Process(), local_buffer.get(), buffer_bytes, + &remote_buffer)) + return SBOX_ERROR_CANNOT_COPY_DATA_TO_CHILD; + + bool hot_patch_needed = (0 != buffer_bytes); + ResultCode rc = PatchNtdll(hot_patch_needed); + + if (rc != SBOX_ALL_OK) + return rc; + + g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer); + rc = child_->TransferVariable("g_interceptions", &g_interceptions, + sizeof(g_interceptions)); + return rc; +} + +size_t InterceptionManager::GetBufferSize() const { + std::set<std::wstring> dlls; + size_t buffer_bytes = 0; + + for (const auto& interception : interceptions_) { + // skip interceptions that are performed from the parent + if (!IsInterceptionPerformedByChild(interception)) + continue; + + if (!dlls.count(interception.dll)) { + // NULL terminate the dll name on the structure + size_t dll_name_bytes = (interception.dll.size() + 1) * sizeof(wchar_t); + + // include the dll related size + buffer_bytes += RoundUpToMultiple( + offsetof(DllPatchInfo, dll_name) + dll_name_bytes, sizeof(size_t)); + dlls.insert(interception.dll); + } + + // we have to NULL terminate the strings on the structure + size_t strings_chars = + interception.function.size() + interception.interceptor.size() + 2; + + // a new FunctionInfo is required per function + size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars; + record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t)); + buffer_bytes += record_bytes; + } + + if (0 != buffer_bytes) + // add the part of SharedMemory that we have not counted yet + buffer_bytes += offsetof(SharedMemory, dll_list); + + return buffer_bytes; +} + +// Basically, walk the list of interceptions moving them to the config buffer, +// but keeping together all interceptions that belong to the same dll. +// The config buffer is a local buffer, not the one allocated on the child. +bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) { + if (0 == buffer_bytes) + return true; + + DCHECK(buffer_bytes > sizeof(SharedMemory)); + + SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer); + DllPatchInfo* dll_info = shared_memory->dll_list; + int num_dlls = 0; + + shared_memory->interceptor_base = + names_used_ ? child_->MainModule() : nullptr; + + buffer_bytes -= offsetof(SharedMemory, dll_list); + buffer = dll_info; + + std::list<InterceptionData>::iterator it = interceptions_.begin(); + for (; it != interceptions_.end();) { + // skip interceptions that are performed from the parent + if (!IsInterceptionPerformedByChild(*it)) { + ++it; + continue; + } + + const std::wstring dll = it->dll; + if (!SetupDllInfo(*it, &buffer, &buffer_bytes)) + return false; + + // walk the interceptions from this point, saving the ones that are + // performed on this dll, and removing the entry from the list. + // advance the iterator before removing the element from the list + std::list<InterceptionData>::iterator rest = it; + for (; rest != interceptions_.end();) { + if (rest->dll == dll) { + if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info)) + return false; + if (it == rest) + ++it; + rest = interceptions_.erase(rest); + } else { + ++rest; + } + } + dll_info = reinterpret_cast<DllPatchInfo*>(buffer); + ++num_dlls; + } + + shared_memory->num_intercepted_dlls = num_dlls; + return true; +} + +// Fills up just the part that depends on the dll, not the info that depends on +// the actual interception. +bool InterceptionManager::SetupDllInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes) const { + DCHECK(buffer_bytes); + DCHECK(buffer); + DCHECK(*buffer); + + DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer); + + // the strings have to be zero terminated + size_t required = offsetof(DllPatchInfo, dll_name) + + (data.dll.size() + 1) * sizeof(wchar_t); + required = RoundUpToMultiple(required, sizeof(size_t)); + if (*buffer_bytes < required) + return false; + + *buffer_bytes -= required; + *buffer = reinterpret_cast<char*>(*buffer) + required; + + // set up the dll info to be what we know about it at this time + dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE); + dll_info->record_bytes = required; + dll_info->offset_to_functions = required; + dll_info->num_functions = 0; + data.dll.copy(dll_info->dll_name, data.dll.size()); + dll_info->dll_name[data.dll.size()] = L'\0'; + + return true; +} + +bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes, + DllPatchInfo* dll_info) const { + DCHECK(buffer_bytes); + DCHECK(buffer); + DCHECK(*buffer); + + if ((dll_info->unload_module) && (data.function != kUnloadDLLDummyFunction)) { + // Can't specify a dll for both patch and unload. + NOTREACHED(); + } + + FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer); + + size_t name_bytes = data.function.size(); + size_t interceptor_bytes = data.interceptor.size(); + + // the strings at the end of the structure are zero terminated + size_t required = + offsetof(FunctionInfo, function) + name_bytes + interceptor_bytes + 2; + required = RoundUpToMultiple(required, sizeof(size_t)); + if (*buffer_bytes < required) + return false; + + // update the caller's values + *buffer_bytes -= required; + *buffer = reinterpret_cast<char*>(*buffer) + required; + + function->record_bytes = required; + function->type = data.type; + function->id = data.id; + function->interceptor_address = data.interceptor_address; + char* names = function->function; + + data.function.copy(names, name_bytes); + names += name_bytes; + *names++ = '\0'; + + // interceptor follows the function_name + data.interceptor.copy(names, interceptor_bytes); + names += interceptor_bytes; + *names++ = '\0'; + + // update the dll table + dll_info->num_functions++; + dll_info->record_bytes += required; + + return true; +} + +// Only return true if the child should be able to perform this interception. +bool InterceptionManager::IsInterceptionPerformedByChild( + const InterceptionData& data) const { + if (INTERCEPTION_INVALID == data.type) + return false; + + if (INTERCEPTION_SERVICE_CALL == data.type) + return false; + + if (data.type >= INTERCEPTION_LAST) + return false; + + std::wstring ntdll(kNtdllName); + if (ntdll == data.dll) + return false; // ntdll has to be intercepted from the parent + + return true; +} + +ResultCode InterceptionManager::PatchNtdll(bool hot_patch_needed) { + // Maybe there is nothing to do + if (!hot_patch_needed && interceptions_.empty()) + return SBOX_ALL_OK; + + if (hot_patch_needed) { +#if defined(SANDBOX_EXPORTS) +// Make sure the functions are not excluded by the linker. +#if defined(_WIN64) +#pragma comment(linker, "/include:TargetNtMapViewOfSection64") +#pragma comment(linker, "/include:TargetNtUnmapViewOfSection64") +#else +#pragma comment(linker, "/include:_TargetNtMapViewOfSection@44") +#pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12") +#endif +#endif // defined(SANDBOX_EXPORTS) + ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44); + ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12); + } + + // Reserve a full 64k memory range in the child process. + HANDLE child = child_->Process(); + BYTE* thunk_base = reinterpret_cast<BYTE*>(::VirtualAllocEx( + child, nullptr, kAllocGranularity, MEM_RESERVE, PAGE_NOACCESS)); + + // Find an aligned, random location within the reserved range. + size_t thunk_bytes = + interceptions_.size() * sizeof(ThunkData) + sizeof(DllInterceptionData); + size_t thunk_offset = internal::GetGranularAlignedRandomOffset(thunk_bytes); + + // Split the base and offset along page boundaries. + thunk_base += thunk_offset & ~(kPageSize - 1); + thunk_offset &= kPageSize - 1; + + // Make an aligned, padded allocation, and move the pointer to our chunk. + size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & ~(kPageSize - 1); + thunk_base = reinterpret_cast<BYTE*>( + ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded, MEM_COMMIT, + PAGE_EXECUTE_READWRITE)); + CHECK(thunk_base); // If this fails we'd crash anyway on an invalid access. + DllInterceptionData* thunks = + reinterpret_cast<DllInterceptionData*>(thunk_base + thunk_offset); + + DllInterceptionData dll_data; + dll_data.data_bytes = thunk_bytes; + dll_data.num_thunks = 0; + dll_data.used_bytes = offsetof(DllInterceptionData, thunks); + + // Reset all helpers for a new child. + memset(g_originals, 0, sizeof(g_originals)); + + // this should write all the individual thunks to the child's memory + ResultCode rc = PatchClientFunctions(thunks, thunk_bytes, &dll_data); + + if (rc != SBOX_ALL_OK) + return rc; + + // and now write the first part of the table to the child's memory + SIZE_T written; + bool ok = + !!::WriteProcessMemory(child, thunks, &dll_data, + offsetof(DllInterceptionData, thunks), &written); + + if (!ok || (offsetof(DllInterceptionData, thunks) != written)) + return SBOX_ERROR_CANNOT_WRITE_INTERCEPTION_THUNK; + + // Attempt to protect all the thunks, but ignore failure + DWORD old_protection; + ::VirtualProtectEx(child, thunks, thunk_bytes, PAGE_EXECUTE_READ, + &old_protection); + + ResultCode ret = + child_->TransferVariable("g_originals", g_originals, sizeof(g_originals)); + return ret; +} + +ResultCode InterceptionManager::PatchClientFunctions( + DllInterceptionData* thunks, + size_t thunk_bytes, + DllInterceptionData* dll_data) { + DCHECK(thunks); + DCHECK(dll_data); + + HMODULE ntdll_base = ::GetModuleHandle(kNtdllName); + if (!ntdll_base) + return SBOX_ERROR_NO_HANDLE; + + char* interceptor_base = nullptr; + +#if defined(SANDBOX_EXPORTS) + interceptor_base = reinterpret_cast<char*>(child_->MainModule()); + base::ScopedNativeLibrary local_interceptor(::LoadLibrary(child_->Name())); +#endif // defined(SANDBOX_EXPORTS) + + std::unique_ptr<ServiceResolverThunk> thunk; +#if defined(_WIN64) + thunk.reset(new ServiceResolverThunk(child_->Process(), relaxed_)); +#else + base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { + if (os_info->version() >= base::win::Version::WIN10) + thunk.reset(new Wow64W10ResolverThunk(child_->Process(), relaxed_)); + else if (os_info->version() >= base::win::Version::WIN8) + thunk.reset(new Wow64W8ResolverThunk(child_->Process(), relaxed_)); + else + thunk.reset(new Wow64ResolverThunk(child_->Process(), relaxed_)); + } else if (os_info->version() >= base::win::Version::WIN8) { + thunk.reset(new Win8ResolverThunk(child_->Process(), relaxed_)); + } else { + thunk.reset(new ServiceResolverThunk(child_->Process(), relaxed_)); + } +#endif + + for (auto interception : interceptions_) { + const std::wstring ntdll(kNtdllName); + if (interception.dll != ntdll) + return SBOX_ERROR_BAD_PARAMS; + + if (INTERCEPTION_SERVICE_CALL != interception.type) + return SBOX_ERROR_BAD_PARAMS; + +#if defined(SANDBOX_EXPORTS) + // We may be trying to patch by function name. + if (!interception.interceptor_address) { + const char* address; + NTSTATUS ret = thunk->ResolveInterceptor( + local_interceptor.get(), interception.interceptor.c_str(), + reinterpret_cast<const void**>(&address)); + if (!NT_SUCCESS(ret)) { + ::SetLastError(GetLastErrorFromNtStatus(ret)); + return SBOX_ERROR_CANNOT_RESOLVE_INTERCEPTION_THUNK; + } + + // Translate the local address to an address on the child. + interception.interceptor_address = + interceptor_base + + (address - reinterpret_cast<char*>(local_interceptor.get())); + } +#endif // defined(SANDBOX_EXPORTS) + NTSTATUS ret = thunk->Setup( + ntdll_base, interceptor_base, interception.function.c_str(), + interception.interceptor.c_str(), interception.interceptor_address, + &thunks->thunks[dll_data->num_thunks], + thunk_bytes - dll_data->used_bytes, nullptr); + if (!NT_SUCCESS(ret)) { + ::SetLastError(GetLastErrorFromNtStatus(ret)); + return SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_THUNK; + } + + DCHECK(!g_originals[interception.id]); + g_originals[interception.id] = &thunks->thunks[dll_data->num_thunks]; + + dll_data->num_thunks++; + dll_data->used_bytes += sizeof(ThunkData); + } + + return SBOX_ALL_OK; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interception.h b/security/sandbox/chromium/sandbox/win/src/interception.h new file mode 100644 index 0000000000..6b4612fb6c --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception.h @@ -0,0 +1,290 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines InterceptionManager, the class in charge of setting up interceptions +// for the sandboxed process. For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_H_ +#define SANDBOX_SRC_INTERCEPTION_H_ + +#include <stddef.h> + +#include <list> +#include <string> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +class TargetProcess; + +// Internal structures used for communication between the broker and the target. +struct DllPatchInfo; +struct DllInterceptionData; + +// The InterceptionManager executes on the parent application, and it is in +// charge of setting up the desired interceptions, and placing the Interception +// Agent into the child application. +// +// The exposed API consists of two methods: AddToPatchedFunctions to set up a +// particular interception, and InitializeInterceptions to actually go ahead and +// perform all interceptions and transfer data to the child application. +// +// The typical usage is something like this: +// +// InterceptionManager interception_manager(child); +// if (!interception_manager.AddToPatchedFunctions( +// L"ntdll.dll", "NtCreateFile", +// sandbox::INTERCEPTION_SERVICE_CALL, &MyNtCreateFile, MY_ID_1)) +// return false; +// +// if (!interception_manager.AddToPatchedFunctions( +// L"kernel32.dll", "CreateDirectoryW", +// sandbox::INTERCEPTION_EAT, L"MyCreateDirectoryW@12", MY_ID_2)) +// return false; +// +// sandbox::ResultCode rc = interception_manager.InitializeInterceptions(); +// if (rc != sandbox::SBOX_ALL_OK) { +// DWORD error = ::GetLastError(); +// return rc; +// } +// +// Any required syncronization must be performed outside this class. Also, it is +// not possible to perform further interceptions after InitializeInterceptions +// is called. +// +class InterceptionManager { + // The unit test will access private members. + // Allow tests to be marked DISABLED_. Note that FLAKY_ and FAILS_ prefixes + // do not work with sandbox tests. + FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout1); + FRIEND_TEST_ALL_PREFIXES(InterceptionManagerTest, BufferLayout2); + + public: + // An interception manager performs interceptions on a given child process. + // If we are allowed to intercept functions that have been patched by somebody + // else, relaxed should be set to true. + // Note: We increase the child's reference count internally. + InterceptionManager(TargetProcess* child_process, bool relaxed); + ~InterceptionManager(); + + // Patches function_name inside dll_name to point to replacement_code_address. + // function_name has to be an exported symbol of dll_name. + // Returns true on success. + // + // The new function should match the prototype and calling convention of the + // function to intercept except for one extra argument (the first one) that + // contains a pointer to the original function, to simplify the development + // of interceptors (for IA32). In x64, there is no extra argument to the + // interceptor, so the provided InterceptorId is used to keep a table of + // intercepted functions so that the interceptor can index that table to get + // the pointer that would have been the first argument (g_originals[id]). + // + // For example, to intercept NtClose, the following code could be used: + // + // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); + // NTSTATUS WINAPI MyNtCose(IN NtCloseFunction OriginalClose, + // IN HANDLE Handle) { + // // do something + // // call the original function + // return OriginalClose(Handle); + // } + // + // And in x64: + // + // typedef NTSTATUS (WINAPI *NtCloseFunction) (IN HANDLE Handle); + // NTSTATUS WINAPI MyNtCose64(IN HANDLE Handle) { + // // do something + // // call the original function + // NtCloseFunction OriginalClose = g_originals[NT_CLOSE_ID]; + // return OriginalClose(Handle); + // } + bool AddToPatchedFunctions(const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const void* replacement_code_address, + InterceptorId id); + + // Patches function_name inside dll_name to point to + // replacement_function_name. + bool AddToPatchedFunctions(const wchar_t* dll_name, + const char* function_name, + InterceptionType interception_type, + const char* replacement_function_name, + InterceptorId id); + + // The interception agent will unload the dll with dll_name. + bool AddToUnloadModules(const wchar_t* dll_name); + + // Initializes all interceptions on the client. + // Returns SBOX_ALL_OK on success, or an appropriate error code. + // + // The child process must be created suspended, and cannot be resumed until + // after this method returns. In addition, no action should be performed on + // the child that may cause it to resume momentarily, such as injecting + // threads or APCs. + // + // This function must be called only once, after all interceptions have been + // set up using AddToPatchedFunctions. + ResultCode InitializeInterceptions(); + + private: + // Used to store the interception information until the actual set-up. + struct InterceptionData { + InterceptionData(); + InterceptionData(const InterceptionData& other); + ~InterceptionData(); + + InterceptionType type; // Interception type. + InterceptorId id; // Interceptor id. + std::wstring dll; // Name of dll to intercept. + std::string function; // Name of function to intercept. + std::string interceptor; // Name of interceptor function. + const void* interceptor_address; // Interceptor's entry point. + }; + + // Calculates the size of the required configuration buffer. + size_t GetBufferSize() const; + + // Rounds up the size of a given buffer, considering alignment (padding). + // value is the current size of the buffer, and alignment is specified in + // bytes. + static inline size_t RoundUpToMultiple(size_t value, size_t alignment) { + return ((value + alignment - 1) / alignment) * alignment; + } + + // Sets up a given buffer with all the information that has to be transfered + // to the child. + // Returns true on success. + // + // The buffer size should be at least the value returned by GetBufferSize + bool SetupConfigBuffer(void* buffer, size_t buffer_bytes); + + // Fills up the part of the transfer buffer that corresponds to information + // about one dll to patch. + // data is the first recorded interception for this dll. + // Returns true on success. + // + // On successful return, buffer will be advanced from it's current position + // to the point where the next block of configuration data should be written + // (the actual interception info), and the current size of the buffer will + // decrease to account the space used by this method. + bool SetupDllInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes) const; + + // Fills up the part of the transfer buffer that corresponds to a single + // function to patch. + // dll_info points to the dll being updated with the interception stored on + // data. The buffer pointer and remaining size are updated by this call. + // Returns true on success. + bool SetupInterceptionInfo(const InterceptionData& data, + void** buffer, + size_t* buffer_bytes, + DllPatchInfo* dll_info) const; + + // Returns true if this interception is to be performed by the child + // as opposed to from the parent. + bool IsInterceptionPerformedByChild(const InterceptionData& data) const; + + // Allocates a buffer on the child's address space (returned on + // remote_buffer), and fills it with the contents of a local buffer. + // Returns SBOX_ALL_OK on success. + ResultCode CopyDataToChild(const void* local_buffer, + size_t buffer_bytes, + void** remote_buffer) const; + + // Performs the cold patch (from the parent) of ntdll. + // Returns SBOX_ALL_OK on success. + // + // This method will insert additional interceptions to launch the interceptor + // agent on the child process, if there are additional interceptions to do. + ResultCode PatchNtdll(bool hot_patch_needed); + + // Peforms the actual interceptions on ntdll. + // thunks is the memory to store all the thunks for this dll (on the child), + // and dll_data is a local buffer to hold global dll interception info. + // Returns SBOX_ALL_OK on success. + ResultCode PatchClientFunctions(DllInterceptionData* thunks, + size_t thunk_bytes, + DllInterceptionData* dll_data); + + // The process to intercept. + TargetProcess* child_; + // Holds all interception info until the call to initialize (perform the + // actual patch). + std::list<InterceptionData> interceptions_; + + // Keep track of patches added by name. + bool names_used_; + + // true if we are allowed to patch already-patched functions. + bool relaxed_; + + DISALLOW_COPY_AND_ASSIGN(InterceptionManager); +}; + +// This macro simply calls interception_manager.AddToPatchedFunctions with +// the given service to intercept (INTERCEPTION_SERVICE_CALL), and assumes that +// the interceptor is called "TargetXXX", where XXX is the name of the service. +// Note that num_params is the number of bytes to pop out of the stack for +// the exported interceptor, following the calling convention of a service call +// (WINAPI = with the "C" underscore). +#if SANDBOX_EXPORTS +#if defined(_WIN64) +#define MAKE_SERVICE_NAME(service, params) "Target" #service "64" +#else +#define MAKE_SERVICE_NAME(service, params) "_Target" #service "@" #params +#endif + +#define ADD_NT_INTERCEPTION(service, id, num_params) \ + AddToPatchedFunctions(kNtdllName, #service, \ + sandbox::INTERCEPTION_SERVICE_CALL, \ + MAKE_SERVICE_NAME(service, num_params), id) + +#define INTERCEPT_NT(manager, service, id, num_params) \ + ((&Target##service) ? manager->ADD_NT_INTERCEPTION(service, id, num_params) \ + : false) + +// When intercepting the EAT it is important that the patched version of the +// function not call any functions imported from system libraries unless +// |TargetServices::InitCalled()| returns true, because it is only then that +// we are guaranteed that our IAT has been initialized. +#define INTERCEPT_EAT(manager, dll, function, id, num_params) \ + ((&Target##function) ? manager->AddToPatchedFunctions( \ + dll, #function, sandbox::INTERCEPTION_EAT, \ + MAKE_SERVICE_NAME(function, num_params), id) \ + : false) +#else // SANDBOX_EXPORTS +#if defined(_WIN64) +#define MAKE_SERVICE_NAME(service) &Target##service##64 +#else +#define MAKE_SERVICE_NAME(service) &Target##service +#endif + +#define ADD_NT_INTERCEPTION(service, id, num_params) \ + AddToPatchedFunctions( \ + kNtdllName, #service, sandbox::INTERCEPTION_SERVICE_CALL, \ + reinterpret_cast<void*>(MAKE_SERVICE_NAME(service)), id) + +#define INTERCEPT_NT(manager, service, id, num_params) \ + manager->ADD_NT_INTERCEPTION(service, id, num_params) + +// When intercepting the EAT it is important that the patched version of the +// function not call any functions imported from system libraries unless +// |TargetServices::InitCalled()| returns true, because it is only then that +// we are guaranteed that our IAT has been initialized. +#define INTERCEPT_EAT(manager, dll, function, id, num_params) \ + manager->AddToPatchedFunctions( \ + dll, #function, sandbox::INTERCEPTION_EAT, \ + reinterpret_cast<void*>(MAKE_SERVICE_NAME(function)), id) +#endif // SANDBOX_EXPORTS + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/interception_agent.cc b/security/sandbox/chromium/sandbox/win/src/interception_agent.cc new file mode 100644 index 0000000000..e095328a71 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception_agent.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// For information about interceptions as a whole see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include "sandbox/win/src/interception_agent.h" + +#include <windows.h> + +#include <stddef.h> + +#include "sandbox/win/src/eat_resolver.h" +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sidestep_resolver.h" + +namespace { + +// Returns true if target lies between base and base + range. +bool IsWithinRange(const void* base, size_t range, const void* target) { + const char* end = reinterpret_cast<const char*>(base) + range; + return reinterpret_cast<const char*>(target) < end; +} + +} // namespace + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +// The list of intercepted functions back-pointers. +SANDBOX_INTERCEPT OriginalFunctions g_originals; + +// Memory buffer mapped from the parent, with the list of interceptions. +SANDBOX_INTERCEPT SharedMemory* g_interceptions = nullptr; + +InterceptionAgent* InterceptionAgent::GetInterceptionAgent() { + static InterceptionAgent* s_singleton = nullptr; + if (!s_singleton) { + if (!g_interceptions) + return nullptr; + + size_t array_bytes = g_interceptions->num_intercepted_dlls * sizeof(void*); + s_singleton = reinterpret_cast<InterceptionAgent*>( + new (NT_ALLOC) char[array_bytes + sizeof(InterceptionAgent)]); + + bool success = s_singleton->Init(g_interceptions); + if (!success) { + operator delete(s_singleton, NT_ALLOC); + s_singleton = nullptr; + } + } + return s_singleton; +} + +bool InterceptionAgent::Init(SharedMemory* shared_memory) { + interceptions_ = shared_memory; + for (int i = 0; i < shared_memory->num_intercepted_dlls; i++) + dlls_[i] = nullptr; + return true; +} + +bool InterceptionAgent::DllMatch(const UNICODE_STRING* full_path, + const UNICODE_STRING* name, + const DllPatchInfo* dll_info) { + UNICODE_STRING current_name; + current_name.Length = + static_cast<USHORT>(g_nt.wcslen(dll_info->dll_name) * sizeof(wchar_t)); + current_name.MaximumLength = current_name.Length; + current_name.Buffer = const_cast<wchar_t*>(dll_info->dll_name); + + BOOLEAN case_insensitive = TRUE; + if (full_path && + !g_nt.RtlCompareUnicodeString(¤t_name, full_path, case_insensitive)) + return true; + + if (name && + !g_nt.RtlCompareUnicodeString(¤t_name, name, case_insensitive)) + return true; + + return false; +} + +bool InterceptionAgent::OnDllLoad(const UNICODE_STRING* full_path, + const UNICODE_STRING* name, + void* base_address) { + DllPatchInfo* dll_info = interceptions_->dll_list; + int i = 0; + for (; i < interceptions_->num_intercepted_dlls; i++) { + if (DllMatch(full_path, name, dll_info)) + break; + + dll_info = reinterpret_cast<DllPatchInfo*>( + reinterpret_cast<char*>(dll_info) + dll_info->record_bytes); + } + + // Return now if the dll is not in our list of interest. + if (i == interceptions_->num_intercepted_dlls) + return true; + + // The dll must be unloaded. + if (dll_info->unload_module) + return false; + + // Purify causes this condition to trigger. + if (dlls_[i]) + return true; + + size_t buffer_bytes = offsetof(DllInterceptionData, thunks) + + dll_info->num_functions * sizeof(ThunkData); + dlls_[i] = reinterpret_cast<DllInterceptionData*>( + new (NT_PAGE, base_address) char[buffer_bytes]); + + DCHECK_NT(dlls_[i]); + if (!dlls_[i]) + return true; + + dlls_[i]->data_bytes = buffer_bytes; + dlls_[i]->num_thunks = 0; + dlls_[i]->base = base_address; + dlls_[i]->used_bytes = offsetof(DllInterceptionData, thunks); + + VERIFY(PatchDll(dll_info, dlls_[i])); + + ULONG old_protect; + SIZE_T real_size = buffer_bytes; + void* to_protect = dlls_[i]; + VERIFY_SUCCESS(g_nt.ProtectVirtualMemory(NtCurrentProcess, &to_protect, + &real_size, PAGE_EXECUTE_READ, + &old_protect)); + return true; +} + +void InterceptionAgent::OnDllUnload(void* base_address) { + for (int i = 0; i < interceptions_->num_intercepted_dlls; i++) { + if (dlls_[i] && dlls_[i]->base == base_address) { + operator delete(dlls_[i], NT_PAGE); + dlls_[i] = nullptr; + break; + } + } +} + +// TODO(rvargas): We have to deal with prebinded dlls. I see two options: change +// the timestamp of the patched dll, or modify the info on the prebinded dll. +// the first approach messes matching of debug symbols, the second one is more +// complicated. +bool InterceptionAgent::PatchDll(const DllPatchInfo* dll_info, + DllInterceptionData* thunks) { + DCHECK_NT(thunks); + DCHECK_NT(dll_info); + + const FunctionInfo* function = reinterpret_cast<const FunctionInfo*>( + reinterpret_cast<const char*>(dll_info) + dll_info->offset_to_functions); + + for (int i = 0; i < dll_info->num_functions; i++) { + if (!IsWithinRange(dll_info, dll_info->record_bytes, function->function)) { + NOTREACHED_NT(); + return false; + } + + ResolverThunk* resolver = GetResolver(function->type); + if (!resolver) + return false; + + const char* interceptor = + function->function + g_nt.strlen(function->function) + 1; + + if (!IsWithinRange(function, function->record_bytes, interceptor) || + !IsWithinRange(dll_info, dll_info->record_bytes, interceptor)) { + NOTREACHED_NT(); + return false; + } + + NTSTATUS ret = resolver->Setup( + thunks->base, interceptions_->interceptor_base, function->function, + interceptor, function->interceptor_address, &thunks->thunks[i], + sizeof(ThunkData), nullptr); + if (!NT_SUCCESS(ret)) { + NOTREACHED_NT(); + return false; + } + + DCHECK_NT(!g_originals[function->id] || + g_originals[function->id] == &thunks->thunks[i]); + g_originals[function->id] = &thunks->thunks[i]; + + thunks->num_thunks++; + thunks->used_bytes += sizeof(ThunkData); + + function = reinterpret_cast<const FunctionInfo*>( + reinterpret_cast<const char*>(function) + function->record_bytes); + } + + return true; +} + +// This method is called from within the loader lock +ResolverThunk* InterceptionAgent::GetResolver(InterceptionType type) { + static EatResolverThunk* eat_resolver = nullptr; + static SidestepResolverThunk* sidestep_resolver = nullptr; + static SmartSidestepResolverThunk* smart_sidestep_resolver = nullptr; + + if (!eat_resolver) + eat_resolver = new (NT_ALLOC) EatResolverThunk; + +#if !defined(_WIN64) + // Sidestep is not supported for x64. + if (!sidestep_resolver) + sidestep_resolver = new (NT_ALLOC) SidestepResolverThunk; + + if (!smart_sidestep_resolver) + smart_sidestep_resolver = new (NT_ALLOC) SmartSidestepResolverThunk; +#endif + + switch (type) { + case INTERCEPTION_EAT: + return eat_resolver; + case INTERCEPTION_SIDESTEP: + return sidestep_resolver; + case INTERCEPTION_SMART_SIDESTEP: + return smart_sidestep_resolver; + default: + NOTREACHED_NT(); + } + + return nullptr; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interception_agent.h b/security/sandbox/chromium/sandbox/win/src/interception_agent.h new file mode 100644 index 0000000000..b2bce08b09 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception_agent.h @@ -0,0 +1,87 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines InterceptionAgent, the class in charge of setting up interceptions +// from the inside of the sandboxed process. For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_AGENT_H__ +#define SANDBOX_SRC_INTERCEPTION_AGENT_H__ + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// Internal structures used for communication between the broker and the target. +struct DllInterceptionData; +struct SharedMemory; +struct DllPatchInfo; + +class ResolverThunk; + +// The InterceptionAgent executes on the target application, and it is in charge +// of setting up the desired interceptions or indicating what module needs to +// be unloaded. +// +// The exposed API consists of three methods: GetInterceptionAgent to retrieve +// the single class instance, OnDllLoad and OnDllUnload to process a dll being +// loaded and unloaded respectively. +// +// This class assumes that it will get called for every dll being loaded, +// starting with kernel32, so the singleton will be instantiated from within the +// loader lock. +class InterceptionAgent { + public: + // Returns the single InterceptionAgent object for this process. + static InterceptionAgent* GetInterceptionAgent(); + + // This method should be invoked whenever a new dll is loaded to perform the + // required patches. If the return value is false, this dll should not be + // allowed to load. + // + // full_path is the (optional) full name of the module being loaded and name + // is the internal module name. If full_path is provided, it will be used + // before the internal name to determine if we care about this dll. + bool OnDllLoad(const UNICODE_STRING* full_path, const UNICODE_STRING* name, + void* base_address); + + // Performs cleanup when a dll is unloaded. + void OnDllUnload(void* base_address); + + private: + ~InterceptionAgent() {} + + // Performs initialization of the singleton. + bool Init(SharedMemory* shared_memory); + + // Returns true if we are interested on this dll. dll_info is an entry of the + // list of intercepted dlls. + bool DllMatch(const UNICODE_STRING* full_path, const UNICODE_STRING* name, + const DllPatchInfo* dll_info); + + // Performs the patching of the dll loaded at base_address. + // The patches to perform are described on dll_info, and thunks is the thunk + // storage for the whole dll. + // Returns true on success. + bool PatchDll(const DllPatchInfo* dll_info, DllInterceptionData* thunks); + + // Returns a resolver for a given interception type. + ResolverThunk* GetResolver(InterceptionType type); + + // Shared memory containing the list of functions to intercept. + SharedMemory* interceptions_; + + // Array of thunk data buffers for the intercepted dlls. This object singleton + // is allocated with a placement new with enough space to hold the complete + // array of pointers, not just the first element. + DllInterceptionData* dlls_[1]; + + DISALLOW_IMPLICIT_CONSTRUCTORS(InterceptionAgent); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_AGENT_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/interception_internal.h b/security/sandbox/chromium/sandbox/win/src/interception_internal.h new file mode 100644 index 0000000000..bf452e049e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception_internal.h @@ -0,0 +1,77 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines InterceptionManager, the class in charge of setting up interceptions +// for the sandboxed process. For more details see: +// http://dev.chromium.org/developers/design-documents/sandbox . + +#ifndef SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ +#define SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ + +#include <stddef.h> + +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +const int kMaxThunkDataBytes = 64; + +// The following structures contain variable size fields at the end, and will be +// used to transfer information between two processes. In order to guarantee +// our ability to follow the chain of structures, the alignment should be fixed, +// hence this pragma. +#pragma pack(push, 4) + +// Structures for the shared memory that contains patching information +// for the InterceptionAgent. +// A single interception: +struct FunctionInfo { + size_t record_bytes; // rounded to sizeof(size_t) bytes + InterceptionType type; + InterceptorId id; + const void* interceptor_address; + char function[1]; // placeholder for null terminated name + // char interceptor[] // followed by the interceptor function +}; + +// A single dll: +struct DllPatchInfo { + size_t record_bytes; // rounded to sizeof(size_t) bytes + size_t offset_to_functions; + int num_functions; + bool unload_module; + wchar_t dll_name[1]; // placeholder for null terminated name + // FunctionInfo function_info[] // followed by the functions to intercept +}; + +// All interceptions: +struct SharedMemory { + int num_intercepted_dlls; + void* interceptor_base; + DllPatchInfo dll_list[1]; // placeholder for the list of dlls +}; + +// Dummy single thunk: +struct ThunkData { + char data[kMaxThunkDataBytes]; +}; + +// In-memory representation of the interceptions for a given dll: +struct DllInterceptionData { + size_t data_bytes; + size_t used_bytes; + void* base; + int num_thunks; +#if defined(_WIN64) + int dummy; // Improve alignment. +#endif + ThunkData thunks[1]; +}; + +#pragma pack(pop) + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTION_INTERNAL_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/interception_unittest.cc b/security/sandbox/chromium/sandbox/win/src/interception_unittest.cc new file mode 100644 index 0000000000..0120682bcb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interception_unittest.cc @@ -0,0 +1,263 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for InterceptionManager. +// The tests require private information so the whole interception.cc file is +// included from this file. + +#include "sandbox/win/src/interception.h" + +#include <windows.h> + +#include <stddef.h> + +#include <algorithm> +#include <memory> +#include <set> + +#include "base/bits.h" +#include "sandbox/win/src/interception_internal.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/target_process.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace internal { +size_t GetGranularAlignedRandomOffset(size_t size); +} + +// Walks the settings buffer, verifying that the values make sense and counting +// objects. +// Arguments: +// buffer (in): the buffer to walk. +// size (in): buffer size +// num_dlls (out): count of the dlls on the buffer. +// num_function (out): count of intercepted functions. +// num_names (out): count of named interceptor functions. +void WalkBuffer(void* buffer, + size_t size, + int* num_dlls, + int* num_functions, + int* num_names) { + ASSERT_TRUE(buffer); + ASSERT_TRUE(num_functions); + ASSERT_TRUE(num_names); + *num_dlls = *num_functions = *num_names = 0; + SharedMemory* memory = reinterpret_cast<SharedMemory*>(buffer); + + ASSERT_GT(size, sizeof(SharedMemory)); + DllPatchInfo* dll = &memory->dll_list[0]; + + for (int i = 0; i < memory->num_intercepted_dlls; i++) { + ASSERT_NE(0u, wcslen(dll->dll_name)); + ASSERT_EQ(0u, dll->record_bytes % sizeof(size_t)); + ASSERT_EQ(0u, dll->offset_to_functions % sizeof(size_t)); + ASSERT_NE(0, dll->num_functions); + + FunctionInfo* function = reinterpret_cast<FunctionInfo*>( + reinterpret_cast<char*>(dll) + dll->offset_to_functions); + + for (int j = 0; j < dll->num_functions; j++) { + ASSERT_EQ(0u, function->record_bytes % sizeof(size_t)); + + char* name = function->function; + size_t length = strlen(name); + ASSERT_NE(0u, length); + name += length + 1; + + // look for overflows + ASSERT_GT(reinterpret_cast<char*>(buffer) + size, name + strlen(name)); + + // look for a named interceptor + if (strlen(name)) { + (*num_names)++; + EXPECT_TRUE(!function->interceptor_address); + } else { + EXPECT_TRUE(function->interceptor_address); + } + + (*num_functions)++; + function = reinterpret_cast<FunctionInfo*>( + reinterpret_cast<char*>(function) + function->record_bytes); + } + + (*num_dlls)++; + dll = reinterpret_cast<DllPatchInfo*>(reinterpret_cast<char*>(dll) + + dll->record_bytes); + } +} + +TEST(InterceptionManagerTest, GetGranularAlignedRandomOffset) { + std::set<size_t> sizes; + + // 544 is current value of interceptions_.size() * sizeof(ThunkData) + + // sizeof(DllInterceptionData). + const size_t kThunkBytes = 544; + + // ciel(log2(544)) = 10. + // Alignment must be 2^10 = 1024. + const size_t kAlignmentBits = base::bits::Log2Ceiling(kThunkBytes); + const size_t kAlignment = static_cast<size_t>(1) << kAlignmentBits; + + const size_t kAllocGranularity = 65536; + + // Generate enough sample data to ensure there is at least one value in each + // potential bucket. + for (size_t i = 0; i < 1000000; i++) + sizes.insert(internal::GetGranularAlignedRandomOffset(kThunkBytes)); + + size_t prev_val = 0; + size_t min_val = kAllocGranularity; + size_t min_nonzero_val = kAllocGranularity; + size_t max_val = 0; + + for (size_t val : sizes) { + ASSERT_LT(val, kAllocGranularity); + if (prev_val) + ASSERT_EQ(val - prev_val, kAlignment); + if (val) + min_nonzero_val = std::min(val, min_nonzero_val); + min_val = std::min(val, min_val); + prev_val = val; + max_val = std::max(val, max_val); + } + ASSERT_EQ(max_val, kAllocGranularity - kAlignment); + ASSERT_EQ(0u, min_val); + ASSERT_EQ(min_nonzero_val, kAlignment); +} + +TEST(InterceptionManagerTest, BufferLayout1) { + wchar_t exe_name[MAX_PATH]; + ASSERT_NE(0u, GetModuleFileName(nullptr, exe_name, MAX_PATH - 1)); + + TargetProcess* target = + MakeTestTargetProcess(::GetCurrentProcess(), ::GetModuleHandle(exe_name)); + + InterceptionManager interceptions(target, true); + + // Any pointer will do for a function pointer. + void* function = &interceptions; + + // We don't care about the interceptor id. + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtCreateFile", + INTERCEPTION_SERVICE_CALL, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateFileEx", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "SomeFileEx", + INTERCEPTION_SMART_SIDESTEP, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "FindWindow", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateMutex", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "PostMsg", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"user32.dll", "PostMsg", + INTERCEPTION_EAT, "replacement", + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtClose", + INTERCEPTION_SERVICE_CALL, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtOpenFile", + INTERCEPTION_SIDESTEP, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"some.dll", "Superfn", INTERCEPTION_EAT, + function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, "a", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_SIDESTEP, "ab", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"comctl.dll", "SaveAsDlg", + INTERCEPTION_EAT, "abc", OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "p", INTERCEPTION_EAT, function, + OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"b.dll", + "TheIncredibleCallToSaveTheWorld", + INTERCEPTION_EAT, function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "BIsLame", INTERCEPTION_EAT, + function, OPEN_KEY_ID); + interceptions.AddToPatchedFunctions(L"a.dll", "ARules", INTERCEPTION_EAT, + function, OPEN_KEY_ID); + + // Verify that all interceptions were added + ASSERT_EQ(18u, interceptions.interceptions_.size()); + + size_t buffer_size = interceptions.GetBufferSize(); + std::unique_ptr<BYTE[]> local_buffer(new BYTE[buffer_size]); + + ASSERT_TRUE(interceptions.SetupConfigBuffer(local_buffer.get(), buffer_size)); + + // At this point, the interceptions should have been separated into two + // groups: one group with the local ("cold") interceptions, consisting of + // everything from ntdll and stuff set as INTRECEPTION_SERVICE_CALL, and + // another group with the interceptions belonging to dlls that will be "hot" + // patched on the client. The second group lives on local_buffer, and the + // first group remains on the list of interceptions (inside the object + // "interceptions"). There are 3 local interceptions (of ntdll); the + // other 15 have to be sent to the child to be performed "hot". + EXPECT_EQ(3u, interceptions.interceptions_.size()); + + int num_dlls, num_functions, num_names; + WalkBuffer(local_buffer.get(), buffer_size, &num_dlls, &num_functions, + &num_names); + + // The 15 interceptions on the buffer (to the child) should be grouped on 6 + // dlls. Only four interceptions are using an explicit name for the + // interceptor function. + EXPECT_EQ(6, num_dlls); + EXPECT_EQ(15, num_functions); + EXPECT_EQ(4, num_names); +} + +TEST(InterceptionManagerTest, BufferLayout2) { + wchar_t exe_name[MAX_PATH]; + ASSERT_NE(0u, GetModuleFileName(nullptr, exe_name, MAX_PATH - 1)); + + TargetProcess* target = + MakeTestTargetProcess(::GetCurrentProcess(), ::GetModuleHandle(exe_name)); + + InterceptionManager interceptions(target, true); + + // Any pointer will do for a function pointer. + void* function = &interceptions; + interceptions.AddToUnloadModules(L"some01.dll"); + // We don't care about the interceptor id. + interceptions.AddToPatchedFunctions(L"ntdll.dll", "NtCreateFile", + INTERCEPTION_SERVICE_CALL, function, + OPEN_FILE_ID); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "CreateFileEx", + INTERCEPTION_EAT, function, OPEN_FILE_ID); + interceptions.AddToUnloadModules(L"some02.dll"); + interceptions.AddToPatchedFunctions(L"kernel32.dll", "SomeFileEx", + INTERCEPTION_SMART_SIDESTEP, function, + OPEN_FILE_ID); + // Verify that all interceptions were added + ASSERT_EQ(5u, interceptions.interceptions_.size()); + + size_t buffer_size = interceptions.GetBufferSize(); + std::unique_ptr<BYTE[]> local_buffer(new BYTE[buffer_size]); + + ASSERT_TRUE(interceptions.SetupConfigBuffer(local_buffer.get(), buffer_size)); + + // At this point, the interceptions should have been separated into two + // groups: one group with the local ("cold") interceptions, and another + // group with the interceptions belonging to dlls that will be "hot" + // patched on the client. The second group lives on local_buffer, and the + // first group remains on the list of interceptions, in this case just one. + EXPECT_EQ(1u, interceptions.interceptions_.size()); + + int num_dlls, num_functions, num_names; + WalkBuffer(local_buffer.get(), buffer_size, &num_dlls, &num_functions, + &num_names); + + EXPECT_EQ(3, num_dlls); + EXPECT_EQ(4, num_functions); + EXPECT_EQ(0, num_names); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interceptors.h b/security/sandbox/chromium/sandbox/win/src/interceptors.h new file mode 100644 index 0000000000..5788614707 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interceptors.h @@ -0,0 +1,73 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_INTERCEPTORS_H_ +#define SANDBOX_SRC_INTERCEPTORS_H_ + +#if defined(_WIN64) +#include "sandbox/win/src/interceptors_64.h" +#endif + +namespace sandbox { + +enum InterceptorId { + // Internal use: + MAP_VIEW_OF_SECTION_ID = 0, + UNMAP_VIEW_OF_SECTION_ID, + // Policy broker: + SET_INFORMATION_THREAD_ID, + OPEN_THREAD_TOKEN_ID, + OPEN_THREAD_TOKEN_EX_ID, + OPEN_THREAD_ID, + OPEN_PROCESS_ID, + OPEN_PROCESS_TOKEN_ID, + OPEN_PROCESS_TOKEN_EX_ID, + // Filesystem dispatcher: + CREATE_FILE_ID, + OPEN_FILE_ID, + QUERY_ATTRIB_FILE_ID, + QUERY_FULL_ATTRIB_FILE_ID, + SET_INFO_FILE_ID, + // Named pipe dispatcher: + CREATE_NAMED_PIPE_ID, + // Process-thread dispatcher: + CREATE_PROCESSW_ID, + CREATE_PROCESSA_ID, + CREATE_THREAD_ID, + // Registry dispatcher: + CREATE_KEY_ID, + OPEN_KEY_ID, + OPEN_KEY_EX_ID, + // Sync dispatcher: + CREATE_EVENT_ID, + OPEN_EVENT_ID, + // Process mitigations Win32k dispatcher: + GDIINITIALIZE_ID, + GETSTOCKOBJECT_ID, + REGISTERCLASSW_ID, + ENUMDISPLAYMONITORS_ID, + ENUMDISPLAYDEVICESA_ID, + GETMONITORINFOA_ID, + GETMONITORINFOW_ID, + CREATEOPMPROTECTEDOUTPUTS_ID, + GETCERTIFICATE_ID, + GETCERTIFICATESIZE_ID, + GETCERTIFICATEBYHANDLE_ID, + GETCERTIFICATESIZEBYHANDLE_ID, + DESTROYOPMPROTECTEDOUTPUT_ID, + CONFIGUREOPMPROTECTEDOUTPUT_ID, + GETOPMINFORMATION_ID, + GETOPMRANDOMNUMBER_ID, + GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE_ID, + SETOPMSIGNINGKEYANDSEQUENCENUMBERS_ID, + // Signed dispatcher: + CREATE_SECTION_ID, + INTERCEPTOR_MAX_ID +}; + +typedef void* OriginalFunctions[INTERCEPTOR_MAX_ID]; + +} // namespace sandbox + +#endif // SANDBOX_SRC_INTERCEPTORS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/interceptors_64.cc b/security/sandbox/chromium/sandbox/win/src/interceptors_64.cc new file mode 100644 index 0000000000..0ac2671fcc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interceptors_64.cc @@ -0,0 +1,531 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/interceptors_64.h" + +#include "sandbox/win/src/filesystem_interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/named_pipe_interception.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/process_mitigations_win32k_interception.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/registry_interception.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/signed_interception.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/target_interceptions.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; +SANDBOX_INTERCEPT OriginalFunctions g_originals; + +NTSTATUS WINAPI TargetNtMapViewOfSection64(HANDLE section, + HANDLE process, + PVOID* base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect) { + NtMapViewOfSectionFunction orig_fn = + reinterpret_cast<NtMapViewOfSectionFunction>( + g_originals[MAP_VIEW_OF_SECTION_ID]); + + return TargetNtMapViewOfSection(orig_fn, section, process, base, zero_bits, + commit_size, offset, view_size, inherit, + allocation_type, protect); +} + +NTSTATUS WINAPI TargetNtUnmapViewOfSection64(HANDLE process, PVOID base) { + NtUnmapViewOfSectionFunction orig_fn = + reinterpret_cast<NtUnmapViewOfSectionFunction>( + g_originals[UNMAP_VIEW_OF_SECTION_ID]); + return TargetNtUnmapViewOfSection(orig_fn, process, base); +} + +// ----------------------------------------------------------------------- + +NTSTATUS WINAPI +TargetNtSetInformationThread64(HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, + PVOID thread_information, + ULONG thread_information_bytes) { + NtSetInformationThreadFunction orig_fn = + reinterpret_cast<NtSetInformationThreadFunction>( + g_originals[SET_INFORMATION_THREAD_ID]); + return TargetNtSetInformationThread(orig_fn, thread, thread_info_class, + thread_information, + thread_information_bytes); +} + +NTSTATUS WINAPI TargetNtOpenThreadToken64(HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + PHANDLE token) { + NtOpenThreadTokenFunction orig_fn = + reinterpret_cast<NtOpenThreadTokenFunction>( + g_originals[OPEN_THREAD_TOKEN_ID]); + return TargetNtOpenThreadToken(orig_fn, thread, desired_access, open_as_self, + token); +} + +NTSTATUS WINAPI TargetNtOpenThreadTokenEx64(HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + ULONG handle_attributes, + PHANDLE token) { + NtOpenThreadTokenExFunction orig_fn = + reinterpret_cast<NtOpenThreadTokenExFunction>( + g_originals[OPEN_THREAD_TOKEN_EX_ID]); + return TargetNtOpenThreadTokenEx(orig_fn, thread, desired_access, + open_as_self, handle_attributes, token); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateFile64(PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, + ULONG sharing, + ULONG disposition, + ULONG options, + PVOID ea_buffer, + ULONG ea_length) { + NtCreateFileFunction orig_fn = + reinterpret_cast<NtCreateFileFunction>(g_originals[CREATE_FILE_ID]); + return TargetNtCreateFile(orig_fn, file, desired_access, object_attributes, + io_status, allocation_size, file_attributes, + sharing, disposition, options, ea_buffer, + ea_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenFile64(PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + ULONG sharing, + ULONG options) { + NtOpenFileFunction orig_fn = + reinterpret_cast<NtOpenFileFunction>(g_originals[OPEN_FILE_ID]); + return TargetNtOpenFile(orig_fn, file, desired_access, object_attributes, + io_status, sharing, options); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtQueryAttributesFile64(POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes) { + NtQueryAttributesFileFunction orig_fn = + reinterpret_cast<NtQueryAttributesFileFunction>( + g_originals[QUERY_ATTRIB_FILE_ID]); + return TargetNtQueryAttributesFile(orig_fn, object_attributes, + file_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile64( + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes) { + NtQueryFullAttributesFileFunction orig_fn = + reinterpret_cast<NtQueryFullAttributesFileFunction>( + g_originals[QUERY_FULL_ATTRIB_FILE_ID]); + return TargetNtQueryFullAttributesFile(orig_fn, object_attributes, + file_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtSetInformationFile64(HANDLE file, + PIO_STATUS_BLOCK io_status, + PVOID file_information, + ULONG length, + FILE_INFORMATION_CLASS file_information_class) { + NtSetInformationFileFunction orig_fn = + reinterpret_cast<NtSetInformationFileFunction>( + g_originals[SET_INFO_FILE_ID]); + return TargetNtSetInformationFile(orig_fn, file, io_status, file_information, + length, file_information_class); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateNamedPipeW64(LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instance, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + CreateNamedPipeWFunction orig_fn = reinterpret_cast<CreateNamedPipeWFunction>( + g_originals[CREATE_NAMED_PIPE_ID]); + return TargetCreateNamedPipeW(orig_fn, pipe_name, open_mode, pipe_mode, + max_instance, out_buffer_size, in_buffer_size, + default_timeout, security_attributes); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThread64(PHANDLE thread, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NtOpenThreadFunction orig_fn = + reinterpret_cast<NtOpenThreadFunction>(g_originals[OPEN_THREAD_ID]); + return TargetNtOpenThread(orig_fn, thread, desired_access, object_attributes, + client_id); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcess64(PHANDLE process, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NtOpenProcessFunction orig_fn = + reinterpret_cast<NtOpenProcessFunction>(g_originals[OPEN_PROCESS_ID]); + return TargetNtOpenProcess(orig_fn, process, desired_access, + object_attributes, client_id); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessToken64(HANDLE process, + ACCESS_MASK desired_access, + PHANDLE token) { + NtOpenProcessTokenFunction orig_fn = + reinterpret_cast<NtOpenProcessTokenFunction>( + g_originals[OPEN_PROCESS_TOKEN_ID]); + return TargetNtOpenProcessToken(orig_fn, process, desired_access, token); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessTokenEx64(HANDLE process, + ACCESS_MASK desired_access, + ULONG handle_attributes, + PHANDLE token) { + NtOpenProcessTokenExFunction orig_fn = + reinterpret_cast<NtOpenProcessTokenExFunction>( + g_originals[OPEN_PROCESS_TOKEN_EX_ID]); + return TargetNtOpenProcessTokenEx(orig_fn, process, desired_access, + handle_attributes, token); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessW64(LPCWSTR application_name, + LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information) { + CreateProcessWFunction orig_fn = + reinterpret_cast<CreateProcessWFunction>(g_originals[CREATE_PROCESSW_ID]); + return TargetCreateProcessW( + orig_fn, application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, environment, current_directory, + startup_info, process_information); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessA64(LPCSTR application_name, + LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information) { + CreateProcessAFunction orig_fn = + reinterpret_cast<CreateProcessAFunction>(g_originals[CREATE_PROCESSA_ID]); + return TargetCreateProcessA( + orig_fn, application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, environment, current_directory, + startup_info, process_information); +} + +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateThread64(LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + PVOID parameter, + DWORD creation_flags, + LPDWORD thread_id) { + CreateThreadFunction orig_fn = + reinterpret_cast<CreateThreadFunction>(g_originals[CREATE_THREAD_ID]); + return TargetCreateThread(orig_fn, thread_attributes, stack_size, + start_address, parameter, creation_flags, + thread_id); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateKey64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG title_index, + PUNICODE_STRING class_name, + ULONG create_options, + PULONG disposition) { + NtCreateKeyFunction orig_fn = + reinterpret_cast<NtCreateKeyFunction>(g_originals[CREATE_KEY_ID]); + return TargetNtCreateKey(orig_fn, key, desired_access, object_attributes, + title_index, class_name, create_options, + disposition); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenKey64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NtOpenKeyFunction orig_fn = + reinterpret_cast<NtOpenKeyFunction>(g_originals[OPEN_KEY_ID]); + return TargetNtOpenKey(orig_fn, key, desired_access, object_attributes); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenKeyEx64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG open_options) { + NtOpenKeyExFunction orig_fn = + reinterpret_cast<NtOpenKeyExFunction>(g_originals[OPEN_KEY_EX_ID]); + return TargetNtOpenKeyEx(orig_fn, key, desired_access, object_attributes, + open_options); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateEvent64(PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state) { + NtCreateEventFunction orig_fn = + reinterpret_cast<NtCreateEventFunction>(g_originals[CREATE_EVENT_ID]); + return TargetNtCreateEvent(orig_fn, event_handle, desired_access, + object_attributes, event_type, initial_state); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenEvent64(PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NtOpenEventFunction orig_fn = + reinterpret_cast<NtOpenEventFunction>(g_originals[OPEN_EVENT_ID]); + return TargetNtOpenEvent(orig_fn, event_handle, desired_access, + object_attributes); +} + +// ----------------------------------------------------------------------- + +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize64(HANDLE dll, + DWORD reason) { + GdiDllInitializeFunction orig_fn = + reinterpret_cast<GdiDllInitializeFunction>(g_originals[GDIINITIALIZE_ID]); + return TargetGdiDllInitialize(orig_fn, dll, reason); +} + +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject64(int object) { + GetStockObjectFunction orig_fn = + reinterpret_cast<GetStockObjectFunction>(g_originals[GETSTOCKOBJECT_ID]); + return TargetGetStockObject(orig_fn, object); +} + +SANDBOX_INTERCEPT ATOM WINAPI +TargetRegisterClassW64(const WNDCLASS* wnd_class) { + RegisterClassWFunction orig_fn = + reinterpret_cast<RegisterClassWFunction>(g_originals[REGISTERCLASSW_ID]); + return TargetRegisterClassW(orig_fn, wnd_class); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetEnumDisplayMonitors64(HDC hdc, + LPCRECT clip_rect, + MONITORENUMPROC enum_function, + LPARAM data_pointer) { + EnumDisplayMonitorsFunction orig_fn = + reinterpret_cast<EnumDisplayMonitorsFunction>( + g_originals[ENUMDISPLAYMONITORS_ID]); + return TargetEnumDisplayMonitors(orig_fn, hdc, clip_rect, enum_function, + data_pointer); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetEnumDisplayDevicesA64(LPCSTR device, + DWORD device_num, + PDISPLAY_DEVICEA display_device, + DWORD flags) { + EnumDisplayDevicesAFunction orig_fn = + reinterpret_cast<EnumDisplayDevicesAFunction>( + g_originals[ENUMDISPLAYDEVICESA_ID]); + return TargetEnumDisplayDevicesA(orig_fn, device, device_num, display_device, + flags); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetGetMonitorInfoA64(HMONITOR monitor, LPMONITORINFO monitor_info) { + GetMonitorInfoAFunction orig_fn = reinterpret_cast<GetMonitorInfoAFunction>( + g_originals[GETMONITORINFOA_ID]); + return TargetGetMonitorInfoA(orig_fn, monitor, monitor_info); +} + +SANDBOX_INTERCEPT BOOL WINAPI +TargetGetMonitorInfoW64(HMONITOR monitor, LPMONITORINFO monitor_info) { + GetMonitorInfoWFunction orig_fn = reinterpret_cast<GetMonitorInfoWFunction>( + g_originals[GETMONITORINFOW_ID]); + return TargetGetMonitorInfoW(orig_fn, monitor, monitor_info); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetSuggestedOPMProtectedOutputArraySize64( + PUNICODE_STRING device_name, + DWORD* suggested_output_array_size) { + GetSuggestedOPMProtectedOutputArraySizeFunction orig_fn = + reinterpret_cast<GetSuggestedOPMProtectedOutputArraySizeFunction>( + g_originals[GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE_ID]); + return TargetGetSuggestedOPMProtectedOutputArraySize( + orig_fn, device_name, suggested_output_array_size); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetCreateOPMProtectedOutputs64( + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD protected_output_array_size, + DWORD* num_output_handles, + OPM_PROTECTED_OUTPUT_HANDLE* protected_output_array) { + CreateOPMProtectedOutputsFunction orig_fn = + reinterpret_cast<CreateOPMProtectedOutputsFunction>( + g_originals[CREATEOPMPROTECTEDOUTPUTS_ID]); + return TargetCreateOPMProtectedOutputs( + orig_fn, device_name, vos, protected_output_array_size, + num_output_handles, protected_output_array); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificate64(PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length) { + GetCertificateFunction orig_fn = + reinterpret_cast<GetCertificateFunction>(g_originals[GETCERTIFICATE_ID]); + return TargetGetCertificate(orig_fn, device_name, certificate_type, + certificate, certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateSize64(PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + GetCertificateSizeFunction orig_fn = + reinterpret_cast<GetCertificateSizeFunction>( + g_originals[GETCERTIFICATESIZE_ID]); + return TargetGetCertificateSize(orig_fn, device_name, certificate_type, + certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateByHandle64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length) { + GetCertificateByHandleFunction orig_fn = + reinterpret_cast<GetCertificateByHandleFunction>( + g_originals[GETCERTIFICATE_ID]); + return TargetGetCertificateByHandle(orig_fn, protected_output, + certificate_type, certificate, + certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateSizeByHandle64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + GetCertificateSizeByHandleFunction orig_fn = + reinterpret_cast<GetCertificateSizeByHandleFunction>( + g_originals[GETCERTIFICATESIZE_ID]); + return TargetGetCertificateSizeByHandle(orig_fn, protected_output, + certificate_type, certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetDestroyOPMProtectedOutput64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output) { + DestroyOPMProtectedOutputFunction orig_fn = + reinterpret_cast<DestroyOPMProtectedOutputFunction>( + g_originals[DESTROYOPMPROTECTEDOUTPUT_ID]); + return TargetDestroyOPMProtectedOutput(orig_fn, protected_output); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetOPMInformation64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_info) { + GetOPMInformationFunction orig_fn = + reinterpret_cast<GetOPMInformationFunction>( + g_originals[GETOPMINFORMATION_ID]); + return TargetGetOPMInformation(orig_fn, protected_output, parameters, + requested_info); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetOPMRandomNumber64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number) { + GetOPMRandomNumberFunction orig_fn = + reinterpret_cast<GetOPMRandomNumberFunction>( + g_originals[GETOPMRANDOMNUMBER_ID]); + return TargetGetOPMRandomNumber(orig_fn, protected_output, random_number); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetSetOPMSigningKeyAndSequenceNumbers64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters) { + SetOPMSigningKeyAndSequenceNumbersFunction orig_fn = + reinterpret_cast<SetOPMSigningKeyAndSequenceNumbersFunction>( + g_originals[SETOPMSIGNINGKEYANDSEQUENCENUMBERS_ID]); + return TargetSetOPMSigningKeyAndSequenceNumbers(orig_fn, protected_output, + parameters); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetConfigureOPMProtectedOutput64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters) { + ConfigureOPMProtectedOutputFunction orig_fn = + reinterpret_cast<ConfigureOPMProtectedOutputFunction>( + g_originals[CONFIGUREOPMPROTECTEDOUTPUT_ID]); + return TargetConfigureOPMProtectedOutput( + orig_fn, protected_output, parameters, additional_parameters_size, + additional_parameters); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateSection64(PHANDLE section_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PLARGE_INTEGER maximum_size, + ULONG section_page_protection, + ULONG allocation_attributes, + HANDLE file_handle) { + NtCreateSectionFunction orig_fn = + reinterpret_cast<NtCreateSectionFunction>(g_originals[CREATE_SECTION_ID]); + return TargetNtCreateSection( + orig_fn, section_handle, desired_access, object_attributes, maximum_size, + section_page_protection, allocation_attributes, file_handle); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/interceptors_64.h b/security/sandbox/chromium/sandbox/win/src/interceptors_64.h new file mode 100644 index 0000000000..f34627dcd2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/interceptors_64.h @@ -0,0 +1,330 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_INTERCEPTORS_64_H_ +#define SANDBOX_WIN_SRC_INTERCEPTORS_64_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interception of NtMapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being loaded, so we can patch them if needed. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtMapViewOfSection64(HANDLE section, + HANDLE process, + PVOID* base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect); + +// Interception of NtUnmapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being unloaded, so we can clean up our interceptions. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtUnmapViewOfSection64(HANDLE process, + PVOID base); + +// ----------------------------------------------------------------------- +// Interceptors without IPC. + +// Interception of NtSetInformationThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtSetInformationThread64(HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, + PVOID thread_information, + ULONG thread_information_bytes); + +// Interception of NtOpenThreadToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThreadToken64(HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + PHANDLE token); + +// Interception of NtOpenThreadTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThreadTokenEx64(HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + ULONG handle_attributes, + PHANDLE token); + +// ----------------------------------------------------------------------- +// Interceptors handled by the file system dispatcher. + +// Interception of NtCreateFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateFile64(PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + PLARGE_INTEGER allocation_size, + ULONG file_attributes, + ULONG sharing, + ULONG disposition, + ULONG options, + PVOID ea_buffer, + ULONG ea_length); + +// Interception of NtOpenFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenFile64(PHANDLE file, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PIO_STATUS_BLOCK io_status, + ULONG sharing, + ULONG options); + +// Interception of NtQueryAtttributesFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtQueryAttributesFile64(POBJECT_ATTRIBUTES object_attributes, + PFILE_BASIC_INFORMATION file_attributes); + +// Interception of NtQueryFullAtttributesFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtQueryFullAttributesFile64( + POBJECT_ATTRIBUTES object_attributes, + PFILE_NETWORK_OPEN_INFORMATION file_attributes); + +// Interception of NtSetInformationFile on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtSetInformationFile64(HANDLE file, + PIO_STATUS_BLOCK io_status, + PVOID file_information, + ULONG length, + FILE_INFORMATION_CLASS file_information_class); + +// ----------------------------------------------------------------------- +// Interceptors handled by the named pipe dispatcher. + +// Interception of CreateNamedPipeW in kernel32.dll +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateNamedPipeW64(LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instance, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes); + +// ----------------------------------------------------------------------- +// Interceptors handled by the process-thread dispatcher. + +// Interception of NtOpenThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThread64(PHANDLE thread, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcess on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcess64(PHANDLE process, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcessToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessToken64(HANDLE process, + ACCESS_MASK desired_access, + PHANDLE token); + +// Interception of NtOpenProcessTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessTokenEx64(HANDLE process, + ACCESS_MASK desired_access, + ULONG handle_attributes, + PHANDLE token); + +// Interception of CreateProcessW in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessW64(LPCWSTR application_name, + LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information); + +// Interception of CreateProcessA in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessA64(LPCSTR application_name, + LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information); + +// Interception of CreateThread in kernel32.dll. +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateThread64(LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + PVOID parameter, + DWORD creation_flags, + LPDWORD thread_id); + +// ----------------------------------------------------------------------- +// Interceptors handled by the registry dispatcher. + +// Interception of NtCreateKey on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateKey64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG title_index, + PUNICODE_STRING class_name, + ULONG create_options, + PULONG disposition); + +// Interception of NtOpenKey on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenKey64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// Interception of NtOpenKeyEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenKeyEx64(PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG open_options); + +// ----------------------------------------------------------------------- +// Interceptors handled by the sync dispatcher. + +// Interception of NtCreateEvent/NtOpenEvent on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateEvent64(PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenEvent64(PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// ----------------------------------------------------------------------- +// Interceptors handled by the process mitigations win32k lockdown code. + +// Interceptor for the GdiDllInitialize function. +SANDBOX_INTERCEPT BOOL WINAPI TargetGdiDllInitialize64(HANDLE dll, + DWORD reason); + +// Interceptor for the GetStockObject function. +SANDBOX_INTERCEPT HGDIOBJ WINAPI TargetGetStockObject64(int object); + +// Interceptor for the RegisterClassW function. +SANDBOX_INTERCEPT ATOM WINAPI TargetRegisterClassW64(const WNDCLASS* wnd_class); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetEnumDisplayMonitors64(HDC hdc, + LPCRECT lprcClip, + MONITORENUMPROC lpfnEnum, + LPARAM dwData); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetEnumDisplayDevicesA64(LPCSTR lpDevice, + DWORD iDevNum, + PDISPLAY_DEVICEA lpDisplayDevice, + DWORD dwFlags); + +SANDBOX_INTERCEPT BOOL WINAPI TargetGetMonitorInfoA64(HMONITOR hMonitor, + LPMONITORINFO lpmi); + +SANDBOX_INTERCEPT BOOL WINAPI TargetGetMonitorInfoW64(HMONITOR hMonitor, + LPMONITORINFO lpmi); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetSuggestedOPMProtectedOutputArraySize64( + PUNICODE_STRING device_name, + DWORD* suggested_output_array_size); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetCreateOPMProtectedOutputs64( + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD protected_output_array_size, + DWORD* num_output_handles, + OPM_PROTECTED_OUTPUT_HANDLE* protected_output_array); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificate64(PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateSize64(PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateByHandle64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificateSizeByHandle64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetDestroyOPMProtectedOutput64(OPM_PROTECTED_OUTPUT_HANDLE protected_output); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetOPMInformation64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_info); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetOPMRandomNumber64(OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetSetOPMSigningKeyAndSequenceNumbers64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetConfigureOPMProtectedOutput64( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters); + +// ----------------------------------------------------------------------- +// Interceptors handled by the signed process code. + +// Interception of NtCreateSection on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateSection64(PHANDLE section_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PLARGE_INTEGER maximum_size, + ULONG section_page_protection, + ULONG allocation_attributes, + HANDLE file_handle); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_INTERCEPTORS_64_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/internal_types.h b/security/sandbox/chromium/sandbox/win/src/internal_types.h new file mode 100644 index 0000000000..9971da81d0 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/internal_types.h @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ +#define SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ + +#include <stdint.h> + +namespace sandbox { + +const wchar_t kNtdllName[] = L"ntdll.dll"; +const wchar_t kKerneldllName[] = L"kernel32.dll"; +const wchar_t kKernelBasedllName[] = L"kernelbase.dll"; + +// Defines the supported C++ types encoding to numeric id. Like a simplified +// RTTI. Note that true C++ RTTI will not work because the types are not +// polymorphic anyway. +enum ArgType { + INVALID_TYPE = 0, + WCHAR_TYPE, + UINT32_TYPE, + UNISTR_TYPE, + VOIDPTR_TYPE, + INPTR_TYPE, + INOUTPTR_TYPE, + LAST_TYPE +}; + +// Encapsulates a pointer to a buffer and the size of the buffer. +class CountedBuffer { + public: + CountedBuffer(void* buffer, uint32_t size) : size_(size), buffer_(buffer) {} + + uint32_t Size() const { return size_; } + + void* Buffer() const { return buffer_; } + + private: + uint32_t size_; + void* buffer_; +}; + +// Helper class to convert void-pointer packed ints for both +// 32 and 64 bit builds. This construct is non-portable. +class IPCInt { + public: + explicit IPCInt(void* buffer) { buffer_.vp = buffer; } + + explicit IPCInt(uint32_t i32) { + buffer_.vp = nullptr; + buffer_.i32 = i32; + } + + uint32_t As32Bit() const { return buffer_.i32; } + + void* AsVoidPtr() const { return buffer_.vp; } + + private: + union U { + void* vp; + uint32_t i32; + } buffer_; +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_INTERNAL_TYPES_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_args.cc b/security/sandbox/chromium/sandbox/win/src/ipc_args.cc new file mode 100644 index 0000000000..c0c64f1864 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_args.cc @@ -0,0 +1,96 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/ipc_args.h" + +#include <stddef.h> + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" + +namespace sandbox { + +// Releases memory allocated for IPC arguments, if needed. +void ReleaseArgs(const IPCParams* ipc_params, void* args[kMaxIpcParams]) { + for (size_t i = 0; i < kMaxIpcParams; i++) { + switch (ipc_params->args[i]) { + case WCHAR_TYPE: { + delete reinterpret_cast<std::wstring*>(args[i]); + args[i] = nullptr; + break; + } + case INPTR_TYPE: + case INOUTPTR_TYPE: { + delete reinterpret_cast<CountedBuffer*>(args[i]); + args[i] = nullptr; + break; + } + default: + break; + } + } +} + +// Fills up the list of arguments (args and ipc_params) for an IPC call. +bool GetArgs(CrossCallParamsEx* params, + IPCParams* ipc_params, + void* args[kMaxIpcParams]) { + if (kMaxIpcParams < params->GetParamsCount()) + return false; + + for (uint32_t i = 0; i < params->GetParamsCount(); i++) { + uint32_t size; + ArgType type; + args[i] = params->GetRawParameter(i, &size, &type); + if (args[i]) { + ipc_params->args[i] = type; + switch (type) { + case WCHAR_TYPE: { + std::unique_ptr<std::wstring> data(new std::wstring); + if (!params->GetParameterStr(i, data.get())) { + args[i] = 0; + ReleaseArgs(ipc_params, args); + return false; + } + args[i] = data.release(); + break; + } + case UINT32_TYPE: { + uint32_t data; + if (!params->GetParameter32(i, &data)) { + ReleaseArgs(ipc_params, args); + return false; + } + IPCInt ipc_int(data); + args[i] = ipc_int.AsVoidPtr(); + break; + } + case VOIDPTR_TYPE: { + void* data; + if (!params->GetParameterVoidPtr(i, &data)) { + ReleaseArgs(ipc_params, args); + return false; + } + args[i] = data; + break; + } + case INPTR_TYPE: + case INOUTPTR_TYPE: { + if (!args[i]) { + ReleaseArgs(ipc_params, args); + return false; + } + CountedBuffer* buffer = new CountedBuffer(args[i], size); + args[i] = buffer; + break; + } + default: + break; + } + } + } + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_args.h b/security/sandbox/chromium/sandbox/win/src/ipc_args.h new file mode 100644 index 0000000000..58b7507dee --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_args.h @@ -0,0 +1,24 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_IPC_ARGS_H_ +#define SANDBOX_WIN_SRC_IPC_ARGS_H_ + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" + +namespace sandbox { + +// Releases memory allocated for IPC arguments. +void ReleaseArgs(const IPCParams* ipc_params, void* args[kMaxIpcParams]); + +// Fills up the list of arguments (args and ipc_params) for an IPC call. +// Call ReleaseArgs on |ipc_params| and |args| after calling this. +bool GetArgs(CrossCallParamsEx* params, + IPCParams* ipc_params, + void* args[kMaxIpcParams]); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_IPC_ARGS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_ping_test.cc b/security/sandbox/chromium/sandbox/win/src/ipc_ping_test.cc new file mode 100644 index 0000000000..44f6be433d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_ping_test.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Tests that the IPC is working by issuing a special IPC that is not exposed +// in the public API. +SBOX_TESTS_COMMAND int IPC_Ping(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED; + + TargetServices* ts = SandboxFactory::GetTargetServices(); + if (!ts) + return SBOX_TEST_FAILED; + + // Downcast because we have internal knowledge of the object returned. + TargetServicesBase* ts_base = reinterpret_cast<TargetServicesBase*>(ts); + + int version = 0; + if (L'1' == argv[0][0]) + version = 1; + else + version = 2; + + if (!ts_base->TestIPCPing(version)) + return SBOX_TEST_FAILED; + + ::Sleep(1); + if (!ts_base->TestIPCPing(version)) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +// The IPC ping test should work before and after the token drop. +TEST(IPCTest, IPCPingTestSimple) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 1")); +} + +TEST(IPCTest, IPCPingTestWithOutput) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(EVERY_STATE); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 2")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"IPC_Ping 2")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_tags.h b/security/sandbox/chromium/sandbox/win/src/ipc_tags.h new file mode 100644 index 0000000000..ec6de4a66a --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_tags.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_IPC_TAGS_H__ +#define SANDBOX_SRC_IPC_TAGS_H__ + +namespace sandbox { + +enum class IpcTag { + UNUSED = 0, + PING1, // Takes a cookie in parameters and returns the cookie + // multiplied by 2 and the tick_count. Used for testing only. + PING2, // Takes an in/out cookie in parameters and modify the cookie + // to be multiplied by 3. Used for testing only. + NTCREATEFILE, + NTOPENFILE, + NTQUERYATTRIBUTESFILE, + NTQUERYFULLATTRIBUTESFILE, + NTSETINFO_RENAME, + CREATENAMEDPIPEW, + NTOPENTHREAD, + NTOPENPROCESS, + NTOPENPROCESSTOKEN, + NTOPENPROCESSTOKENEX, + CREATEPROCESSW, + CREATEEVENT, + OPENEVENT, + NTCREATEKEY, + NTOPENKEY, + DUPLICATEHANDLEPROXY, + GDI_GDIDLLINITIALIZE, + GDI_GETSTOCKOBJECT, + USER_REGISTERCLASSW, + CREATETHREAD, + USER_ENUMDISPLAYMONITORS, + USER_ENUMDISPLAYDEVICES, + USER_GETMONITORINFO, + GDI_CREATEOPMPROTECTEDOUTPUTS, + GDI_GETCERTIFICATE, + GDI_GETCERTIFICATESIZE, + GDI_DESTROYOPMPROTECTEDOUTPUT, + GDI_CONFIGUREOPMPROTECTEDOUTPUT, + GDI_GETOPMINFORMATION, + GDI_GETOPMRANDOMNUMBER, + GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE, + GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS, + NTCREATESECTION, + GETCOMPLEXLINEBREAKS, + LAST +}; + +constexpr size_t kMaxServiceCount = 64; +constexpr size_t kMaxIpcTag = static_cast<size_t>(IpcTag::LAST); +static_assert(kMaxIpcTag <= kMaxServiceCount, "kMaxServiceCount is too low"); + +} // namespace sandbox + +#endif // SANDBOX_SRC_IPC_TAGS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/ipc_unittest.cc b/security/sandbox/chromium/sandbox/win/src/ipc_unittest.cc new file mode 100644 index 0000000000..71f7aff4cc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/ipc_unittest.cc @@ -0,0 +1,632 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Helper function to make the fake shared memory with some +// basic elements initialized. +IPCControl* MakeChannels(size_t channel_size, + size_t total_shared_size, + size_t* base_start) { + // Allocate memory + char* mem = new char[total_shared_size]; + memset(mem, 0, total_shared_size); + // Calculate how many channels we can fit in the shared memory. + total_shared_size -= offsetof(IPCControl, channels); + size_t channel_count = + total_shared_size / (sizeof(ChannelControl) + channel_size); + // Calculate the start of the first channel. + *base_start = + (sizeof(ChannelControl) * channel_count) + offsetof(IPCControl, channels); + // Setup client structure. + IPCControl* client_control = reinterpret_cast<IPCControl*>(mem); + client_control->channels_count = channel_count; + return client_control; +} + +enum TestFixMode { FIX_NO_EVENTS, FIX_PONG_READY, FIX_PONG_NOT_READY }; + +void FixChannels(IPCControl* client_control, + size_t base_start, + size_t channel_size, + TestFixMode mode) { + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + channel.channel_base = base_start; + channel.state = kFreeChannel; + if (mode != FIX_NO_EVENTS) { + bool signaled = (FIX_PONG_READY == mode) ? true : false; + channel.ping_event = ::CreateEventW(nullptr, false, false, nullptr); + channel.pong_event = ::CreateEventW(nullptr, false, signaled, nullptr); + } + base_start += channel_size; + } +} + +void CloseChannelEvents(IPCControl* client_control) { + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + ::CloseHandle(channel.ping_event); + ::CloseHandle(channel.pong_event); + } +} + +TEST(IPCTest, ChannelMaker) { + // Test that our testing rig is computing offsets properly. We should have + // 5 channnels and the offset to the first channel is 108 bytes in 32 bits + // and 216 in 64 bits. + size_t channel_start = 0; + IPCControl* client_control = MakeChannels(12 * 64, 4096, &channel_start); + ASSERT_TRUE(client_control); + EXPECT_EQ(5u, client_control->channels_count); +#if defined(_WIN64) + EXPECT_EQ(216u, channel_start); +#else + EXPECT_EQ(108u, channel_start); +#endif + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, ClientLockUnlock) { + // Make 7 channels of kIPCChannelSize (1kb) each. Test that we lock and + // unlock channels properly. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_NO_EVENTS); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + // Test that we lock the first 3 channels in sequence. + void* buff0 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff1 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff2 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[2].channel_base == buff2); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + // Test that we unlock and re-lock the right channel. + client.FreeBuffer(buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff2b = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff2b); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + client.FreeBuffer(buff0); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallStrPacking) { + // This test tries the CrossCall object with null and non-null string + // combination of parameters, integer types and verifies that the unpacker + // can read them properly. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 4, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + IpcTag tag1 = IpcTag::PING1; + const wchar_t* text = L"98765 - 43210"; + std::wstring copied_text; + CrossCallParamsEx* actual_params; + + CrossCall(client, tag1, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1u, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_STREQ(text, copied_text.c_str()); + copied_text.clear(); + + // Check with an empty string. + IpcTag tag2 = IpcTag::PING2; + const wchar_t* null_text = nullptr; + CrossCall(client, tag2, null_text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1u, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + uint32_t param_size = 1; + ArgType type = INVALID_TYPE; + void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + EXPECT_TRUE(param_addr); + EXPECT_EQ(0u, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_TRUE(copied_text.empty()); + + IpcTag tag3 = IpcTag::PING1; + param_size = 1; + copied_text.clear(); + + // Check with an empty string and a non-empty string. + CrossCall(client, tag3, null_text, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(2u, actual_params->GetParamsCount()); + EXPECT_EQ(tag3, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + EXPECT_TRUE(param_addr); + EXPECT_EQ(0u, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_TRUE(copied_text.empty()); + EXPECT_TRUE(actual_params->GetParameterStr(1, &copied_text)); + EXPECT_STREQ(text, copied_text.c_str()); + + param_size = 1; + std::wstring copied_text_p0, copied_text_p2; + + const wchar_t* text2 = L"AeFG"; + CrossCall(client, tag1, text2, null_text, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(3u, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text_p0)); + EXPECT_STREQ(text2, copied_text_p0.c_str()); + EXPECT_TRUE(actual_params->GetParameterStr(2, &copied_text_p2)); + EXPECT_STREQ(text, copied_text_p2.c_str()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + EXPECT_TRUE(param_addr); + EXPECT_EQ(0u, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallIntPacking) { + // Check handling for regular 32 bit integers used in Windows. + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 4, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + IpcTag tag1 = IpcTag::PING1; + IpcTag tag2 = IpcTag::PING2; + const wchar_t* text = L"godzilla"; + CrossCallParamsEx* actual_params; + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + DWORD dw = 0xE6578; + CrossCall(client, tag2, dw, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1u, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + ArgType type = INVALID_TYPE; + uint32_t param_size = 1; + void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + ASSERT_EQ(sizeof(dw), param_size); + EXPECT_EQ(UINT32_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&dw, param_addr, param_size)); + + // Check handling for windows HANDLES. + HANDLE h = HANDLE(0x70000500); + CrossCall(client, tag1, text, h, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(2u, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + + // Check combination of 32 and 64 bits. + CrossCall(client, tag2, h, dw, h, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(3u, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + ASSERT_EQ(sizeof(dw), param_size); + EXPECT_EQ(UINT32_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&dw, param_addr, param_size)); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(2, ¶m_size, &type); + ASSERT_EQ(sizeof(h), param_size); + EXPECT_EQ(VOIDPTR_TYPE, type); + ASSERT_TRUE(param_addr); + EXPECT_EQ(0, memcmp(&h, param_addr, param_size)); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallValidation) { + // First a sanity test with a well formed parameter object. + unsigned long value = 124816; + IpcTag kTag = IpcTag::PING1; + const uint32_t kBufferSize = 256; + ActualCallParams<1, kBufferSize> params_1(kTag); + params_1.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + void* buffer = const_cast<void*>(params_1.GetBuffer()); + + uint32_t out_size = 0; + CrossCallParamsEx* ccp = 0; + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, params_1.GetSize(), + &out_size); + ASSERT_TRUE(ccp); + EXPECT_TRUE(ccp->GetBuffer() != buffer); + EXPECT_EQ(kTag, ccp->GetTag()); + EXPECT_EQ(1u, ccp->GetParamsCount()); + delete[](reinterpret_cast<char*>(ccp)); + + // Test that we handle integer overflow on the number of params + // correctly. We use a test-only ctor for ActualCallParams that + // allows to create malformed cross-call buffers. + const int32_t kPtrDiffSz = sizeof(ptrdiff_t); + for (int32_t ix = -1; ix != 3; ++ix) { + uint32_t fake_num_params = (UINT32_MAX / kPtrDiffSz) + ix; + ActualCallParams<1, kBufferSize> params_2(kTag, fake_num_params); + params_2.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + buffer = const_cast<void*>(params_2.GetBuffer()); + + EXPECT_TRUE(buffer); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, params_1.GetSize(), + &out_size); + // If the buffer is malformed the return is nullptr. + EXPECT_TRUE(!ccp); + } + + ActualCallParams<1, kBufferSize> params_3(kTag, 1); + params_3.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + buffer = const_cast<void*>(params_3.GetBuffer()); + EXPECT_TRUE(buffer); + + uint32_t correct_size = params_3.OverrideSize(1); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(!ccp); + + // The correct_size is 8 bytes aligned. + params_3.OverrideSize(correct_size - 7); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(!ccp); + + params_3.OverrideSize(correct_size); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(ccp); + + // Make sure that two parameters work as expected. + ActualCallParams<2, kBufferSize> params_4(kTag, 2); + params_4.CopyParamIn(0, &value, sizeof(value), false, UINT32_TYPE); + params_4.CopyParamIn(1, buffer, sizeof(buffer), false, VOIDPTR_TYPE); + buffer = const_cast<void*>(params_4.GetBuffer()); + EXPECT_TRUE(buffer); + + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(ccp); + +#if defined(_WIN64) + correct_size = params_4.OverrideSize(1); + params_4.OverrideSize(correct_size - 1); + ccp = CrossCallParamsEx::CreateFromBuffer(buffer, kBufferSize, &out_size); + EXPECT_TRUE(!ccp); +#endif +} + +// This structure is passed to the mock server threads to simulate +// the server side IPC so it has the required kernel objects. +struct ServerEvents { + HANDLE ping; + HANDLE pong; + volatile LONG* state; + HANDLE mutex; +}; + +// This is the server thread that quicky answers an IPC and exits. +DWORD WINAPI QuickResponseServer(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->ping, INFINITE); + ::InterlockedExchange(events->state, kAckChannel); + ::SetEvent(events->pong); + return wait_result; +} + +class CrossCallParamsMock : public CrossCallParams { + public: + CrossCallParamsMock(IpcTag tag, uint32_t params_count) + : CrossCallParams(tag, params_count) {} +}; + +void FakeOkAnswerInChannel(void* channel) { + CrossCallReturn* answer = reinterpret_cast<CrossCallReturn*>(channel); + answer->call_outcome = SBOX_ALL_OK; +} + +// Create two threads that will quickly answer IPCs; the first one +// using channel 1 (channel 0 is busy) and one using channel 0. No time-out +// should occur. +TEST(IPCTest, ClientFastServer) { + const size_t channel_size = kIPCChannelSize; + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(channel_size, 4096 * 2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_NOT_READY); + client_control->server_alive = ::CreateMutex(nullptr, false, nullptr); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + ServerEvents events = {0}; + events.ping = client_control->channels[1].ping_event; + events.pong = client_control->channels[1].pong_event; + events.state = &client_control->channels[1].state; + + HANDLE t1 = + ::CreateThread(nullptr, 0, QuickResponseServer, &events, 0, nullptr); + ASSERT_TRUE(t1); + ::CloseHandle(t1); + + void* buff0 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + void* buff1 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + EXPECT_EQ(IpcTag::UNUSED, client_control->channels[1].ipc_tag); + + IpcTag tag = IpcTag::PING1; + CrossCallReturn answer; + CrossCallParamsMock* params1 = new (buff1) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff1); + + ResultCode result = client.DoCall(params1, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff1); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[1].ipc_tag); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + HANDLE t2 = + ::CreateThread(nullptr, 0, QuickResponseServer, &events, 0, nullptr); + ASSERT_TRUE(t2); + ::CloseHandle(t2); + + client.FreeBuffer(buff0); + events.ping = client_control->channels[0].ping_event; + events.pong = client_control->channels[0].pong_event; + events.state = &client_control->channels[0].state; + + tag = IpcTag::PING2; + CrossCallParamsMock* params2 = new (buff0) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff0); + + result = client.DoCall(params2, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff0); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[0].ipc_tag); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + CloseChannelEvents(client_control); + ::CloseHandle(client_control->server_alive); + + delete[] reinterpret_cast<char*>(client_control); +} + +// This is the server thread that very slowly answers an IPC and exits. Note +// that the pong event needs to be signaled twice. +DWORD WINAPI SlowResponseServer(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->ping, INFINITE); + ::Sleep(kIPCWaitTimeOut1 + kIPCWaitTimeOut2 + 200); + ::InterlockedExchange(events->state, kAckChannel); + ::SetEvent(events->pong); + return wait_result; +} + +// This thread's job is to keep the mutex locked. +DWORD WINAPI MainServerThread(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->mutex, INFINITE); + Sleep(kIPCWaitTimeOut1 * 20); + return wait_result; +} + +// Creates a server thread that answers the IPC so slow that is guaranteed to +// trigger the time-out code path in the client. A second thread is created +// to hold locked the server_alive mutex: this signals the client that the +// server is not dead and it retries the wait. +TEST(IPCTest, ClientSlowServer) { + size_t base_start = 0; + IPCControl* client_control = + MakeChannels(kIPCChannelSize, 4096 * 2, &base_start); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_NOT_READY); + client_control->server_alive = ::CreateMutex(nullptr, false, nullptr); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + ServerEvents events = {0}; + events.ping = client_control->channels[0].ping_event; + events.pong = client_control->channels[0].pong_event; + events.state = &client_control->channels[0].state; + + HANDLE t1 = + ::CreateThread(nullptr, 0, SlowResponseServer, &events, 0, nullptr); + ASSERT_TRUE(t1); + ::CloseHandle(t1); + + ServerEvents events2 = {0}; + events2.pong = events.pong; + events2.mutex = client_control->server_alive; + + HANDLE t2 = + ::CreateThread(nullptr, 0, MainServerThread, &events2, 0, nullptr); + ASSERT_TRUE(t2); + ::CloseHandle(t2); + + ::Sleep(1); + + void* buff0 = client.GetBuffer(); + IpcTag tag = IpcTag::PING1; + CrossCallReturn answer; + CrossCallParamsMock* params1 = new (buff0) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff0); + + ResultCode result = client.DoCall(params1, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff0); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[0].ipc_tag); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + + CloseChannelEvents(client_control); + ::CloseHandle(client_control->server_alive); + delete[] reinterpret_cast<char*>(client_control); +} + +// This test-only IPC dispatcher has two handlers with the same signature +// but only CallOneHandler should be used. +class UnitTestIPCDispatcher : public Dispatcher { + public: + UnitTestIPCDispatcher(); + ~UnitTestIPCDispatcher() override {} + + bool SetupService(InterceptionManager* manager, IpcTag service) override { + return true; + } + + private: + bool CallOneHandler(IPCInfo* ipc, HANDLE p1, uint32_t p2) { + ipc->return_info.extended[0].handle = p1; + ipc->return_info.extended[1].unsigned_int = p2; + return true; + } + + bool CallTwoHandler(IPCInfo* ipc, HANDLE p1, uint32_t p2) { return true; } +}; + +UnitTestIPCDispatcher::UnitTestIPCDispatcher() { + static const IPCCall call_one = {{IpcTag::PING1, {VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &UnitTestIPCDispatcher::CallOneHandler)}; + static const IPCCall call_two = {{IpcTag::PING2, {VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &UnitTestIPCDispatcher::CallTwoHandler)}; + ipc_calls_.push_back(call_one); + ipc_calls_.push_back(call_two); +} + +// This test does most of the shared memory IPC client-server roundtrip +// and tests the packing, unpacking and call dispatching. +TEST(IPCTest, SharedMemServerTests) { + size_t base_start = 0; + IPCControl* client_control = MakeChannels(kIPCChannelSize, 4096, &base_start); + client_control->server_alive = HANDLE(1); + FixChannels(client_control, base_start, kIPCChannelSize, FIX_PONG_READY); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + HANDLE bar = HANDLE(191919); + DWORD foo = 6767676; + CrossCall(client, IpcTag::PING1, bar, foo, &answer); + void* buff = client.GetBuffer(); + ASSERT_TRUE(buff); + + UnitTestIPCDispatcher dispatcher; + // Since we are directly calling InvokeCallback, most of this structure + // can be set to nullptr. + sandbox::SharedMemIPCServer::ServerControl srv_control = {}; + srv_control.channel_size = kIPCChannelSize; + srv_control.shared_base = reinterpret_cast<char*>(client_control); + srv_control.dispatcher = &dispatcher; + + sandbox::CrossCallReturn call_return = {0}; + EXPECT_TRUE( + SharedMemIPCServer::InvokeCallback(&srv_control, buff, &call_return)); + EXPECT_EQ(SBOX_ALL_OK, call_return.call_outcome); + EXPECT_TRUE(bar == call_return.extended[0].handle); + EXPECT_EQ(foo, call_return.extended[1].unsigned_int); + + CloseChannelEvents(client_control); + delete[] reinterpret_cast<char*>(client_control); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/job.cc b/security/sandbox/chromium/sandbox/win/src/job.cc new file mode 100644 index 0000000000..39baffb193 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/job.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/job.h" + +#include <stddef.h> +#include <utility> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/restricted_token.h" + +namespace sandbox { + +Job::Job() : job_handle_(nullptr) {} + +Job::~Job() {} + +DWORD Job::Init(JobLevel security_level, + const wchar_t* job_name, + DWORD ui_exceptions, + size_t memory_limit) { + if (job_handle_.IsValid()) + return ERROR_ALREADY_INITIALIZED; + + job_handle_.Set(::CreateJobObject(nullptr, // No security attribute + job_name)); + if (!job_handle_.IsValid()) + return ::GetLastError(); + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {}; + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {}; + + // Set the settings for the different security levels. Note: The higher levels + // inherit from the lower levels. + switch (security_level) { + case JOB_LOCKDOWN: { + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION; + FALLTHROUGH; + } + case JOB_RESTRICTED: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_WRITECLIPBOARD; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_READCLIPBOARD; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_GLOBALATOMS; + FALLTHROUGH; + } + case JOB_LIMITED_USER: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_DISPLAYSETTINGS; + jeli.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_ACTIVE_PROCESS; + jeli.BasicLimitInformation.ActiveProcessLimit = 1; + FALLTHROUGH; + } + case JOB_INTERACTIVE: { + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_DESKTOP; + jbur.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS; + FALLTHROUGH; + } + case JOB_UNPROTECTED: { + if (memory_limit) { + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_PROCESS_MEMORY; + jeli.ProcessMemoryLimit = memory_limit; + } + + jeli.BasicLimitInformation.LimitFlags |= + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + break; + } + default: { return ERROR_BAD_ARGUMENTS; } + } + + if (!::SetInformationJobObject(job_handle_.Get(), + JobObjectExtendedLimitInformation, &jeli, + sizeof(jeli))) { + return ::GetLastError(); + } + + jbur.UIRestrictionsClass = jbur.UIRestrictionsClass & (~ui_exceptions); + if (!::SetInformationJobObject(job_handle_.Get(), + JobObjectBasicUIRestrictions, &jbur, + sizeof(jbur))) { + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +DWORD Job::UserHandleGrantAccess(HANDLE handle) { + if (!job_handle_.IsValid()) + return ERROR_NO_DATA; + + if (!::UserHandleGrantAccess(handle, job_handle_.Get(), + true)) { // Access allowed. + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +base::win::ScopedHandle Job::Take() { + return std::move(job_handle_); +} + +DWORD Job::AssignProcessToJob(HANDLE process_handle) { + if (!job_handle_.IsValid()) + return ERROR_NO_DATA; + + if (!::AssignProcessToJobObject(job_handle_.Get(), process_handle)) + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/job.h b/security/sandbox/chromium/sandbox/win/src/job.h new file mode 100644 index 0000000000..153080bf6b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/job.h @@ -0,0 +1,66 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_JOB_H_ +#define SANDBOX_SRC_JOB_H_ + +#include <stddef.h> + +#include "base/macros.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/restricted_token_utils.h" + +namespace sandbox { + +// Handles the creation of job objects based on a security profile. +// Sample usage: +// Job job; +// job.Init(JOB_LOCKDOWN, nullptr); //no job name +// job.AssignProcessToJob(process_handle); +class Job { + public: + Job(); + + ~Job(); + + // Initializes and creates the job object. The security of the job is based + // on the security_level parameter. + // job_name can be nullptr if the job is unnamed. + // If the chosen profile has too many ui restrictions, you can disable some + // by specifying them in the ui_exceptions parameters. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD Init(JobLevel security_level, + const wchar_t* job_name, + DWORD ui_exceptions, + size_t memory_limit); + + // Assigns the process referenced by process_handle to the job. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AssignProcessToJob(HANDLE process_handle); + + // Grants access to "handle" to the job. All processes in the job can + // subsequently recognize and use the handle. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD UserHandleGrantAccess(HANDLE handle); + + // Revokes ownership to the job handle and returns it. + // If the object is not yet initialized, it returns an invalid handle. + base::win::ScopedHandle Take(); + + private: + // Handle to the job referenced by the object. + base::win::ScopedHandle job_handle_; + + DISALLOW_COPY_AND_ASSIGN(Job); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_JOB_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/job_unittest.cc b/security/sandbox/chromium/sandbox/win/src/job_unittest.cc new file mode 100644 index 0000000000..536900dc40 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/job_unittest.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for the job object. + +#include "sandbox/win/src/job.h" + +#include "base/win/scoped_process_information.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Tests the creation and destruction of the job. +TEST(JobTest, TestCreation) { + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + // check if the job exists. + HANDLE job_handle = + ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name"); + ASSERT_TRUE(job_handle); + + if (job_handle) + CloseHandle(job_handle); + } + + // Check if the job is destroyed when the object goes out of scope. + HANDLE job_handle = ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name"); + ASSERT_TRUE(!job_handle); + ASSERT_EQ(static_cast<DWORD>(ERROR_FILE_NOT_FOUND), ::GetLastError()); +} + +// Tests the method "Take". +TEST(JobTest, Take) { + base::win::ScopedHandle job_handle; + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + job_handle = job.Take(); + ASSERT_TRUE(job_handle.IsValid()); + } + + // Check to be sure that the job is still alive even after the object is gone + // out of scope. + HANDLE job_handle_dup = + ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name"); + ASSERT_TRUE(job_handle_dup); + + // Remove all references. + if (job_handle_dup) + ::CloseHandle(job_handle_dup); + + job_handle.Close(); + + // Check if the jbo is really dead. + job_handle_dup = ::OpenJobObjectW(GENERIC_ALL, false, L"my_test_job_name"); + ASSERT_TRUE(!job_handle_dup); + ASSERT_EQ(static_cast<DWORD>(ERROR_FILE_NOT_FOUND), ::GetLastError()); +} + +// Tests the ui exceptions +TEST(JobTest, TestExceptions) { + base::win::ScopedHandle job_handle; + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", + JOB_OBJECT_UILIMIT_READCLIPBOARD, 0)); + + job_handle = job.Take(); + ASSERT_TRUE(job_handle.IsValid()); + + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0}; + DWORD size = sizeof(jbur); + ASSERT_TRUE(::QueryInformationJobObject( + job_handle.Get(), JobObjectBasicUIRestrictions, &jbur, size, &size)); + + ASSERT_EQ(0u, jbur.UIRestrictionsClass & JOB_OBJECT_UILIMIT_READCLIPBOARD); + job_handle.Close(); + } + + // Scope the creation of Job. + { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + + job_handle = job.Take(); + ASSERT_TRUE(job_handle.IsValid()); + + JOBOBJECT_BASIC_UI_RESTRICTIONS jbur = {0}; + DWORD size = sizeof(jbur); + ASSERT_TRUE(::QueryInformationJobObject( + job_handle.Get(), JobObjectBasicUIRestrictions, &jbur, size, &size)); + + ASSERT_EQ(static_cast<DWORD>(JOB_OBJECT_UILIMIT_READCLIPBOARD), + jbur.UIRestrictionsClass & JOB_OBJECT_UILIMIT_READCLIPBOARD); + } +} + +// Tests the error case when the job is initialized twice. +TEST(JobTest, DoubleInit) { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_LOCKDOWN, L"my_test_job_name", 0, 0)); + ASSERT_EQ(static_cast<DWORD>(ERROR_ALREADY_INITIALIZED), + job.Init(JOB_LOCKDOWN, L"test", 0, 0)); +} + +// Tests the error case when we use a method and the object is not yet +// initialized. +TEST(JobTest, NoInit) { + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_NO_DATA), + job.UserHandleGrantAccess(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_NO_DATA), job.AssignProcessToJob(nullptr)); + ASSERT_FALSE(job.Take().IsValid()); +} + +// Tests the initialization of the job with different security level. +TEST(JobTest, SecurityLevel) { + Job job1; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job1.Init(JOB_LOCKDOWN, L"job1", 0, 0)); + + Job job2; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job2.Init(JOB_RESTRICTED, L"job2", 0, 0)); + + Job job3; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job3.Init(JOB_LIMITED_USER, L"job3", 0, 0)); + + Job job4; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job4.Init(JOB_INTERACTIVE, L"job4", 0, 0)); + + Job job5; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job5.Init(JOB_UNPROTECTED, L"job5", 0, 0)); + + // JOB_NONE means we run without a job object so Init should fail. + Job job6; + ASSERT_EQ(static_cast<DWORD>(ERROR_BAD_ARGUMENTS), + job6.Init(JOB_NONE, L"job6", 0, 0)); + + Job job7; + ASSERT_EQ(static_cast<DWORD>(ERROR_BAD_ARGUMENTS), + job7.Init(static_cast<JobLevel>(JOB_NONE + 1), L"job7", 0, 0)); +} + +// Tests the method "AssignProcessToJob". +TEST(JobTest, ProcessInJob) { + // Create the job. + Job job; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.Init(JOB_UNPROTECTED, L"job_test_process", 0, 0)); + + wchar_t notepad[] = L"notepad"; + STARTUPINFO si = {sizeof(si)}; + PROCESS_INFORMATION temp_process_info = {}; + ASSERT_TRUE(::CreateProcess(nullptr, notepad, nullptr, nullptr, false, 0, + nullptr, nullptr, &si, &temp_process_info)); + base::win::ScopedProcessInformation pi(temp_process_info); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + job.AssignProcessToJob(pi.process_handle())); + + // Get the job handle. + base::win::ScopedHandle job_handle = job.Take(); + + // Check if the process is in the job. + JOBOBJECT_BASIC_PROCESS_ID_LIST jbpidl = {0}; + DWORD size = sizeof(jbpidl); + EXPECT_TRUE(::QueryInformationJobObject( + job_handle.Get(), JobObjectBasicProcessIdList, &jbpidl, size, &size)); + + EXPECT_EQ(1u, jbpidl.NumberOfAssignedProcesses); + EXPECT_EQ(1u, jbpidl.NumberOfProcessIdsInList); + EXPECT_EQ(pi.process_id(), jbpidl.ProcessIdList[0]); + + EXPECT_TRUE(::TerminateProcess(pi.process_handle(), 0)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.cc new file mode 100644 index 0000000000..85ffebe6e6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/named_pipe_dispatcher.h" + +#include <stdint.h> + +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/named_pipe_interception.h" +#include "sandbox/win/src/named_pipe_policy.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" + +namespace sandbox { + +NamedPipeDispatcher::NamedPipeDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::CREATENAMEDPIPEW, + {WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&NamedPipeDispatcher::CreateNamedPipe)}; + + ipc_calls_.push_back(create_params); +} + +bool NamedPipeDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (IpcTag::CREATENAMEDPIPEW == service) + return INTERCEPT_EAT(manager, kKerneldllName, CreateNamedPipeW, + CREATE_NAMED_PIPE_ID, 36); + + return false; +} + +bool NamedPipeDispatcher::CreateNamedPipe(IPCInfo* ipc, + std::wstring* name, + uint32_t open_mode, + uint32_t pipe_mode, + uint32_t max_instances, + uint32_t out_buffer_size, + uint32_t in_buffer_size, + uint32_t default_timeout) { + ipc->return_info.win32_result = ERROR_ACCESS_DENIED; + ipc->return_info.handle = INVALID_HANDLE_VALUE; + + base::StringPiece16 dotdot(STRING16_LITERAL("..")); + + for (const base::StringPiece16& path : base::SplitStringPiece( + base::AsStringPiece16(*name), STRING16_LITERAL("/"), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + for (const base::StringPiece16& inner : + base::SplitStringPiece(path, STRING16_LITERAL("\\"), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + if (inner == dotdot) + return true; + } + } + + const wchar_t* pipe_name = name->c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(pipe_name); + + EvalResult eval = + policy_base_->EvalPolicy(IpcTag::CREATENAMEDPIPEW, params.GetBase()); + + // "For file I/O, the "\\?\" prefix to a path string tells the Windows APIs to + // disable all string parsing and to send the string that follows it straight + // to the file system." + // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + // This ensures even if there is a path traversal in the pipe name, and it is + // able to get past the checks above, it will still not be allowed to escape + // our allowed namespace. + if (name->compare(0, 4, L"\\\\.\\") == 0) + name->replace(0, 4, L"\\\\\?\\"); + + HANDLE pipe; + DWORD ret = NamedPipePolicy::CreateNamedPipeAction( + eval, *ipc->client_info, *name, open_mode, pipe_mode, max_instances, + out_buffer_size, in_buffer_size, default_timeout, &pipe); + + ipc->return_info.win32_result = ret; + ipc->return_info.handle = pipe; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.h new file mode 100644 index 0000000000..a14f658506 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_dispatcher.h @@ -0,0 +1,46 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ +#define SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles named pipe related IPC calls. +class NamedPipeDispatcher : public Dispatcher { + public: + explicit NamedPipeDispatcher(PolicyBase* policy_base); + ~NamedPipeDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to CreateNamedPipeW() in the + // target. + bool CreateNamedPipe(IPCInfo* ipc, + std::wstring* name, + uint32_t open_mode, + uint32_t pipe_mode, + uint32_t max_instances, + uint32_t out_buffer_size, + uint32_t in_buffer_size, + uint32_t default_timeout); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(NamedPipeDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_NAMED_PIPE_DISPATCHER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.cc b/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.cc new file mode 100644 index 0000000000..aa3d5dc358 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/named_pipe_interception.h" + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +HANDLE WINAPI +TargetCreateNamedPipeW(CreateNamedPipeWFunction orig_CreateNamedPipeW, + LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instance, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + HANDLE pipe = orig_CreateNamedPipeW( + pipe_name, open_mode, pipe_mode, max_instance, out_buffer_size, + in_buffer_size, default_timeout, security_attributes); + if (INVALID_HANDLE_VALUE != pipe) + return pipe; + + mozilla::sandboxing::LogBlocked("CreateNamedPipeW", pipe_name); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return INVALID_HANDLE_VALUE; + + DWORD original_error = ::GetLastError(); + + // We don't support specific Security Attributes. + if (security_attributes) + return INVALID_HANDLE_VALUE; + + do { + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(pipe_name); + + if (!QueryBroker(IpcTag::CREATENAMEDPIPEW, params.GetBase())) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = + CrossCall(ipc, IpcTag::CREATENAMEDPIPEW, pipe_name, open_mode, + pipe_mode, max_instance, out_buffer_size, in_buffer_size, + default_timeout, &answer); + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + + if (ERROR_SUCCESS != answer.win32_result) + return INVALID_HANDLE_VALUE; + + mozilla::sandboxing::LogAllowed("CreateNamedPipeW", pipe_name); + return answer.handle; + } while (false); + + ::SetLastError(original_error); + return INVALID_HANDLE_VALUE; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.h b/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.h new file mode 100644 index 0000000000..8eedd14b31 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_interception.h @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_NAMED_PIPE_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_NAMED_PIPE_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +typedef HANDLE(WINAPI* CreateNamedPipeWFunction)( + LPCWSTR lpName, + DWORD dwOpenMode, + DWORD dwPipeMode, + DWORD nMaxInstances, + DWORD nOutBufferSize, + DWORD nInBufferSize, + DWORD nDefaultTimeOut, + LPSECURITY_ATTRIBUTES lpSecurityAttributes); + +// Interception of CreateNamedPipeW in kernel32.dll +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateNamedPipeW(CreateNamedPipeWFunction orig_CreateNamedPipeW, + LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instance, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_NAMED_PIPE_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.cc b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.cc new file mode 100644 index 0000000000..bfad75852d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/named_pipe_policy.h" + +#include <string> + +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace { + +// Creates a named pipe and duplicates the handle to 'target_process'. The +// remaining parameters are the same as CreateNamedPipeW(). +HANDLE CreateNamedPipeHelper(HANDLE target_process, + LPCWSTR pipe_name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instances, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + LPSECURITY_ATTRIBUTES security_attributes) { + HANDLE pipe = ::CreateNamedPipeW( + pipe_name, open_mode, pipe_mode, max_instances, out_buffer_size, + in_buffer_size, default_timeout, security_attributes); + if (INVALID_HANDLE_VALUE == pipe) + return pipe; + + HANDLE new_pipe; + if (!::DuplicateHandle(::GetCurrentProcess(), pipe, target_process, &new_pipe, + 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return INVALID_HANDLE_VALUE; + } + + return new_pipe; +} + +} // namespace + +namespace sandbox { + +bool NamedPipePolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + if (TargetPolicy::NAMEDPIPES_ALLOW_ANY != semantics) { + return false; + } + PolicyRule pipe(ASK_BROKER); + if (!pipe.AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IpcTag::CREATENAMEDPIPEW, &pipe)) { + return false; + } + return true; +} + +DWORD NamedPipePolicy::CreateNamedPipeAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instances, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + HANDLE* pipe) { + *pipe = INVALID_HANDLE_VALUE; + // The only action supported is ASK_BROKER which means create the pipe. + if (ASK_BROKER != eval_result) { + return ERROR_ACCESS_DENIED; + } + + *pipe = CreateNamedPipeHelper(client_info.process, name.c_str(), open_mode, + pipe_mode, max_instances, out_buffer_size, + in_buffer_size, default_timeout, nullptr); + + if (INVALID_HANDLE_VALUE == *pipe) + return ERROR_ACCESS_DENIED; + + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.h b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.h new file mode 100644 index 0000000000..4ad272f16f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy.h @@ -0,0 +1,43 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_NAMED_PIPE_POLICY_H__ +#define SANDBOX_SRC_NAMED_PIPE_POLICY_H__ + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to named pipe creation. +class NamedPipePolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level. + // policy rule for named pipe creation + // 'name' is the named pipe to be created + // 'semantics' is the desired semantics. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Processes a 'CreateNamedPipeW()' request from the target. + static DWORD CreateNamedPipeAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& name, + DWORD open_mode, + DWORD pipe_mode, + DWORD max_instances, + DWORD out_buffer_size, + DWORD in_buffer_size, + DWORD default_timeout, + HANDLE* pipe); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_NAMED_PIPE_POLICY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/named_pipe_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy_test.cc new file mode 100644 index 0000000000..db532d618d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/named_pipe_policy_test.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/windows_version.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int NamedPipe_Create(int argc, wchar_t** argv) { + if (argc < 1 || argc > 2 || !argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + HANDLE pipe = ::CreateNamedPipeW( + argv[0], PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1, 4096, 4096, 2000, nullptr); + if (INVALID_HANDLE_VALUE == pipe) + return SBOX_TEST_DENIED; + + // The second parameter allows us to enforce an allowlist for where the + // pipe should be in the object namespace after creation. + if (argc == 2) { + std::wstring handle_name; + if (GetHandleName(pipe, &handle_name)) { + if (handle_name.compare(0, wcslen(argv[1]), argv[1]) != 0) + return SBOX_TEST_FAILED; + } else { + return SBOX_TEST_FAILED; + } + } + + OVERLAPPED overlapped = {0}; + overlapped.hEvent = ::CreateEvent(nullptr, true, true, nullptr); + bool result = ::ConnectNamedPipe(pipe, &overlapped); + + if (!result) { + DWORD error = ::GetLastError(); + if (ERROR_PIPE_CONNECTED != error && ERROR_IO_PENDING != error) { + return SBOX_TEST_FAILED; + } + } + + if (!::CloseHandle(pipe)) + return SBOX_TEST_FAILED; + + ::CloseHandle(overlapped.hEvent); + return SBOX_TEST_SUCCEEDED; +} + +// Tests if we can create a pipe in the sandbox. +TEST(NamedPipePolicyTest, CreatePipe) { + TestRunner runner; + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\testbleh")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\bleh")); +} + +// Tests if we can create a pipe with a path traversal in the sandbox. +TEST(NamedPipePolicyTest, CreatePipeTraversal) { + TestRunner runner; + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test\\..\\bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test/../bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test\\../bleh")); + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\test/..\\bleh")); +} + +// This tests that path canonicalization is actually disabled if we use \\?\ +// syntax. +TEST(NamedPipePolicyTest, CreatePipeCanonicalization) { + // "For file I/O, the "\\?\" prefix to a path string tells the Windows APIs to + // disable all string parsing and to send the string that follows it straight + // to the file system." + // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + const wchar_t* argv[2] = {L"\\\\?\\pipe\\test\\..\\bleh", + L"\\Device\\NamedPipe\\test"}; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + NamedPipe_Create(2, const_cast<wchar_t**>(argv))); +} + +// The same test as CreatePipe but this time using strict interceptions. +TEST(NamedPipePolicyTest, CreatePipeStrictInterceptions) { + TestRunner runner; + runner.GetPolicy()->SetStrictInterceptions(); + + // TODO(nsylvain): This policy is wrong because "*" is a valid char in a + // namedpipe name. Here we apply it like a wildcard. http://b/893603 + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_NAMED_PIPES, + TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\test*")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\testbleh")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"NamedPipe_Create \\\\.\\pipe\\bleh")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/nt_internals.h b/security/sandbox/chromium/sandbox/win/src/nt_internals.h new file mode 100644 index 0000000000..6041566436 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/nt_internals.h @@ -0,0 +1,983 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file holds definitions related to the ntdll API. + +#ifndef SANDBOX_WIN_SRC_NT_INTERNALS_H__ +#define SANDBOX_WIN_SRC_NT_INTERNALS_H__ + +#include <windows.h> + +#include <stddef.h> + +typedef LONG NTSTATUS; +#define NT_SUCCESS(st) (st >= 0) +#define NT_ERROR(st) ((((ULONG)(st)) >> 30) == 3) + +// clang-format off +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) +#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002L) +#define STATUS_INVALID_INFO_CLASS ((NTSTATUS)0xC0000003L) +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +#ifndef STATUS_INVALID_PARAMETER +// It is now defined in Windows 2008 SDK. +#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000DL) +#endif +#define STATUS_CONFLICTING_ADDRESSES ((NTSTATUS)0xC0000018L) +#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L) +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034L) +#define STATUS_OBJECT_NAME_COLLISION ((NTSTATUS)0xC0000035L) +#define STATUS_PROCEDURE_NOT_FOUND ((NTSTATUS)0xC000007AL) +#define STATUS_INVALID_IMAGE_FORMAT ((NTSTATUS)0xC000007BL) +#define STATUS_NO_TOKEN ((NTSTATUS)0xC000007CL) +#define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BBL) +#define STATUS_INVALID_IMAGE_HASH ((NTSTATUS)0xC0000428L) +// clang-format on + +#define CURRENT_PROCESS ((HANDLE)-1) +#define CURRENT_THREAD ((HANDLE)-2) +#define NtCurrentProcess CURRENT_PROCESS + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING; +typedef UNICODE_STRING* PUNICODE_STRING; +typedef const UNICODE_STRING* PCUNICODE_STRING; + +typedef struct _STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} STRING; +typedef STRING* PSTRING; + +typedef STRING ANSI_STRING; +typedef PSTRING PANSI_STRING; +typedef CONST PSTRING PCANSI_STRING; + +typedef STRING OEM_STRING; +typedef PSTRING POEM_STRING; +typedef CONST STRING* PCOEM_STRING; + +#define OBJ_CASE_INSENSITIVE 0x00000040L +#define OBJ_OPENIF 0x00000080L + +typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES; +typedef OBJECT_ATTRIBUTES* POBJECT_ATTRIBUTES; + +#define InitializeObjectAttributes(p, n, a, r, s) \ + { \ + (p)->Length = sizeof(OBJECT_ATTRIBUTES); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = nullptr; \ + } + +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +// ----------------------------------------------------------------------- +// File IO + +// Create disposition values. + +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + +// Create/open option flags. + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 + +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 + +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 + +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +// Create/open result values. These are the disposition values returned on the +// io status information. +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + +typedef NTSTATUS(WINAPI* NtCreateFileFunction)( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PLARGE_INTEGER AllocationSize OPTIONAL, + IN ULONG FileAttributes, + IN ULONG ShareAccess, + IN ULONG CreateDisposition, + IN ULONG CreateOptions, + IN PVOID EaBuffer OPTIONAL, + IN ULONG EaLength); + +typedef NTSTATUS(WINAPI* NtOpenFileFunction)(OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG ShareAccess, + IN ULONG OpenOptions); + +typedef NTSTATUS(WINAPI* NtCloseFunction)(IN HANDLE Handle); + +typedef enum _FILE_INFORMATION_CLASS { + FileRenameInformation = 10 +} FILE_INFORMATION_CLASS, + *PFILE_INFORMATION_CLASS; + +typedef struct _FILE_RENAME_INFORMATION { + BOOLEAN ReplaceIfExists; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + +typedef NTSTATUS(WINAPI* NtSetInformationFileFunction)( + IN HANDLE FileHandle, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PVOID FileInformation, + IN ULONG Length, + IN FILE_INFORMATION_CLASS FileInformationClass); + +typedef struct FILE_BASIC_INFORMATION { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + ULONG FileAttributes; +} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQueryAttributesFileFunction)( + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PFILE_BASIC_INFORMATION FileAttributes); + +typedef struct _FILE_NETWORK_OPEN_INFORMATION { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG FileAttributes; +} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQueryFullAttributesFileFunction)( + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PFILE_NETWORK_OPEN_INFORMATION FileAttributes); + +// ----------------------------------------------------------------------- +// Sections + +typedef NTSTATUS(WINAPI* NtCreateSectionFunction)( + OUT PHANDLE SectionHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, + IN PLARGE_INTEGER MaximumSize OPTIONAL, + IN ULONG SectionPageProtection, + IN ULONG AllocationAttributes, + IN HANDLE FileHandle OPTIONAL); + +typedef ULONG SECTION_INHERIT; +#define ViewShare 1 +#define ViewUnmap 2 + +typedef NTSTATUS(WINAPI* NtMapViewOfSectionFunction)( + IN HANDLE SectionHandle, + IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN ULONG_PTR ZeroBits, + IN SIZE_T CommitSize, + IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, + IN OUT PSIZE_T ViewSize, + IN SECTION_INHERIT InheritDisposition, + IN ULONG AllocationType, + IN ULONG Win32Protect); + +typedef NTSTATUS(WINAPI* NtUnmapViewOfSectionFunction)(IN HANDLE ProcessHandle, + IN PVOID BaseAddress); + +typedef enum _SECTION_INFORMATION_CLASS { + SectionBasicInformation = 0, + SectionImageInformation +} SECTION_INFORMATION_CLASS; + +typedef struct _SECTION_BASIC_INFORMATION { + PVOID BaseAddress; + ULONG Attributes; + LARGE_INTEGER Size; +} SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQuerySectionFunction)( + IN HANDLE SectionHandle, + IN SECTION_INFORMATION_CLASS SectionInformationClass, + OUT PVOID SectionInformation, + IN SIZE_T SectionInformationLength, + OUT PSIZE_T ReturnLength OPTIONAL); + +// ----------------------------------------------------------------------- +// Process and Thread + +typedef struct _CLIENT_ID { + PVOID UniqueProcess; + PVOID UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef NTSTATUS(WINAPI* NtOpenThreadFunction)(OUT PHANDLE ThreadHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + IN PCLIENT_ID ClientId); + +typedef NTSTATUS(WINAPI* NtOpenProcessFunction)(OUT PHANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + IN PCLIENT_ID ClientId); + +typedef enum _NT_THREAD_INFORMATION_CLASS { + ThreadBasicInformation, + ThreadTimes, + ThreadPriority, + ThreadBasePriority, + ThreadAffinityMask, + ThreadImpersonationToken, + ThreadDescriptorTableEntry, + ThreadEnableAlignmentFaultFixup, + ThreadEventPair, + ThreadQuerySetWin32StartAddress, + ThreadZeroTlsCell, + ThreadPerformanceCount, + ThreadAmILastThread, + ThreadIdealProcessor, + ThreadPriorityBoost, + ThreadSetTlsArrayAddress, + ThreadIsIoPending, + ThreadHideFromDebugger +} NT_THREAD_INFORMATION_CLASS, + *PNT_THREAD_INFORMATION_CLASS; + +typedef NTSTATUS(WINAPI* NtSetInformationThreadFunction)( + IN HANDLE ThreadHandle, + IN NT_THREAD_INFORMATION_CLASS ThreadInformationClass, + IN PVOID ThreadInformation, + IN ULONG ThreadInformationLength); + +// Partial definition only: +typedef enum _PROCESSINFOCLASS { + ProcessBasicInformation = 0, + ProcessExecuteFlags = 0x22 +} PROCESSINFOCLASS; + +// For the structure documentation, see +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa813741(v=vs.85).aspx +typedef struct _RTL_USER_PROCESS_PARAMETERS { + BYTE Reserved1[16]; + PVOID Reserved2[10]; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; +} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + +// Partial definition only, from +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa813706(v=vs.85).aspx +typedef struct _PEB { + BYTE InheritedAddressSpace; + BYTE ReadImageFileExecOptions; + BYTE BeingDebugged; + BYTE SpareBool; + PVOID Mutant; + PVOID ImageBaseAddress; + PVOID Ldr; + PRTL_USER_PROCESS_PARAMETERS ProcessParameters; +} PEB, *PPEB; + +typedef LONG KPRIORITY; + +typedef struct _PROCESS_BASIC_INFORMATION { + union { + NTSTATUS ExitStatus; + PVOID padding_for_x64_0; + }; + PPEB PebBaseAddress; + KAFFINITY AffinityMask; + union { + KPRIORITY BasePriority; + PVOID padding_for_x64_1; + }; + union { + DWORD UniqueProcessId; + PVOID padding_for_x64_2; + }; + union { + DWORD InheritedFromUniqueProcessId; + PVOID padding_for_x64_3; + }; +} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQueryInformationProcessFunction)( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength OPTIONAL); + +typedef NTSTATUS(WINAPI* NtSetInformationProcessFunction)( + HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + IN PVOID ProcessInformation, + IN ULONG ProcessInformationLength); + +typedef NTSTATUS(WINAPI* NtOpenThreadTokenFunction)(IN HANDLE ThreadHandle, + IN ACCESS_MASK + DesiredAccess, + IN BOOLEAN OpenAsSelf, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS(WINAPI* NtOpenThreadTokenExFunction)(IN HANDLE ThreadHandle, + IN ACCESS_MASK + DesiredAccess, + IN BOOLEAN OpenAsSelf, + IN ULONG HandleAttributes, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS(WINAPI* NtOpenProcessTokenFunction)(IN HANDLE ProcessHandle, + IN ACCESS_MASK + DesiredAccess, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS(WINAPI* NtOpenProcessTokenExFunction)( + IN HANDLE ProcessHandle, + IN ACCESS_MASK DesiredAccess, + IN ULONG HandleAttributes, + OUT PHANDLE TokenHandle); + +typedef NTSTATUS(WINAPI* NtQueryInformationTokenFunction)( + IN HANDLE TokenHandle, + IN TOKEN_INFORMATION_CLASS TokenInformationClass, + OUT PVOID TokenInformation, + IN ULONG TokenInformationLength, + OUT PULONG ReturnLength); + +typedef NTSTATUS(WINAPI* RtlCreateUserThreadFunction)( + IN HANDLE Process, + IN PSECURITY_DESCRIPTOR ThreadSecurityDescriptor, + IN BOOLEAN CreateSuspended, + IN ULONG ZeroBits, + IN SIZE_T MaximumStackSize, + IN SIZE_T CommittedStackSize, + IN LPTHREAD_START_ROUTINE StartAddress, + IN PVOID Parameter, + OUT PHANDLE Thread, + OUT PCLIENT_ID ClientId); + +typedef NTSTATUS(WINAPI* RtlConvertSidToUnicodeStringFunction)( + OUT PUNICODE_STRING UnicodeString, + IN PSID Sid, + IN BOOLEAN AllocateDestinationString); + +typedef VOID(WINAPI* RtlFreeUnicodeStringFunction)( + IN OUT PUNICODE_STRING UnicodeString); + +// ----------------------------------------------------------------------- +// Registry + +typedef enum _KEY_INFORMATION_CLASS { + KeyBasicInformation = 0, + KeyFullInformation = 2 +} KEY_INFORMATION_CLASS, + *PKEY_INFORMATION_CLASS; + +typedef struct _KEY_BASIC_INFORMATION { + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG NameLength; + WCHAR Name[1]; +} KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION; + +typedef struct _KEY_FULL_INFORMATION { + LARGE_INTEGER LastWriteTime; + ULONG TitleIndex; + ULONG ClassOffset; + ULONG ClassLength; + ULONG SubKeys; + ULONG MaxNameLen; + ULONG MaxClassLen; + ULONG Values; + ULONG MaxValueNameLen; + ULONG MaxValueDataLen; + WCHAR Class[1]; +} KEY_FULL_INFORMATION, *PKEY_FULL_INFORMATION; + +typedef enum _KEY_VALUE_INFORMATION_CLASS { + KeyValueFullInformation = 1 +} KEY_VALUE_INFORMATION_CLASS, + *PKEY_VALUE_INFORMATION_CLASS; + +typedef struct _KEY_VALUE_FULL_INFORMATION { + ULONG TitleIndex; + ULONG Type; + ULONG DataOffset; + ULONG DataLength; + ULONG NameLength; + WCHAR Name[1]; +} KEY_VALUE_FULL_INFORMATION, *PKEY_VALUE_FULL_INFORMATION; + +typedef NTSTATUS(WINAPI* NtCreateKeyFunction)(OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + IN ULONG TitleIndex, + IN PUNICODE_STRING Class OPTIONAL, + IN ULONG CreateOptions, + OUT PULONG Disposition OPTIONAL); + +typedef NTSTATUS(WINAPI* NtOpenKeyFunction)(OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes); + +typedef NTSTATUS(WINAPI* NtOpenKeyExFunction)(OUT PHANDLE KeyHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES + ObjectAttributes, + IN DWORD open_options); + +typedef NTSTATUS(WINAPI* NtDeleteKeyFunction)(IN HANDLE KeyHandle); + +typedef NTSTATUS(WINAPI* RtlFormatCurrentUserKeyPathFunction)( + OUT PUNICODE_STRING RegistryPath); + +typedef NTSTATUS(WINAPI* NtQueryKeyFunction)(IN HANDLE KeyHandle, + IN KEY_INFORMATION_CLASS + KeyInformationClass, + OUT PVOID KeyInformation, + IN ULONG Length, + OUT PULONG ResultLength); + +typedef NTSTATUS(WINAPI* NtEnumerateKeyFunction)(IN HANDLE KeyHandle, + IN ULONG Index, + IN KEY_INFORMATION_CLASS + KeyInformationClass, + OUT PVOID KeyInformation, + IN ULONG Length, + OUT PULONG ResultLength); + +typedef NTSTATUS(WINAPI* NtQueryValueKeyFunction)(IN HANDLE KeyHandle, + IN PUNICODE_STRING ValueName, + IN KEY_VALUE_INFORMATION_CLASS + KeyValueInformationClass, + OUT PVOID KeyValueInformation, + IN ULONG Length, + OUT PULONG ResultLength); + +typedef NTSTATUS(WINAPI* NtSetValueKeyFunction)(IN HANDLE KeyHandle, + IN PUNICODE_STRING ValueName, + IN ULONG TitleIndex OPTIONAL, + IN ULONG Type, + IN PVOID Data, + IN ULONG DataSize); + +// ----------------------------------------------------------------------- +// Memory + +// Don't really need this structure right now. +typedef PVOID PRTL_HEAP_PARAMETERS; + +typedef PVOID(WINAPI* RtlCreateHeapFunction)(IN ULONG Flags, + IN PVOID HeapBase OPTIONAL, + IN SIZE_T ReserveSize OPTIONAL, + IN SIZE_T CommitSize OPTIONAL, + IN PVOID Lock OPTIONAL, + IN PRTL_HEAP_PARAMETERS Parameters + OPTIONAL); + +typedef PVOID(WINAPI* RtlDestroyHeapFunction)(IN PVOID HeapHandle); + +typedef PVOID(WINAPI* RtlAllocateHeapFunction)(IN PVOID HeapHandle, + IN ULONG Flags, + IN SIZE_T Size); + +typedef BOOLEAN(WINAPI* RtlFreeHeapFunction)(IN PVOID HeapHandle, + IN ULONG Flags, + IN PVOID HeapBase); + +typedef NTSTATUS(WINAPI* NtAllocateVirtualMemoryFunction)( + IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN ULONG_PTR ZeroBits, + IN OUT PSIZE_T RegionSize, + IN ULONG AllocationType, + IN ULONG Protect); + +typedef NTSTATUS(WINAPI* NtFreeVirtualMemoryFunction)(IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN OUT PSIZE_T RegionSize, + IN ULONG FreeType); + +typedef enum _MEMORY_INFORMATION_CLASS { + MemoryBasicInformation = 0, + MemoryWorkingSetList, + MemorySectionName, + MemoryBasicVlmInformation +} MEMORY_INFORMATION_CLASS; + +typedef struct _MEMORY_SECTION_NAME { // Information Class 2 + UNICODE_STRING SectionFileName; +} MEMORY_SECTION_NAME, *PMEMORY_SECTION_NAME; + +typedef NTSTATUS(WINAPI* NtQueryVirtualMemoryFunction)( + IN HANDLE ProcessHandle, + IN PVOID BaseAddress, + IN MEMORY_INFORMATION_CLASS MemoryInformationClass, + OUT PVOID MemoryInformation, + IN SIZE_T MemoryInformationLength, + OUT PSIZE_T ReturnLength OPTIONAL); + +typedef NTSTATUS(WINAPI* NtProtectVirtualMemoryFunction)( + IN HANDLE ProcessHandle, + IN OUT PVOID* BaseAddress, + IN OUT PSIZE_T ProtectSize, + IN ULONG NewProtect, + OUT PULONG OldProtect); + +// ----------------------------------------------------------------------- +// Objects + +typedef enum _OBJECT_INFORMATION_CLASS { + ObjectBasicInformation, + ObjectNameInformation, + ObjectTypeInformation, + ObjectAllInformation, + ObjectDataInformation +} OBJECT_INFORMATION_CLASS, + *POBJECT_INFORMATION_CLASS; + +typedef struct _OBJDIR_INFORMATION { + UNICODE_STRING ObjectName; + UNICODE_STRING ObjectTypeName; + BYTE Data[1]; +} OBJDIR_INFORMATION; + +typedef struct _PUBLIC_OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG Reserved[10]; // reserved for internal use +} PUBLIC_OBJECT_BASIC_INFORMATION, *PPUBLIC_OBJECT_BASIC_INFORMATION; + +typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION { + UNICODE_STRING TypeName; + ULONG Reserved[22]; // reserved for internal use +} PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION; + +typedef enum _POOL_TYPE { + NonPagedPool, + PagedPool, + NonPagedPoolMustSucceed, + ReservedType, + NonPagedPoolCacheAligned, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS +} POOL_TYPE; + +typedef struct _OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; + ULONG Reserved[3]; + ULONG NameInformationLength; + ULONG TypeInformationLength; + ULONG SecurityDescriptorLength; + LARGE_INTEGER CreateTime; +} OBJECT_BASIC_INFORMATION, *POBJECT_BASIC_INFORMATION; + +typedef struct _OBJECT_TYPE_INFORMATION { + UNICODE_STRING Name; + ULONG TotalNumberOfObjects; + ULONG TotalNumberOfHandles; + ULONG TotalPagedPoolUsage; + ULONG TotalNonPagedPoolUsage; + ULONG TotalNamePoolUsage; + ULONG TotalHandleTableUsage; + ULONG HighWaterNumberOfObjects; + ULONG HighWaterNumberOfHandles; + ULONG HighWaterPagedPoolUsage; + ULONG HighWaterNonPagedPoolUsage; + ULONG HighWaterNamePoolUsage; + ULONG HighWaterHandleTableUsage; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccess; + BOOLEAN SecurityRequired; + BOOLEAN MaintainHandleCount; + USHORT MaintainTypeList; + POOL_TYPE PoolType; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +typedef enum _SYSTEM_INFORMATION_CLASS { + SystemHandleInformation = 16 +} SYSTEM_INFORMATION_CLASS; + +typedef struct _SYSTEM_HANDLE_INFORMATION { + USHORT ProcessId; + USHORT CreatorBackTraceIndex; + UCHAR ObjectTypeNumber; + UCHAR Flags; + USHORT Handle; + PVOID Object; + ACCESS_MASK GrantedAccess; +} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX { + ULONG NumberOfHandles; + SYSTEM_HANDLE_INFORMATION Information[1]; +} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; + +typedef struct _OBJECT_NAME_INFORMATION { + UNICODE_STRING ObjectName; +} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; + +typedef NTSTATUS(WINAPI* NtQueryObjectFunction)( + IN HANDLE Handle, + IN OBJECT_INFORMATION_CLASS ObjectInformationClass, + OUT PVOID ObjectInformation OPTIONAL, + IN ULONG ObjectInformationLength, + OUT PULONG ReturnLength OPTIONAL); + +typedef NTSTATUS(WINAPI* NtDuplicateObjectFunction)(IN HANDLE SourceProcess, + IN HANDLE SourceHandle, + IN HANDLE TargetProcess, + OUT PHANDLE TargetHandle, + IN ACCESS_MASK + DesiredAccess, + IN ULONG Attributes, + IN ULONG Options); + +typedef NTSTATUS(WINAPI* NtSignalAndWaitForSingleObjectFunction)( + IN HANDLE HandleToSignal, + IN HANDLE HandleToWait, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER Timeout OPTIONAL); + +typedef NTSTATUS(WINAPI* NtWaitForSingleObjectFunction)( + IN HANDLE ObjectHandle, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER TimeOut OPTIONAL); + +typedef NTSTATUS(WINAPI* NtQuerySystemInformation)( + IN SYSTEM_INFORMATION_CLASS SystemInformationClass, + OUT PVOID SystemInformation, + IN ULONG SystemInformationLength, + OUT PULONG ReturnLength); + +typedef NTSTATUS(WINAPI* NtQueryObject)(IN HANDLE Handle, + IN OBJECT_INFORMATION_CLASS + ObjectInformationClass, + OUT PVOID ObjectInformation, + IN ULONG ObjectInformationLength, + OUT PULONG ReturnLength); + +// ----------------------------------------------------------------------- +// Strings + +typedef int(__cdecl* _strnicmpFunction)(IN const char* _Str1, + IN const char* _Str2, + IN size_t _MaxCount); + +typedef size_t(__cdecl* strlenFunction)(IN const char* _Str); + +typedef size_t(__cdecl* wcslenFunction)(IN const wchar_t* _Str); + +typedef void*(__cdecl* memcpyFunction)(IN void* dest, + IN const void* src, + IN size_t count); + +typedef NTSTATUS(WINAPI* RtlAnsiStringToUnicodeStringFunction)( + IN OUT PUNICODE_STRING DestinationString, + IN PANSI_STRING SourceString, + IN BOOLEAN AllocateDestinationString); + +typedef LONG(WINAPI* RtlCompareUnicodeStringFunction)( + IN PCUNICODE_STRING String1, + IN PCUNICODE_STRING String2, + IN BOOLEAN CaseInSensitive); + +typedef VOID(WINAPI* RtlInitUnicodeStringFunction)(IN OUT PUNICODE_STRING + DestinationString, + IN PCWSTR SourceString); + +typedef ULONG(WINAPI* RtlNtStatusToDosErrorFunction)(NTSTATUS status); + +typedef enum _EVENT_TYPE { + NotificationEvent, + SynchronizationEvent +} EVENT_TYPE, + *PEVENT_TYPE; + +typedef NTSTATUS(WINAPI* NtCreateDirectoryObjectFunction)( + PHANDLE DirectoryHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS(WINAPI* NtOpenDirectoryObjectFunction)( + PHANDLE DirectoryHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +typedef NTSTATUS(WINAPI* NtQuerySymbolicLinkObjectFunction)( + HANDLE LinkHandle, + PUNICODE_STRING LinkTarget, + PULONG ReturnedLength); + +typedef NTSTATUS(WINAPI* NtOpenSymbolicLinkObjectFunction)( + PHANDLE LinkHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +#define DIRECTORY_QUERY 0x0001 +#define DIRECTORY_TRAVERSE 0x0002 +#define DIRECTORY_CREATE_OBJECT 0x0004 +#define DIRECTORY_CREATE_SUBDIRECTORY 0x0008 +#define DIRECTORY_ALL_ACCESS 0x000F + +typedef NTSTATUS(WINAPI* NtCreateLowBoxToken)( + OUT PHANDLE token, + IN HANDLE original_handle, + IN ACCESS_MASK access, + IN POBJECT_ATTRIBUTES object_attribute, + IN PSID appcontainer_sid, + IN DWORD capabilityCount, + IN PSID_AND_ATTRIBUTES capabilities, + IN DWORD handle_count, + IN PHANDLE handles); + +typedef NTSTATUS(WINAPI* NtSetInformationProcess)(IN HANDLE process_handle, + IN ULONG info_class, + IN PVOID process_information, + IN ULONG information_length); + +struct PROCESS_ACCESS_TOKEN { + HANDLE token; + HANDLE thread; +}; + +const unsigned int NtProcessInformationAccessToken = 9; + +typedef NTSTATUS(WINAPI* RtlDeriveCapabilitySidsFromNameFunction)( + PCUNICODE_STRING SourceString, + PSID CapabilityGroupSid, + PSID CapabilitySid); + +// ----------------------------------------------------------------------- +// GDI OPM API and Supported Calls + +#define DXGKMDT_OPM_OMAC_SIZE 16 +#define DXGKMDT_OPM_128_BIT_RANDOM_NUMBER_SIZE 16 +#define DXGKMDT_OPM_ENCRYPTED_PARAMETERS_SIZE 256 +#define DXGKMDT_OPM_CONFIGURE_SETTING_DATA_SIZE 4056 +#define DXGKMDT_OPM_GET_INFORMATION_PARAMETERS_SIZE 4056 +#define DXGKMDT_OPM_REQUESTED_INFORMATION_SIZE 4076 +#define DXGKMDT_OPM_HDCP_KEY_SELECTION_VECTOR_SIZE 5 +#define DXGKMDT_OPM_PROTECTION_TYPE_SIZE 4 + +enum DXGKMDT_CERTIFICATE_TYPE { + DXGKMDT_OPM_CERTIFICATE = 0, + DXGKMDT_COPP_CERTIFICATE = 1, + DXGKMDT_UAB_CERTIFICATE = 2, + DXGKMDT_FORCE_ULONG = 0xFFFFFFFF +}; + +enum DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS { + DXGKMDT_OPM_VOS_COPP_SEMANTICS = 0, + DXGKMDT_OPM_VOS_OPM_SEMANTICS = 1 +}; + +enum DXGKMDT_DPCP_PROTECTION_LEVEL { + DXGKMDT_OPM_DPCP_OFF = 0, + DXGKMDT_OPM_DPCP_ON = 1, + DXGKMDT_OPM_DPCP_FORCE_ULONG = 0x7fffffff +}; + +enum DXGKMDT_OPM_HDCP_PROTECTION_LEVEL { + DXGKMDT_OPM_HDCP_OFF = 0, + DXGKMDT_OPM_HDCP_ON = 1, + DXGKMDT_OPM_HDCP_FORCE_ULONG = 0x7fffffff +}; + +enum DXGKMDT_OPM_HDCP_FLAG { + DXGKMDT_OPM_HDCP_FLAG_NONE = 0x00, + DXGKMDT_OPM_HDCP_FLAG_REPEATER = 0x01 +}; + +enum DXGKMDT_OPM_PROTECTION_TYPE { + DXGKMDT_OPM_PROTECTION_TYPE_OTHER = 0x80000000, + DXGKMDT_OPM_PROTECTION_TYPE_NONE = 0x00000000, + DXGKMDT_OPM_PROTECTION_TYPE_COPP_COMPATIBLE_HDCP = 0x00000001, + DXGKMDT_OPM_PROTECTION_TYPE_ACP = 0x00000002, + DXGKMDT_OPM_PROTECTION_TYPE_CGMSA = 0x00000004, + DXGKMDT_OPM_PROTECTION_TYPE_HDCP = 0x00000008, + DXGKMDT_OPM_PROTECTION_TYPE_DPCP = 0x00000010, + DXGKMDT_OPM_PROTECTION_TYPE_MASK = 0x8000001F +}; + +typedef void* OPM_PROTECTED_OUTPUT_HANDLE; + +struct DXGKMDT_OPM_ENCRYPTED_PARAMETERS { + BYTE abEncryptedParameters[DXGKMDT_OPM_ENCRYPTED_PARAMETERS_SIZE]; +}; + +struct DXGKMDT_OPM_OMAC { + BYTE abOMAC[DXGKMDT_OPM_OMAC_SIZE]; +}; + +struct DXGKMDT_OPM_CONFIGURE_PARAMETERS { + DXGKMDT_OPM_OMAC omac; + GUID guidSetting; + ULONG ulSequenceNumber; + ULONG cbParametersSize; + BYTE abParameters[DXGKMDT_OPM_CONFIGURE_SETTING_DATA_SIZE]; +}; + +struct DXGKMDT_OPM_RANDOM_NUMBER { + BYTE abRandomNumber[DXGKMDT_OPM_128_BIT_RANDOM_NUMBER_SIZE]; +}; + +struct DXGKMDT_OPM_GET_INFO_PARAMETERS { + DXGKMDT_OPM_OMAC omac; + DXGKMDT_OPM_RANDOM_NUMBER rnRandomNumber; + GUID guidInformation; + ULONG ulSequenceNumber; + ULONG cbParametersSize; + BYTE abParameters[DXGKMDT_OPM_GET_INFORMATION_PARAMETERS_SIZE]; +}; + +struct DXGKMDT_OPM_REQUESTED_INFORMATION { + DXGKMDT_OPM_OMAC omac; + ULONG cbRequestedInformationSize; + BYTE abRequestedInformation[DXGKMDT_OPM_REQUESTED_INFORMATION_SIZE]; +}; + +struct DXGKMDT_OPM_SET_PROTECTION_LEVEL_PARAMETERS { + ULONG ulProtectionType; + ULONG ulProtectionLevel; + ULONG Reserved; + ULONG Reserved2; +}; + +struct DXGKMDT_OPM_STANDARD_INFORMATION { + DXGKMDT_OPM_RANDOM_NUMBER rnRandomNumber; + ULONG ulStatusFlags; + ULONG ulInformation; + ULONG ulReserved; + ULONG ulReserved2; +}; + +typedef NTSTATUS(WINAPI* GetSuggestedOPMProtectedOutputArraySizeFunction)( + PUNICODE_STRING device_name, + DWORD* suggested_output_array_size); + +typedef NTSTATUS(WINAPI* CreateOPMProtectedOutputsFunction)( + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD output_array_size, + DWORD* num_in_output_array, + OPM_PROTECTED_OUTPUT_HANDLE* output_array); + +typedef NTSTATUS(WINAPI* GetCertificateFunction)( + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +typedef NTSTATUS(WINAPI* GetCertificateSizeFunction)( + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +typedef NTSTATUS(WINAPI* GetCertificateByHandleFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +typedef NTSTATUS(WINAPI* GetCertificateSizeByHandleFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +typedef NTSTATUS(WINAPI* DestroyOPMProtectedOutputFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output); + +typedef NTSTATUS(WINAPI* ConfigureOPMProtectedOutputFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters); + +typedef NTSTATUS(WINAPI* GetOPMInformationFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_information); + +typedef NTSTATUS(WINAPI* GetOPMRandomNumberFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number); + +typedef NTSTATUS(WINAPI* SetOPMSigningKeyAndSequenceNumbersFunction)( + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters); + +#endif // SANDBOX_WIN_SRC_NT_INTERNALS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_broker.cc b/security/sandbox/chromium/sandbox/win/src/policy_broker.cc new file mode 100644 index 0000000000..406057acbb --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_broker.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_broker.h" + +#include <stddef.h> + +#include <map> + +#include "base/logging.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/target_process.h" + +// This code executes on the broker side, as a callback from the policy on the +// target side (the child). + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +#define INIT_GLOBAL_NT(member) \ + g_nt.member = reinterpret_cast<Nt##member##Function>( \ + ntdll_image.GetProcAddress("Nt" #member)); \ + if (!g_nt.member) \ + return false + +#define INIT_GLOBAL_RTL(member) \ + g_nt.member = \ + reinterpret_cast<member##Function>(ntdll_image.GetProcAddress(#member)); \ + if (!g_nt.member) \ + return false + +bool InitGlobalNt() { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + base::win::PEImage ntdll_image(ntdll); + + INIT_GLOBAL_NT(AllocateVirtualMemory); + INIT_GLOBAL_NT(Close); + INIT_GLOBAL_NT(DuplicateObject); + INIT_GLOBAL_NT(FreeVirtualMemory); + INIT_GLOBAL_NT(MapViewOfSection); + INIT_GLOBAL_NT(ProtectVirtualMemory); + INIT_GLOBAL_NT(QueryInformationProcess); + INIT_GLOBAL_NT(QueryObject); + INIT_GLOBAL_NT(QuerySection); + INIT_GLOBAL_NT(QueryVirtualMemory); + INIT_GLOBAL_NT(UnmapViewOfSection); + INIT_GLOBAL_NT(SignalAndWaitForSingleObject); + INIT_GLOBAL_NT(WaitForSingleObject); + + INIT_GLOBAL_RTL(RtlAllocateHeap); + INIT_GLOBAL_RTL(RtlAnsiStringToUnicodeString); + INIT_GLOBAL_RTL(RtlCompareUnicodeString); + INIT_GLOBAL_RTL(RtlCreateHeap); + INIT_GLOBAL_RTL(RtlCreateUserThread); + INIT_GLOBAL_RTL(RtlDestroyHeap); + INIT_GLOBAL_RTL(RtlFreeHeap); + INIT_GLOBAL_RTL(_strnicmp); + INIT_GLOBAL_RTL(strlen); + INIT_GLOBAL_RTL(wcslen); + INIT_GLOBAL_RTL(memcpy); + + return true; +} + +bool SetupNtdllImports(TargetProcess* child) { + if (!InitGlobalNt()) { + return false; + } + +#ifndef NDEBUG + // Verify that the structure is fully initialized. + for (size_t i = 0; i < sizeof(g_nt) / sizeof(void*); i++) + DCHECK(reinterpret_cast<char**>(&g_nt)[i]); +#endif + return (SBOX_ALL_OK == child->TransferVariable("g_nt", &g_nt, sizeof(g_nt))); +} + +#undef INIT_GLOBAL_NT +#undef INIT_GLOBAL_RTL + +bool SetupBasicInterceptions(InterceptionManager* manager, + bool is_csrss_connected) { + // Interceptions provided by process_thread_policy, without actual policy. + if (!INTERCEPT_NT(manager, NtOpenThread, OPEN_THREAD_ID, 20) || + !INTERCEPT_NT(manager, NtOpenProcess, OPEN_PROCESS_ID, 20) || + !INTERCEPT_NT(manager, NtOpenProcessToken, OPEN_PROCESS_TOKEN_ID, 16)) + return false; + + // Interceptions with neither policy nor IPC. + if (!INTERCEPT_NT(manager, NtSetInformationThread, SET_INFORMATION_THREAD_ID, + 20) || + !INTERCEPT_NT(manager, NtOpenThreadToken, OPEN_THREAD_TOKEN_ID, 20)) + return false; + + // This one is also provided by process_thread_policy. + if (!INTERCEPT_NT(manager, NtOpenProcessTokenEx, OPEN_PROCESS_TOKEN_EX_ID, + 20)) + return false; + + if (!INTERCEPT_NT(manager, NtOpenThreadTokenEx, OPEN_THREAD_TOKEN_EX_ID, 24)) + return false; + + if (!is_csrss_connected) { + if (!INTERCEPT_EAT(manager, kKerneldllName, CreateThread, CREATE_THREAD_ID, + 28)) + return false; + } + + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_broker.h b/security/sandbox/chromium/sandbox/win/src/policy_broker.h new file mode 100644 index 0000000000..95b123bd0d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_broker.h @@ -0,0 +1,27 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_BROKER_H_ +#define SANDBOX_SRC_POLICY_BROKER_H_ + +#include "sandbox/win/src/interception.h" + +namespace sandbox { + +class TargetProcess; + +// Initializes global imported symbols from ntdll. +bool InitGlobalNt(); + +// Sets up interceptions not controlled by explicit policies. +bool SetupBasicInterceptions(InterceptionManager* manager, + bool is_csrss_connected); + +// Sets up imports from NTDLL for the given target process so the interceptions +// can work. +bool SetupNtdllImports(TargetProcess* child); + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_BROKER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.cc b/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.cc new file mode 100644 index 0000000000..02c636f4aa --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.cc @@ -0,0 +1,450 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_engine_opcodes.h" + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace { +const unsigned short kMaxUniStrSize = 0xfffc / sizeof(wchar_t); + +bool InitStringUnicode(const wchar_t* source, + size_t length, + UNICODE_STRING* ustring) { + if (length > kMaxUniStrSize) { + return false; + } + ustring->Buffer = const_cast<wchar_t*>(source); + ustring->Length = static_cast<USHORT>(length) * sizeof(wchar_t); + ustring->MaximumLength = source ? ustring->Length + sizeof(wchar_t) : 0; + return true; +} + +} // namespace + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +// Note: The opcodes are implemented as functions (as opposed to classes derived +// from PolicyOpcode) because you should not add more member variables to the +// PolicyOpcode class since it would cause object slicing on the target. So to +// enforce that (instead of just trusting the developer) the opcodes became +// just functions. +// +// In the code that follows I have keep the evaluation function and the factory +// function together to stress the close relationship between both. For example, +// only the factory method and the evaluation function know the stored argument +// order and meaning. + +template <int> +EvalResult OpcodeEval(PolicyOpcode* opcode, + const ParameterSet* pp, + MatchContext* match); + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAlwaysFalse: +// Does not require input parameter. + +PolicyOpcode* OpcodeFactory::MakeOpAlwaysFalse(uint32_t options) { + return MakeBase(OP_ALWAYS_FALSE, options, -1); +} + +template <> +EvalResult OpcodeEval<OP_ALWAYS_FALSE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + return EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAlwaysTrue: +// Does not require input parameter. + +PolicyOpcode* OpcodeFactory::MakeOpAlwaysTrue(uint32_t options) { + return MakeBase(OP_ALWAYS_TRUE, options, -1); +} + +template <> +EvalResult OpcodeEval<OP_ALWAYS_TRUE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + return EVAL_TRUE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpAction: +// Does not require input parameter. +// Argument 0 contains the actual action to return. + +PolicyOpcode* OpcodeFactory::MakeOpAction(EvalResult action, uint32_t options) { + PolicyOpcode* opcode = MakeBase(OP_ACTION, options, -1); + if (!opcode) + return nullptr; + opcode->SetArgument(0, action); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_ACTION>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + int action = 0; + opcode->GetArgument(0, &action); + return static_cast<EvalResult>(action); +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberMatch: +// Requires a uint32_t or void* in selected_param +// Argument 0 is the stored number to match. +// Argument 1 is the C++ type of the 0th argument. + +PolicyOpcode* OpcodeFactory::MakeOpNumberMatch(int16_t selected_param, + uint32_t match, + uint32_t options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_MATCH, options, selected_param); + if (!opcode) + return nullptr; + opcode->SetArgument(0, match); + opcode->SetArgument(1, UINT32_TYPE); + return opcode; +} + +PolicyOpcode* OpcodeFactory::MakeOpVoidPtrMatch(int16_t selected_param, + const void* match, + uint32_t options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_MATCH, options, selected_param); + if (!opcode) + return nullptr; + opcode->SetArgument(0, match); + opcode->SetArgument(1, VOIDPTR_TYPE); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + uint32_t value_uint32 = 0; + if (param->Get(&value_uint32)) { + uint32_t match_uint32 = 0; + opcode->GetArgument(0, &match_uint32); + return (match_uint32 != value_uint32) ? EVAL_FALSE : EVAL_TRUE; + } else { + const void* value_ptr = nullptr; + if (param->Get(&value_ptr)) { + const void* match_ptr = nullptr; + opcode->GetArgument(0, &match_ptr); + return (match_ptr != value_ptr) ? EVAL_FALSE : EVAL_TRUE; + } + } + return EVAL_ERROR; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberMatchRange +// Requires a uint32_t in selected_param. +// Argument 0 is the stored lower bound to match. +// Argument 1 is the stored upper bound to match. + +PolicyOpcode* OpcodeFactory::MakeOpNumberMatchRange(int16_t selected_param, + uint32_t lower_bound, + uint32_t upper_bound, + uint32_t options) { + if (lower_bound > upper_bound) { + return nullptr; + } + PolicyOpcode* opcode = + MakeBase(OP_NUMBER_MATCH_RANGE, options, selected_param); + if (!opcode) + return nullptr; + opcode->SetArgument(0, lower_bound); + opcode->SetArgument(1, upper_bound); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_MATCH_RANGE>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + uint32_t value = 0; + if (!param->Get(&value)) + return EVAL_ERROR; + + uint32_t lower_bound = 0; + uint32_t upper_bound = 0; + opcode->GetArgument(0, &lower_bound); + opcode->GetArgument(1, &upper_bound); + return ((lower_bound <= value) && (upper_bound >= value)) ? EVAL_TRUE + : EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpNumberAndMatch: +// Requires a uint32_t in selected_param. +// Argument 0 is the stored number to match. + +PolicyOpcode* OpcodeFactory::MakeOpNumberAndMatch(int16_t selected_param, + uint32_t match, + uint32_t options) { + PolicyOpcode* opcode = MakeBase(OP_NUMBER_AND_MATCH, options, selected_param); + if (!opcode) + return nullptr; + opcode->SetArgument(0, match); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_NUMBER_AND_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + uint32_t value = 0; + if (!param->Get(&value)) + return EVAL_ERROR; + + uint32_t number = 0; + opcode->GetArgument(0, &number); + return (number & value) ? EVAL_TRUE : EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode OpWStringMatch: +// Requires a wchar_t* in selected_param. +// Argument 0 is the byte displacement of the stored string. +// Argument 1 is the length in chars of the stored string. +// Argument 2 is the offset to apply on the input string. It has special values. +// as noted in the header file. +// Argument 3 is the string matching options. + +PolicyOpcode* OpcodeFactory::MakeOpWStringMatch(int16_t selected_param, + const wchar_t* match_str, + int start_position, + StringMatchOptions match_opts, + uint32_t options) { + if (!match_str) + return nullptr; + if ('\0' == match_str[0]) + return nullptr; + + int length = lstrlenW(match_str); + + PolicyOpcode* opcode = MakeBase(OP_WSTRING_MATCH, options, selected_param); + if (!opcode) + return nullptr; + ptrdiff_t delta_str = AllocRelative(opcode, match_str, wcslen(match_str) + 1); + if (0 == delta_str) + return nullptr; + opcode->SetArgument(0, delta_str); + opcode->SetArgument(1, length); + opcode->SetArgument(2, start_position); + opcode->SetArgument(3, match_opts); + return opcode; +} + +template <> +EvalResult OpcodeEval<OP_WSTRING_MATCH>(PolicyOpcode* opcode, + const ParameterSet* param, + MatchContext* context) { + if (!context) { + return EVAL_ERROR; + } + const wchar_t* source_str = nullptr; + if (!param->Get(&source_str)) + return EVAL_ERROR; + + int start_position = 0; + int match_len = 0; + unsigned int match_opts = 0; + opcode->GetArgument(1, &match_len); + opcode->GetArgument(2, &start_position); + opcode->GetArgument(3, &match_opts); + + const wchar_t* match_str = opcode->GetRelativeString(0); + // Advance the source string to the last successfully evaluated position + // according to the match context. + source_str = &source_str[context->position]; + int source_len = static_cast<int>(g_nt.wcslen(source_str)); + + if (0 == source_len) { + // If we reached the end of the source string there is nothing we can + // match against. + return EVAL_FALSE; + } + if (match_len > source_len) { + // There can't be a positive match when the target string is bigger than + // the source string + return EVAL_FALSE; + } + + BOOLEAN case_sensitive = (match_opts & CASE_INSENSITIVE) ? TRUE : FALSE; + + // We have three cases, depending on the value of start_pos: + // Case 1. We skip N characters and compare once. + // Case 2: We skip to the end and compare once. + // Case 3: We match the first substring (if we find any). + if (start_position >= 0) { + if (kSeekToEnd == start_position) { + start_position = source_len - match_len; + } else if (match_opts & EXACT_LENGTH) { + // A sub-case of case 3 is when the EXACT_LENGTH flag is on + // the match needs to be not just substring but full match. + if ((match_len + start_position) != source_len) { + return EVAL_FALSE; + } + } + + // Advance start_pos characters. Warning! this does not consider + // utf16 encodings (surrogate pairs) or other Unicode 'features'. + source_str += start_position; + + // Since we skipped, lets reevaluate just the lengths again. + if ((match_len + start_position) > source_len) { + return EVAL_FALSE; + } + + UNICODE_STRING match_ustr; + UNICODE_STRING source_ustr; + if (!InitStringUnicode(match_str, match_len, &match_ustr) || + !InitStringUnicode(source_str, match_len, &source_ustr)) + return EVAL_ERROR; + + if (0 == g_nt.RtlCompareUnicodeString(&match_ustr, &source_ustr, + case_sensitive)) { + // Match! update the match context. + context->position += start_position + match_len; + return EVAL_TRUE; + } else { + return EVAL_FALSE; + } + } else if (start_position < 0) { + UNICODE_STRING match_ustr; + UNICODE_STRING source_ustr; + if (!InitStringUnicode(match_str, match_len, &match_ustr) || + !InitStringUnicode(source_str, match_len, &source_ustr)) + return EVAL_ERROR; + + do { + if (0 == g_nt.RtlCompareUnicodeString(&match_ustr, &source_ustr, + case_sensitive)) { + // Match! update the match context. + context->position += (source_ustr.Buffer - source_str) + match_len; + return EVAL_TRUE; + } + ++source_ustr.Buffer; + --source_len; + } while (source_len >= match_len); + } + return EVAL_FALSE; +} + +////////////////////////////////////////////////////////////////////////////// +// OpcodeMaker (other member functions). + +PolicyOpcode* OpcodeFactory::MakeBase(OpcodeID opcode_id, + uint32_t options, + int16_t selected_param) { + if (memory_size() < sizeof(PolicyOpcode)) + return nullptr; + + // Create opcode using placement-new on the buffer memory. + PolicyOpcode* opcode = new (memory_top_) PolicyOpcode(); + + // Fill in the standard fields, that every opcode has. + memory_top_ += sizeof(PolicyOpcode); + opcode->opcode_id_ = opcode_id; + opcode->SetOptions(options); + opcode->parameter_ = selected_param; + return opcode; +} + +ptrdiff_t OpcodeFactory::AllocRelative(void* start, + const wchar_t* str, + size_t length) { + size_t bytes = length * sizeof(wchar_t); + if (memory_size() < bytes) + return 0; + memory_bottom_ -= bytes; + if (reinterpret_cast<UINT_PTR>(memory_bottom_) & 1) { + // TODO(cpu) replace this for something better. + ::DebugBreak(); + } + memcpy(memory_bottom_, str, bytes); + ptrdiff_t delta = memory_bottom_ - reinterpret_cast<char*>(start); + return delta; +} + +////////////////////////////////////////////////////////////////////////////// +// Opcode evaluation dispatchers. + +// This function is the one and only entry for evaluating any opcode. It is +// in charge of applying any relevant opcode options and calling EvaluateInner +// were the actual dispatch-by-id is made. It would seem at first glance that +// the dispatch should be done by virtual function (vtable) calls but you have +// to remember that the opcodes are made in the broker process and copied as +// raw memory to the target process. + +EvalResult PolicyOpcode::Evaluate(const ParameterSet* call_params, + size_t param_count, + MatchContext* match) { + if (!call_params) + return EVAL_ERROR; + const ParameterSet* selected_param = nullptr; + if (parameter_ >= 0) { + if (static_cast<size_t>(parameter_) >= param_count) { + return EVAL_ERROR; + } + selected_param = &call_params[parameter_]; + } + EvalResult result = EvaluateHelper(selected_param, match); + + // Apply the general options regardless of the particular type of opcode. + if (kPolNone == options_) { + return result; + } + + if (options_ & kPolNegateEval) { + if (EVAL_TRUE == result) { + result = EVAL_FALSE; + } else if (EVAL_FALSE == result) { + result = EVAL_TRUE; + } else if (EVAL_ERROR != result) { + result = EVAL_ERROR; + } + } + if (match) { + if (options_ & kPolClearContext) + match->Clear(); + if (options_ & kPolUseOREval) + match->options = kPolUseOREval; + } + return result; +} + +#define OPCODE_EVAL(op, x, y, z) \ + case op: \ + return OpcodeEval<op>(x, y, z) + +EvalResult PolicyOpcode::EvaluateHelper(const ParameterSet* parameters, + MatchContext* match) { + switch (opcode_id_) { + OPCODE_EVAL(OP_ALWAYS_FALSE, this, parameters, match); + OPCODE_EVAL(OP_ALWAYS_TRUE, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_MATCH, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_MATCH_RANGE, this, parameters, match); + OPCODE_EVAL(OP_NUMBER_AND_MATCH, this, parameters, match); + OPCODE_EVAL(OP_WSTRING_MATCH, this, parameters, match); + OPCODE_EVAL(OP_ACTION, this, parameters, match); + default: + return EVAL_ERROR; + } +} + +#undef OPCODE_EVAL + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h b/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h new file mode 100644 index 0000000000..8e8816df1c --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h @@ -0,0 +1,379 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ +#define SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/numerics/safe_conversions.h" +#include "sandbox/win/src/policy_engine_params.h" + +// The low-level policy is implemented using the concept of policy 'opcodes'. +// An opcode is a structure that contains enough information to perform one +// comparison against one single input parameter. For example, an opcode can +// encode just one of the following comparison: +// +// - Is input parameter 3 not equal to nullptr? +// - Does input parameter 2 start with L"c:\\"? +// - Is input parameter 5, bit 3 is equal 1? +// +// Each opcode is in fact equivalent to a function invocation where all +// the parameters are known by the opcode except one. So say you have a +// function of this form: +// bool fn(a, b, c, d) with 4 arguments +// +// Then an opcode is: +// op(fn, b, c, d) +// Which stores the function to call and its 3 last arguments +// +// Then and opcode evaluation is: +// op.eval(a) ------------------------> fn(a,b,c,d) +// internally calls +// +// The idea is that complex policy rules can be split into streams of +// opcodes which are evaluated in sequence. The evaluation is done in +// groups of opcodes that have N comparison opcodes plus 1 action opcode: +// +// [comparison 1][comparison 2]...[comparison N][action][comparison 1]... +// ----- evaluation order-----------> +// +// Each opcode group encodes one high-level policy rule. The rule applies +// only if all the conditions on the group evaluate to true. The action +// opcode contains the policy outcome for that particular rule. +// +// Note that this header contains the main building blocks of low-level policy +// but not the low level policy class. +namespace sandbox { + +// These are the possible policy outcomes. Note that some of them might +// not apply and can be removed. Also note that The following values only +// specify what to do, not how to do it and it is acceptable given specific +// cases to ignore the policy outcome. +enum EvalResult { + // Comparison opcode values: + EVAL_TRUE, // Opcode condition evaluated true. + EVAL_FALSE, // Opcode condition evaluated false. + EVAL_ERROR, // Opcode condition generated an error while evaluating. + // Action opcode values: + ASK_BROKER, // The target must generate an IPC to the broker. On the broker + // side, this means grant access to the resource. + DENY_ACCESS, // No access granted to the resource. + GIVE_READONLY, // Give readonly access to the resource. + GIVE_ALLACCESS, // Give full access to the resource. + GIVE_CACHED, // IPC is not required. Target can return a cached handle. + GIVE_FIRST, // TODO(cpu) + SIGNAL_ALARM, // Unusual activity. Generate an alarm. + FAKE_SUCCESS, // Do not call original function. Just return 'success'. + FAKE_ACCESS_DENIED, // Do not call original function. Just return 'denied' + // and do not do IPC. + TERMINATE_PROCESS, // Destroy target process. Do IPC as well. +}; + +// The following are the implemented opcodes. +enum OpcodeID { + OP_ALWAYS_FALSE, // Evaluates to false (EVAL_FALSE). + OP_ALWAYS_TRUE, // Evaluates to true (EVAL_TRUE). + OP_NUMBER_MATCH, // Match a 32-bit integer as n == a. + OP_NUMBER_MATCH_RANGE, // Match a 32-bit integer as a <= n <= b. + OP_NUMBER_AND_MATCH, // Match using bitwise AND; as in: n & a != 0. + OP_WSTRING_MATCH, // Match a string for equality. + OP_ACTION // Evaluates to an action opcode. +}; + +// Options that apply to every opcode. They are specified when creating +// each opcode using OpcodeFactory::MakeOpXXXXX() family of functions +// Do nothing special. +const uint32_t kPolNone = 0; + +// Convert EVAL_TRUE into EVAL_FALSE and vice-versa. This allows to express +// negated conditions such as if ( a && !b). +const uint32_t kPolNegateEval = 1; + +// Zero the MatchContext context structure. This happens after the opcode +// is evaluated. +const uint32_t kPolClearContext = 2; + +// Use OR when evaluating this set of opcodes. The policy evaluator by default +// uses AND when evaluating. Very helpful when +// used with kPolNegateEval. For example if you have a condition best expressed +// as if(! (a && b && c)), the use of this flags allows it to be expressed as +// if ((!a) || (!b) || (!c)). +const uint32_t kPolUseOREval = 4; + +// Keeps the evaluation state between opcode evaluations. This is used +// for string matching where the next opcode needs to continue matching +// from the last character position from the current opcode. The match +// context is preserved across opcode evaluation unless an opcode specifies +// as an option kPolClearContext. +struct MatchContext { + size_t position; + uint32_t options; + + MatchContext() { Clear(); } + + void Clear() { + position = 0; + options = 0; + } +}; + +// Models a policy opcode; that is a condition evaluation were all the +// arguments but one are stored in objects of this class. Use OpcodeFactory +// to create objects of this type. +// This class is just an implementation artifact and not exposed to the +// API clients or visible in the intercepted service. Internally, an +// opcode is just: +// - An integer that identifies the actual opcode. +// - An index to indicate which one is the input argument +// - An array of arguments. +// While an OO hierarchy of objects would have been a natural choice, the fact +// that 1) this code can execute before the CRT is loaded, presents serious +// problems in terms of guarantees about the actual state of the vtables and +// 2) because the opcode objects are generated in the broker process, we need to +// use plain objects. To preserve some minimal type safety templates are used +// when possible. +class PolicyOpcode { + friend class OpcodeFactory; + + public: + // Evaluates the opcode. For a typical comparison opcode the return value + // is EVAL_TRUE or EVAL_FALSE. If there was an error in the evaluation the + // the return is EVAL_ERROR. If the opcode is an action opcode then the + // return can take other values such as ASK_BROKER. + // parameters: An array of all input parameters. This argument is normally + // created by the macros POLPARAMS_BEGIN() POLPARAMS_END. + // count: The number of parameters passed as first argument. + // match: The match context that is persisted across the opcode evaluation + // sequence. + EvalResult Evaluate(const ParameterSet* parameters, + size_t count, + MatchContext* match); + + // Retrieves a stored argument by index. Valid index values are + // from 0 to < kArgumentCount. + template <typename T> + void GetArgument(size_t index, T* argument) const { + static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size"); + *argument = *reinterpret_cast<const T*>(&arguments_[index].mem); + } + + // Sets a stored argument by index. Valid index values are + // from 0 to < kArgumentCount. + template <typename T> + void SetArgument(size_t index, const T& argument) { + static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size"); + *reinterpret_cast<T*>(&arguments_[index].mem) = argument; + } + + // Retrieves the actual address of a string argument. When using + // GetArgument() to retrieve an index that contains a string, the returned + // value is just an offset to the actual string. + // index: the stored string index. Valid values are from 0 + // to < kArgumentCount. + const wchar_t* GetRelativeString(size_t index) const { + ptrdiff_t str_delta = 0; + GetArgument(index, &str_delta); + const char* delta = reinterpret_cast<const char*>(this) + str_delta; + return reinterpret_cast<const wchar_t*>(delta); + } + + // Returns true if this opcode is an action opcode without actually + // evaluating it. Used to do a quick scan forward to the next opcode group. + bool IsAction() const { return (OP_ACTION == opcode_id_); } + + // Returns the opcode type. + OpcodeID GetID() const { return opcode_id_; } + + // Returns the stored options such as kPolNegateEval and others. + uint32_t GetOptions() const { return options_; } + + // Sets the stored options such as kPolNegateEval. + void SetOptions(uint32_t options) { options_ = options; } + + // Returns the parameter of the function the opcode concerns. + uint16_t GetParameter() const { return parameter_; } + + private: + static const size_t kArgumentCount = 4; // The number of supported argument. + + struct OpcodeArgument { + UINT_PTR mem; + }; + + // Better define placement new in the class instead of relying on the + // global definition which seems to be fubared. + void* operator new(size_t, void* location) { return location; } + + // Helper function to evaluate the opcode. The parameters have the same + // meaning that in Evaluate(). + EvalResult EvaluateHelper(const ParameterSet* parameters, + MatchContext* match); + OpcodeID opcode_id_; + int16_t parameter_; + uint32_t options_; + OpcodeArgument arguments_[PolicyOpcode::kArgumentCount]; +}; + +enum StringMatchOptions { + CASE_SENSITIVE = 0, // Pay or Not attention to the case as defined by + CASE_INSENSITIVE = 1, // RtlCompareUnicodeString windows API. + EXACT_LENGTH = 2 // Don't do substring match. Do full string match. +}; + +// Opcodes that do string comparisons take a parameter that is the starting +// position to perform the comparison so we can do substring matching. There +// are two special values: +// +// Start from the current position and compare strings advancing forward until +// a match is found if any. Similar to CRT strstr(). +const int kSeekForward = -1; +// Perform a match with the end of the string. It only does a single comparison. +const int kSeekToEnd = 0xfffff; + +// A PolicyBuffer is a variable size structure that contains all the opcodes +// that are to be created or evaluated in sequence. +struct PolicyBuffer { + size_t opcode_count; + PolicyOpcode opcodes[1]; +}; + +// Helper class to create any opcode sequence. This class is normally invoked +// only by the high level policy module or when you need to handcraft a special +// policy. +// The factory works by creating the opcodes using a chunk of memory given +// in the constructor. The opcodes themselves are allocated from the beginning +// (top) of the memory, while any string that an opcode needs is allocated from +// the end (bottom) of the memory. +// +// In essence: +// +// low address ---> [opcode 1] +// [opcode 2] +// [opcode 3] +// | | <--- memory_top_ +// | free | +// | | +// | | <--- memory_bottom_ +// [string 1] +// high address --> [string 2] +// +// Note that this class does not keep track of the number of opcodes made and +// it is designed to be a building block for low-level policy. +// +// Note that any of the MakeOpXXXXX member functions below can return nullptr on +// failure. When that happens opcode sequence creation must be aborted. +class OpcodeFactory { + public: + // memory: base pointer to a chunk of memory where the opcodes are created. + // memory_size: the size in bytes of the memory chunk. + OpcodeFactory(char* memory, size_t memory_size) : memory_top_(memory) { + memory_bottom_ = &memory_top_[memory_size]; + } + + // policy: contains the raw memory where the opcodes are created. + // memory_size: contains the actual size of the policy argument. + OpcodeFactory(PolicyBuffer* policy, size_t memory_size) { + memory_top_ = reinterpret_cast<char*>(&policy->opcodes[0]); + memory_bottom_ = &memory_top_[memory_size]; + } + + // Returns the available memory to make opcodes. + size_t memory_size() const { + DCHECK_GE(memory_bottom_, memory_top_); + return memory_bottom_ - memory_top_; + } + + // Creates an OpAlwaysFalse opcode. + PolicyOpcode* MakeOpAlwaysFalse(uint32_t options); + + // Creates an OpAlwaysFalse opcode. + PolicyOpcode* MakeOpAlwaysTrue(uint32_t options); + + // Creates an OpAction opcode. + // action: The action to return when Evaluate() is called. + PolicyOpcode* MakeOpAction(EvalResult action, uint32_t options); + + // Creates an OpNumberMatch opcode. + // selected_param: index of the input argument. It must be a uint32_t or the + // evaluation result will generate a EVAL_ERROR. + // match: the number to compare against the selected_param. + PolicyOpcode* MakeOpNumberMatch(int16_t selected_param, + uint32_t match, + uint32_t options); + + // Creates an OpNumberMatch opcode (void pointers are cast to numbers). + // selected_param: index of the input argument. It must be an void* or the + // evaluation result will generate a EVAL_ERROR. + // match: the pointer numeric value to compare against selected_param. + PolicyOpcode* MakeOpVoidPtrMatch(int16_t selected_param, + const void* match, + uint32_t options); + + // Creates an OpNumberMatchRange opcode using the memory passed in the ctor. + // selected_param: index of the input argument. It must be a uint32_t or the + // evaluation result will generate a EVAL_ERROR. + // lower_bound, upper_bound: the range to compare against selected_param. + PolicyOpcode* MakeOpNumberMatchRange(int16_t selected_param, + uint32_t lower_bound, + uint32_t upper_bound, + uint32_t options); + + // Creates an OpWStringMatch opcode using the raw memory passed in the ctor. + // selected_param: index of the input argument. It must be a wide string + // pointer or the evaluation result will generate a EVAL_ERROR. + // match_str: string to compare against selected_param. + // start_position: when its value is from 0 to < 0x7fff it indicates an + // offset from the selected_param string where to perform the comparison. If + // the value is SeekForward then a substring search is performed. If the + // value is SeekToEnd the comparison is performed against the last part of + // the selected_param string. + // Note that the range in the position (0 to 0x7fff) is dictated by the + // current implementation. + // match_opts: Indicates additional matching flags. Currently CaseInsensitive + // is supported. + PolicyOpcode* MakeOpWStringMatch(int16_t selected_param, + const wchar_t* match_str, + int start_position, + StringMatchOptions match_opts, + uint32_t options); + + // Creates an OpNumberAndMatch opcode using the raw memory passed in the ctor. + // selected_param: index of the input argument. It must be uint32_t or the + // evaluation result will generate a EVAL_ERROR. + // match: the value to bitwise AND against selected_param. + PolicyOpcode* MakeOpNumberAndMatch(int16_t selected_param, + uint32_t match, + uint32_t options); + + private: + // Constructs the common part of every opcode. selected_param is the index + // of the input param to use when evaluating the opcode. Pass -1 in + // selected_param to indicate that no input parameter is required. + PolicyOpcode* MakeBase(OpcodeID opcode_id, + uint32_t options, + int16_t selected_param); + + // Allocates (and copies) a string (of size length) inside the buffer and + // returns the displacement with respect to start. + ptrdiff_t AllocRelative(void* start, const wchar_t* str, size_t length); + + // Points to the lowest currently available address of the memory + // used to make the opcodes. This pointer increments as opcodes are made. + char* memory_top_; + + // Points to the highest currently available address of the memory + // used to make the opcodes. This pointer decrements as opcode strings are + // allocated. + char* memory_bottom_; + + DISALLOW_COPY_AND_ASSIGN(OpcodeFactory); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_params.h b/security/sandbox/chromium/sandbox/win/src/policy_engine_params.h new file mode 100644 index 0000000000..07fd7eac82 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_params.h @@ -0,0 +1,190 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ +#define SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ + +#include <stdint.h> + +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +// This header defines the classes that allow the low level policy to select +// the input parameters. In order to better make sense of this header is +// recommended that you check policy_engine_opcodes.h first. + +namespace sandbox { + +// Models the set of interesting parameters of an intercepted system call +// normally you don't create objects of this class directly, instead you +// use the POLPARAMS_XXX macros. +// For example, if an intercepted function has the following signature: +// +// NTSTATUS NtOpenFileFunction (PHANDLE FileHandle, +// ACCESS_MASK DesiredAccess, +// POBJECT_ATTRIBUTES ObjectAttributes, +// PIO_STATUS_BLOCK IoStatusBlock, +// ULONG ShareAccess, +// ULONG OpenOptions); +// +// You could say that the following parameters are of interest to policy: +// +// POLPARAMS_BEGIN(open_params) +// POLPARAM(DESIRED_ACCESS) +// POLPARAM(OBJECT_NAME) +// POLPARAM(SECURITY_DESCRIPTOR) +// POLPARAM(IO_STATUS) +// POLPARAM(OPEN_OPTIONS) +// POLPARAMS_END; +// +// and the actual code will use this for defining the parameters: +// +// CountedParameterSet<open_params> p; +// p[open_params::DESIRED_ACCESS] = ParamPickerMake(DesiredAccess); +// p[open_params::OBJECT_NAME] = +// ParamPickerMake(ObjectAttributes->ObjectName); +// p[open_params::SECURITY_DESCRIPTOR] = +// ParamPickerMake(ObjectAttributes->SecurityDescriptor); +// p[open_params::IO_STATUS] = ParamPickerMake(IoStatusBlock); +// p[open_params::OPEN_OPTIONS] = ParamPickerMake(OpenOptions); +// +// These will create an stack-allocated array of ParameterSet objects which +// have each 1) the address of the parameter 2) a numeric id that encodes the +// original C++ type. This allows the policy to treat any set of supported +// argument types uniformily and with some type safety. +// +// TODO(cpu): support not fully implemented yet for unicode string and will +// probably add other types as well. +class ParameterSet { + public: + ParameterSet() : real_type_(INVALID_TYPE), address_(nullptr) {} + + // Retrieve the stored parameter. If the type does not match ulong fail. + bool Get(uint32_t* destination) const { + if (real_type_ != UINT32_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<uint32_t>(); + return true; + } + + // Retrieve the stored parameter. If the type does not match void* fail. + bool Get(const void** destination) const { + if (real_type_ != VOIDPTR_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<void*>(); + return true; + } + + // Retrieve the stored parameter. If the type does not match wchar_t* fail. + bool Get(const wchar_t** destination) const { + if (real_type_ != WCHAR_TYPE) { + return false; + } + *destination = Void2TypePointerCopy<const wchar_t*>(); + return true; + } + + // False if the parameter is not properly initialized. + bool IsValid() const { return real_type_ != INVALID_TYPE; } + + protected: + // The constructor can only be called by derived types, which should + // safely provide the real_type and the address of the argument. + ParameterSet(ArgType real_type, const void* address) + : real_type_(real_type), address_(address) {} + + private: + // This template provides the same functionality as bits_cast but + // it works with pointer while the former works only with references. + template <typename T> + T Void2TypePointerCopy() const { + return *(reinterpret_cast<const T*>(address_)); + } + + ArgType real_type_; + const void* address_; +}; + +// To safely infer the type, we use a set of template specializations +// in ParameterSetEx with a template function ParamPickerMake to do the +// parameter type deduction. + +// Base template class. Not implemented so using unsupported types should +// fail to compile. +template <typename T> +class ParameterSetEx : public ParameterSet { + public: + ParameterSetEx(const void* address); +}; + +template <> +class ParameterSetEx<void const*> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(VOIDPTR_TYPE, address) {} +}; + +template <> +class ParameterSetEx<void*> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(VOIDPTR_TYPE, address) {} +}; + +template <> +class ParameterSetEx<wchar_t*> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(WCHAR_TYPE, address) {} +}; + +template <> +class ParameterSetEx<wchar_t const*> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(WCHAR_TYPE, address) {} +}; + +template <> +class ParameterSetEx<uint32_t> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(UINT32_TYPE, address) {} +}; + +template <> +class ParameterSetEx<UNICODE_STRING> : public ParameterSet { + public: + ParameterSetEx(const void* address) : ParameterSet(UNISTR_TYPE, address) {} +}; + +template <typename T> +ParameterSet ParamPickerMake(T& parameter) { + return ParameterSetEx<T>(¶meter); +} + +struct CountedParameterSetBase { + size_t count; + ParameterSet parameters[1]; +}; + +// This template defines the actual list of policy parameters for a given +// interception. +// Warning: This template stores the address to the actual variables, in +// other words, the values are not copied. +template <typename T> +struct CountedParameterSet { + CountedParameterSet() : count(T::PolParamLast) {} + + ParameterSet& operator[](typename T::Args n) { return parameters[n]; } + + CountedParameterSetBase* GetBase() { + return reinterpret_cast<CountedParameterSetBase*>(this); + } + + size_t count; + ParameterSet parameters[T::PolParamLast]; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_ENGINE_PARAMS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.cc b/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.cc new file mode 100644 index 0000000000..07969f31a8 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_engine_processor.h" + +#include <stddef.h> +#include <stdint.h> + +namespace sandbox { + +void PolicyProcessor::SetInternalState(size_t index, EvalResult result) { + state_.current_index_ = index; + state_.current_result_ = result; +} + +EvalResult PolicyProcessor::GetAction() const { + return state_.current_result_; +} + +// Decides if an opcode can be skipped (not evaluated) or not. The function +// takes as inputs the opcode and the current evaluation context and returns +// true if the opcode should be skipped or not and also can set keep_skipping +// to false to signal that the current instruction should be skipped but not +// the next after the current one. +bool SkipOpcode(const PolicyOpcode& opcode, + MatchContext* context, + bool* keep_skipping) { + if (opcode.IsAction()) { + uint32_t options = context->options; + context->Clear(); + *keep_skipping = false; + return (kPolUseOREval != options); + } + *keep_skipping = true; + return true; +} + +PolicyResult PolicyProcessor::Evaluate(uint32_t options, + ParameterSet* parameters, + size_t param_count) { + if (!policy_) + return NO_POLICY_MATCH; + if (0 == policy_->opcode_count) + return NO_POLICY_MATCH; + if (!(kShortEval & options)) + return POLICY_ERROR; + + MatchContext context; + bool evaluation = false; + bool skip_group = false; + SetInternalState(0, EVAL_FALSE); + size_t count = policy_->opcode_count; + + // Loop over all the opcodes Evaluating in sequence. Since we only support + // short circuit evaluation, we stop as soon as we find an 'action' opcode + // and the current evaluation is true. + // + // Skipping opcodes can happen when we are in AND mode (!kPolUseOREval) and + // have got EVAL_FALSE or when we are in OR mode (kPolUseOREval) and got + // EVAL_TRUE. Skipping will stop at the next action opcode or at the opcode + // after the action depending on kPolUseOREval. + + for (size_t ix = 0; ix != count; ++ix) { + PolicyOpcode& opcode = policy_->opcodes[ix]; + // Skipping block. + if (skip_group) { + if (SkipOpcode(opcode, &context, &skip_group)) + continue; + } + // Evaluation block. + EvalResult result = opcode.Evaluate(parameters, param_count, &context); + switch (result) { + case EVAL_FALSE: + evaluation = false; + if (kPolUseOREval != context.options) + skip_group = true; + break; + case EVAL_ERROR: + if (kStopOnErrors & options) + return POLICY_ERROR; + break; + case EVAL_TRUE: + evaluation = true; + if (kPolUseOREval == context.options) + skip_group = true; + break; + default: + // We have evaluated an action. + SetInternalState(ix, result); + return POLICY_MATCH; + } + } + + if (evaluation) { + // Reaching the end of the policy with a positive evaluation is probably + // an error: we did not find a final action opcode? + return POLICY_ERROR; + } + return NO_POLICY_MATCH; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.h b/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.h new file mode 100644 index 0000000000..b62973b14a --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_processor.h @@ -0,0 +1,143 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ +#define SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_engine_params.h" + +namespace sandbox { + +// This header contains the core policy evaluator. In its simplest form +// it evaluates a stream of opcodes assuming that they are laid out in +// memory as opcode groups. +// +// An opcode group has N comparison opcodes plus 1 action opcode. For +// example here we have 3 opcode groups (A, B,C): +// +// [comparison 1] <-- group A start +// [comparison 2] +// [comparison 3] +// [action A ] +// [comparison 1] <-- group B start +// [action B ] +// [comparison 1] <-- group C start +// [comparison 2] +// [action C ] +// +// The opcode evaluator proceeds from the top, evaluating each opcode in +// sequence. An opcode group is evaluated until the first comparison that +// returns false. At that point the rest of the group is skipped and evaluation +// resumes with the first comparison of the next group. When all the comparisons +// in a group have evaluated to true and the action is reached. The group is +// considered a matching group. +// +// In the 'ShortEval' mode evaluation stops when it reaches the end or the first +// matching group. The action opcode from this group is the resulting policy +// action. +// +// In the 'RankedEval' mode evaluation stops only when it reaches the end of the +// the opcode stream. In the process all matching groups are saved and at the +// end the 'best' group is selected (what makes the best is TBD) and the action +// from this group is the resulting policy action. +// +// As explained above, the policy evaluation of a group is a logical AND of +// the evaluation of each opcode. However an opcode can request kPolUseOREval +// which makes the evaluation to use logical OR. Given that each opcode can +// request its evaluation result to be negated with kPolNegateEval you can +// achieve the negation of the total group evaluation. This means that if you +// need to express: +// if (!(c1 && c2 && c3)) +// You can do it by: +// if ((!c1) || (!c2) || (!c3)) +// + +// Possible outcomes of policy evaluation. +enum PolicyResult { NO_POLICY_MATCH, POLICY_MATCH, POLICY_ERROR }; + +// Policy evaluation flags +// TODO(cpu): implement the options kStopOnErrors & kRankedEval. +// +// Stop evaluating as soon as an error is encountered. +const uint32_t kStopOnErrors = 1; +// Ignore all non fatal opcode evaluation errors. +const uint32_t kIgnoreErrors = 2; +// Short-circuit evaluation: Only evaluate until opcode group that +// evaluated to true has been found. +const uint32_t kShortEval = 4; +// Discussed briefly at the policy design meeting. It will evaluate +// all rules and then return the 'best' rule that evaluated true. +const uint32_t kRankedEval = 8; + +// This class evaluates a policy-opcode stream given the memory where the +// opcodes are and an input 'parameter set'. +// +// This class is designed to be callable from interception points +// as low as the NtXXXX service level (it is not currently safe, but +// it is designed to be made safe). +// +// Its usage in an interception is: +// +// POLPARAMS_BEGIN(eval_params) +// POLPARAM(param1) +// POLPARAM(param2) +// POLPARAM(param3) +// POLPARAM(param4) +// POLPARAM(param5) +// POLPARAMS_END; +// +// PolicyProcessor pol_evaluator(policy_memory); +// PolicyResult pr = pol_evaluator.Evaluate(ShortEval, eval_params, +// _countof(eval_params)); +// if (NO_POLICY_MATCH == pr) { +// EvalResult policy_action = pol_evaluator.GetAction(); +// // apply policy here... +// } +// +// Where the POLPARAM() arguments are derived from the intercepted function +// arguments, and represent all the 'interesting' policy inputs, and +// policy_memory is a memory buffer containing the opcode stream that is the +// relevant policy for this intercept. +class PolicyProcessor { + public: + // policy_buffer contains opcodes made with OpcodeFactory. They are usually + // created in the broker process and evaluated in the target process. + + // This constructor is just a variant of the previous constructor. + explicit PolicyProcessor(PolicyBuffer* policy) : policy_(policy) { + SetInternalState(0, EVAL_FALSE); + } + + // Evaluates a policy-opcode stream. See the comments at the top of this + // class for more info. Returns POLICY_MATCH if a rule set was found that + // matches an active policy. + PolicyResult Evaluate(uint32_t options, + ParameterSet* parameters, + size_t parameter_count); + + // If the result of Evaluate() was POLICY_MATCH, calling this function returns + // the recommended policy action. + EvalResult GetAction() const; + + private: + struct { + size_t current_index_; + EvalResult current_result_; + } state_; + + // Sets the currently matching action result. + void SetInternalState(size_t index, EvalResult result); + + PolicyBuffer* policy_; + DISALLOW_COPY_AND_ASSIGN(PolicyProcessor); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_ENGINE_PROCESSOR_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_engine_unittest.cc b/security/sandbox/chromium/sandbox/win/src/policy_engine_unittest.cc new file mode 100644 index 0000000000..b8b2002108 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_engine_unittest.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define POLPARAMS_BEGIN(x) sandbox::ParameterSet x[] = { +#define POLPARAM(p) sandbox::ParamPickerMake(p), +#define POLPARAMS_END } + +namespace sandbox { + +bool SetupNtdllImports(); + +TEST(PolicyEngineTest, Rules1) { + SetupNtdllImports(); + + // Construct two policy rules that say: + // + // #1 + // If the path is c:\\documents and settings\\* AND + // If the creation mode is 'open existing' AND + // If the security descriptor is null THEN + // Ask the broker. + // + // #2 + // If the security descriptor is null AND + // If the path ends with *.txt AND + // If the creation mode is not 'create new' THEN + // return Access Denied. + + enum FileCreateArgs { + FileNameArg, + CreationDispositionArg, + FlagsAndAttributesArg, + SecurityAttributes + }; + + const size_t policy_sz = 1024; + PolicyBuffer* policy = reinterpret_cast<PolicyBuffer*>(new char[policy_sz]); + OpcodeFactory opcode_maker(policy, policy_sz - 0x40); + + // Add rule set #1 + opcode_maker.MakeOpWStringMatch(FileNameArg, L"c:\\documents and settings\\", + 0, CASE_INSENSITIVE, kPolNone); + opcode_maker.MakeOpNumberMatch(CreationDispositionArg, OPEN_EXISTING, + kPolNone); + opcode_maker.MakeOpVoidPtrMatch(SecurityAttributes, nullptr, kPolNone); + opcode_maker.MakeOpAction(ASK_BROKER, kPolNone); + + // Add rule set #2 + opcode_maker.MakeOpWStringMatch(FileNameArg, L".TXT", kSeekToEnd, + CASE_INSENSITIVE, kPolNone); + opcode_maker.MakeOpNumberMatch(CreationDispositionArg, CREATE_NEW, + kPolNegateEval); + opcode_maker.MakeOpAction(FAKE_ACCESS_DENIED, kPolNone); + policy->opcode_count = 7; + + const wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; + uint32_t creation_mode = OPEN_EXISTING; + uint32_t flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = nullptr; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) + POLPARAM(creation_mode) + POLPARAM(flags) + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult pr; + PolicyProcessor pol_ev(policy); + + // Test should match the first rule set. + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + // Test should still match the first rule set. + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + // Changing creation_mode such that evaluation should not match any rule. + creation_mode = CREATE_NEW; + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, pr); + + // Changing creation_mode such that evaluation should match rule #2. + creation_mode = OPEN_ALWAYS; + pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, pr); + EXPECT_EQ(FAKE_ACCESS_DENIED, pol_ev.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_low_level.cc b/security/sandbox/chromium/sandbox/win/src/policy_low_level.cc new file mode 100644 index 0000000000..d987211c8b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_low_level.cc @@ -0,0 +1,355 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_low_level.h" + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <string> + +namespace { + +// A single rule can use at most this amount of memory. +const size_t kRuleBufferSize = 1024 * 4; + +// The possible states of the string matching opcode generator. +enum { + PENDING_NONE, + PENDING_ASTERISK, // Have seen an '*' but have not generated an opcode. + PENDING_QMARK, // Have seen an '?' but have not generated an opcode. +}; + +// The category of the last character seen by the string matching opcode +// generator. +const uint32_t kLastCharIsNone = 0; +const uint32_t kLastCharIsAlpha = 1; +const uint32_t kLastCharIsWild = 2; +const uint32_t kLastCharIsAsterisk = kLastCharIsWild + 4; +const uint32_t kLastCharIsQuestionM = kLastCharIsWild + 8; + +} // namespace + +namespace sandbox { + +LowLevelPolicy::LowLevelPolicy(PolicyGlobal* policy_store) + : policy_store_(policy_store) {} + +// Adding a rule is nothing more than pushing it into an stl container. Done() +// is called for the rule in case the code that made the rule in the first +// place has not done it. +bool LowLevelPolicy::AddRule(IpcTag service, PolicyRule* rule) { + if (!rule->Done()) { + return false; + } + + PolicyRule* local_rule = new PolicyRule(*rule); + RuleNode node = {local_rule, service}; + rules_.push_back(node); + return true; +} + +LowLevelPolicy::~LowLevelPolicy() { + // Delete all the rules. + typedef std::list<RuleNode> RuleNodes; + for (RuleNodes::iterator it = rules_.begin(); it != rules_.end(); ++it) { + delete it->rule; + } +} + +// Here is where the heavy byte shuffling is done. We take all the rules and +// 'compile' them into a single memory region. Now, the rules are in random +// order so the first step is to reorganize them into a stl map that is keyed +// by the service id and as a value contains a list with all the rules that +// belong to that service. Then we enter the big for-loop where we carve a +// memory zone for the opcodes and the data and call RebindCopy on each rule +// so they all end up nicely packed in the policy_store_. +bool LowLevelPolicy::Done() { + typedef std::list<RuleNode> RuleNodes; + typedef std::list<const PolicyRule*> RuleList; + typedef std::map<IpcTag, RuleList> Mmap; + Mmap mmap; + + for (RuleNodes::iterator it = rules_.begin(); it != rules_.end(); ++it) { + mmap[it->service].push_back(it->rule); + } + + PolicyBuffer* current_buffer = &policy_store_->data[0]; + char* buffer_end = + reinterpret_cast<char*>(current_buffer) + policy_store_->data_size; + size_t avail_size = policy_store_->data_size; + + for (Mmap::iterator it = mmap.begin(); it != mmap.end(); ++it) { + IpcTag service = (*it).first; + if (static_cast<size_t>(service) >= kMaxServiceCount) { + return false; + } + policy_store_->entry[static_cast<size_t>(service)] = current_buffer; + + RuleList::iterator rules_it = (*it).second.begin(); + RuleList::iterator rules_it_end = (*it).second.end(); + + size_t svc_opcode_count = 0; + + for (; rules_it != rules_it_end; ++rules_it) { + const PolicyRule* rule = (*rules_it); + size_t op_count = rule->GetOpcodeCount(); + + size_t opcodes_size = op_count * sizeof(PolicyOpcode); + if (avail_size < opcodes_size) { + return false; + } + size_t data_size = avail_size - opcodes_size; + PolicyOpcode* opcodes_start = ¤t_buffer->opcodes[svc_opcode_count]; + if (!rule->RebindCopy(opcodes_start, opcodes_size, buffer_end, + &data_size)) { + return false; + } + size_t used = avail_size - data_size; + buffer_end -= used; + avail_size -= used; + svc_opcode_count += op_count; + } + + current_buffer->opcode_count = svc_opcode_count; + size_t policy_buffers_occupied = + (svc_opcode_count * sizeof(PolicyOpcode)) / sizeof(current_buffer[0]); + current_buffer = ¤t_buffer[policy_buffers_occupied + 1]; + } + + return true; +} + +PolicyRule::PolicyRule(EvalResult action) : action_(action), done_(false) { + char* memory = new char[sizeof(PolicyBuffer) + kRuleBufferSize]; + buffer_ = reinterpret_cast<PolicyBuffer*>(memory); + buffer_->opcode_count = 0; + opcode_factory_ = + new OpcodeFactory(buffer_, kRuleBufferSize + sizeof(PolicyOpcode)); +} + +PolicyRule::PolicyRule(const PolicyRule& other) { + if (this == &other) + return; + action_ = other.action_; + done_ = other.done_; + size_t buffer_size = sizeof(PolicyBuffer) + kRuleBufferSize; + char* memory = new char[buffer_size]; + buffer_ = reinterpret_cast<PolicyBuffer*>(memory); + memcpy(buffer_, other.buffer_, buffer_size); + + char* opcode_buffer = reinterpret_cast<char*>(&buffer_->opcodes[0]); + char* next_opcode = &opcode_buffer[GetOpcodeCount() * sizeof(PolicyOpcode)]; + opcode_factory_ = + new OpcodeFactory(next_opcode, other.opcode_factory_->memory_size()); +} + +// This function get called from a simple state machine implemented in +// AddStringMatch() which passes the current state (in state) and it passes +// true in last_call if AddStringMatch() has finished processing the input +// pattern string and this would be the last call to generate any pending +// opcode. The skip_count is the currently accumulated number of '?' seen so +// far and once the associated opcode is generated this function sets it back +// to zero. +bool PolicyRule::GenStringOpcode(RuleType rule_type, + StringMatchOptions match_opts, + uint16_t parameter, + int state, + bool last_call, + int* skip_count, + std::wstring* fragment) { + // The last opcode must: + // 1) Always clear the context. + // 2) Preserve the negation. + // 3) Remove the 'OR' mode flag. + uint32_t options = kPolNone; + if (last_call) { + if (IF_NOT == rule_type) { + options = kPolClearContext | kPolNegateEval; + } else { + options = kPolClearContext; + } + } else if (IF_NOT == rule_type) { + options = kPolUseOREval | kPolNegateEval; + } + + PolicyOpcode* op = nullptr; + + // The fragment string contains the accumulated characters to match with, it + // never contains wildcards (unless they have been escaped) and while there + // is no fragment there is no new string match opcode to generate. + if (fragment->empty()) { + // There is no new opcode to generate but in the last call we have to fix + // the previous opcode because it was really the last but we did not know + // it at that time. + if (last_call && (buffer_->opcode_count > 0)) { + op = &buffer_->opcodes[buffer_->opcode_count - 1]; + op->SetOptions(options); + } + return true; + } + + if (PENDING_ASTERISK == state) { + if (last_call) { + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), + kSeekToEnd, match_opts, options); + } else { + op = opcode_factory_->MakeOpWStringMatch( + parameter, fragment->c_str(), kSeekForward, match_opts, options); + } + + } else if (PENDING_QMARK == state) { + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), + *skip_count, match_opts, options); + *skip_count = 0; + } else { + if (last_call) { + match_opts = static_cast<StringMatchOptions>(EXACT_LENGTH | match_opts); + } + op = opcode_factory_->MakeOpWStringMatch(parameter, fragment->c_str(), 0, + match_opts, options); + } + if (!op) + return false; + ++buffer_->opcode_count; + fragment->clear(); + return true; +} + +bool PolicyRule::AddStringMatch(RuleType rule_type, + int16_t parameter, + const wchar_t* string, + StringMatchOptions match_opts) { + if (done_) { + // Do not allow to add more rules after generating the action opcode. + return false; + } + + const wchar_t* current_char = string; + uint32_t last_char = kLastCharIsNone; + int state = PENDING_NONE; + int skip_count = 0; // counts how many '?' we have seen in a row. + std::wstring fragment; // accumulates the non-wildcard part. + + while (L'\0' != *current_char) { + switch (*current_char) { + case L'*': + if (kLastCharIsWild & last_char) { + // '**' and '&*' is an error. + return false; + } + if (!GenStringOpcode(rule_type, match_opts, parameter, state, false, + &skip_count, &fragment)) { + return false; + } + last_char = kLastCharIsAsterisk; + state = PENDING_ASTERISK; + break; + case L'?': + if (kLastCharIsAsterisk == last_char) { + // '*?' is an error. + return false; + } + if (!GenStringOpcode(rule_type, match_opts, parameter, state, false, + &skip_count, &fragment)) { + return false; + } + ++skip_count; + last_char = kLastCharIsQuestionM; + state = PENDING_QMARK; + break; + case L'/': + // Note: "/?" is an escaped '?'. Eat the slash and fall through. + if (L'?' == current_char[1]) { + ++current_char; + } + FALLTHROUGH; + default: + fragment += *current_char; + last_char = kLastCharIsAlpha; + } + ++current_char; + } + + if (!GenStringOpcode(rule_type, match_opts, parameter, state, true, + &skip_count, &fragment)) { + return false; + } + return true; +} + +bool PolicyRule::AddNumberMatch(RuleType rule_type, + int16_t parameter, + uint32_t number, + RuleOp comparison_op) { + if (done_) { + // Do not allow to add more rules after generating the action opcode. + return false; + } + uint32_t opts = (rule_type == IF_NOT) ? kPolNegateEval : kPolNone; + + if (EQUAL == comparison_op) { + if (!opcode_factory_->MakeOpNumberMatch(parameter, number, opts)) + return false; + } else if (AND == comparison_op) { + if (!opcode_factory_->MakeOpNumberAndMatch(parameter, number, opts)) + return false; + } + ++buffer_->opcode_count; + return true; +} + +bool PolicyRule::Done() { + if (done_) { + return true; + } + if (!opcode_factory_->MakeOpAction(action_, kPolNone)) + return false; + ++buffer_->opcode_count; + done_ = true; + return true; +} + +bool PolicyRule::RebindCopy(PolicyOpcode* opcode_start, + size_t opcode_size, + char* data_start, + size_t* data_size) const { + size_t count = buffer_->opcode_count; + for (size_t ix = 0; ix != count; ++ix) { + if (opcode_size < sizeof(PolicyOpcode)) { + return false; + } + PolicyOpcode& opcode = buffer_->opcodes[ix]; + *opcode_start = opcode; + if (OP_WSTRING_MATCH == opcode.GetID()) { + // For this opcode argument 0 is a delta to the string and argument 1 + // is the length (in chars) of the string. + const wchar_t* str = opcode.GetRelativeString(0); + size_t str_len; + opcode.GetArgument(1, &str_len); + str_len = str_len * sizeof(wchar_t); + if ((*data_size) < str_len) { + return false; + } + *data_size -= str_len; + data_start -= str_len; + memcpy(data_start, str, str_len); + // Recompute the string displacement + ptrdiff_t delta = data_start - reinterpret_cast<char*>(opcode_start); + opcode_start->SetArgument(0, delta); + } + ++opcode_start; + opcode_size -= sizeof(PolicyOpcode); + } + + return true; +} + +PolicyRule::~PolicyRule() { + delete[] reinterpret_cast<char*>(buffer_); + delete opcode_factory_; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_low_level.h b/security/sandbox/chromium/sandbox/win/src/policy_low_level.h new file mode 100644 index 0000000000..1586f96af9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_low_level.h @@ -0,0 +1,189 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_LOW_LEVEL_H__ +#define SANDBOX_SRC_POLICY_LOW_LEVEL_H__ + +#include <stddef.h> +#include <stdint.h> + +#include <list> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_engine_params.h" + +// Low level policy classes. +// Built on top of the PolicyOpcode and OpcodeFactory, the low level policy +// provides a way to define rules on strings and numbers but it is unaware +// of Windows specific details or how the Interceptions must be set up. +// To use these classes you construct one or more rules and add them to the +// LowLevelPolicy object like this: +// +// PolicyRule rule1(ASK_BROKER); +// rule1.AddStringMatch(IF, 0, L"\\\\/?/?\\c:\\*Microsoft*\\*.exe", true); +// rule1.AddNumberMatch(IF_NOT, 1, CREATE_ALWAYS, EQUAL); +// rule1.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL); +// +// PolicyRule rule2(FAKE_SUCCESS); +// rule2.AddStringMatch(IF, 0, L"\\\\/?/?\\Pipe\\Chrome.*", false)); +// rule2.AddNumberMatch(IF, 1, OPEN_EXISTING, EQUAL)); +// +// LowLevelPolicy policyGen(*policy_memory); +// policyGen.AddRule(kNtCreateFileSvc, &rule1); +// policyGen.AddRule(kNtCreateFileSvc, &rule2); +// policyGen.Done(); +// +// At this point (error checking omitted) the policy_memory can be copied +// to the target process where it can be evaluated. + +namespace sandbox { + +// Defines the memory layout of the policy. This memory is filled by +// LowLevelPolicy object. +// For example: +// +// [Service 0] --points to---\ +// [Service 1] --------------|-----\ +// ...... | | +// [Service N] | | +// [data_size] | | +// [Policy Buffer 0] <-------/ | +// [opcodes of] | +// ....... | +// [Policy Buffer 1] <-------------/ +// [opcodes] +// ....... +// ....... +// [Policy Buffer N] +// [opcodes] +// ....... +// <possibly unused space here> +// ....... +// [opcode string ] +// [opcode string ] +// ....... +// [opcode string ] +struct PolicyGlobal { + PolicyBuffer* entry[kMaxServiceCount]; + size_t data_size; + PolicyBuffer data[1]; +}; + +class PolicyRule; + +// Provides the means to collect rules into a policy store (memory) +class LowLevelPolicy { + public: + // policy_store: must contain allocated memory and the internal + // size fields set to correct values. + explicit LowLevelPolicy(PolicyGlobal* policy_store); + + // Destroys all the policy rules. + ~LowLevelPolicy(); + + // Adds a rule to be generated when Done() is called. + // service: The id of the service that this rule is associated with, + // for example the 'Open Thread' service or the "Create File" service. + // returns false on error. + bool AddRule(IpcTag service, PolicyRule* rule); + + // Generates all the rules added with AddRule() into the memory area + // passed on the constructor. Returns false on error. + bool Done(); + + private: + struct RuleNode { + const PolicyRule* rule; + IpcTag service; + }; + std::list<RuleNode> rules_; + PolicyGlobal* policy_store_; + DISALLOW_IMPLICIT_CONSTRUCTORS(LowLevelPolicy); +}; + +// There are 'if' rules and 'if not' comparisons +enum RuleType { + IF = 0, + IF_NOT = 1, +}; + +// Possible comparisons for numbers +enum RuleOp { + EQUAL, + AND, + RANGE // TODO(cpu): Implement this option. +}; + +// Provides the means to collect a set of comparisons into a single +// rule and its associated action. +class PolicyRule { + friend class LowLevelPolicy; + + public: + explicit PolicyRule(EvalResult action); + PolicyRule(const PolicyRule& other); + ~PolicyRule(); + + // Adds a string comparison to the rule. + // rule_type: possible values are IF and IF_NOT. + // parameter: the expected index of the argument for this rule. For example + // in a 'create file' service the file name argument can be at index 0. + // string: is the desired matching pattern. + // match_opts: if the pattern matching is case sensitive or not. + bool AddStringMatch(RuleType rule_type, + int16_t parameter, + const wchar_t* string, + StringMatchOptions match_opts); + + // Adds a number match comparison to the rule. + // rule_type: possible values are IF and IF_NOT. + // parameter: the expected index of the argument for this rule. + // number: the value to compare the input to. + // comparison_op: the comparison kind (equal, logical and, etc). + bool AddNumberMatch(RuleType rule_type, + int16_t parameter, + uint32_t number, + RuleOp comparison_op); + + // Returns the number of opcodes generated so far. + size_t GetOpcodeCount() const { return buffer_->opcode_count; } + + // Called when there is no more comparisons to add. Internally it generates + // the last opcode (the action opcode). Returns false if this operation fails. + bool Done(); + + private: + void operator=(const PolicyRule&); + // Called in a loop from AddStringMatch to generate the required string + // match opcodes. rule_type, match_opts and parameter are the same as + // in AddStringMatch. + bool GenStringOpcode(RuleType rule_type, + StringMatchOptions match_opts, + uint16_t parameter, + int state, + bool last_call, + int* skip_count, + std::wstring* fragment); + + // Loop over all generated opcodes and copy them to increasing memory + // addresses from opcode_start and copy the extra data (strings usually) into + // decreasing addresses from data_start. Extra data is only present in the + // string evaluation opcodes. + bool RebindCopy(PolicyOpcode* opcode_start, + size_t opcode_size, + char* data_start, + size_t* data_size) const; + PolicyBuffer* buffer_; + OpcodeFactory* opcode_factory_; + EvalResult action_; + bool done_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_LOW_LEVEL_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_low_level_unittest.cc b/security/sandbox/chromium/sandbox/win/src/policy_low_level_unittest.cc new file mode 100644 index 0000000000..8484660096 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_low_level_unittest.cc @@ -0,0 +1,684 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_low_level.h" + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define POLPARAMS_BEGIN(x) sandbox::ParameterSet x[] = { +#define POLPARAM(p) sandbox::ParamPickerMake(p), +#define POLPARAMS_END } + +namespace sandbox { + +bool SetupNtdllImports(); + +// Testing that we allow opcode generation on valid string patterns. +TEST(PolicyEngineTest, StringPatternsOK) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\adobe\\ver??\\", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"*.tmp", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\*.doc", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddStringMatch(IF, 0, L"c:\\windows\\*", CASE_SENSITIVE)); + EXPECT_TRUE( + pr.AddStringMatch(IF, 0, L"d:\\adobe\\acrobat.exe", CASE_SENSITIVE)); +} + +// Testing that we signal invalid string patterns. +TEST(PolicyEngineTest, StringPatternsBAD) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"one**two", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"**three", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"five?six*?seven", CASE_SENSITIVE)); + EXPECT_FALSE(pr.AddStringMatch(IF, 0, L"eight?*nine", CASE_SENSITIVE)); +} + +// Helper function to allocate space (on the heap) for policy. +PolicyGlobal* MakePolicyMemory() { + const size_t kTotalPolicySz = 4096 * 8; + char* mem = new char[kTotalPolicySz]; + memset(mem, 0, kTotalPolicySz); + PolicyGlobal* policy = reinterpret_cast<PolicyGlobal*>(mem); + policy->data_size = kTotalPolicySz - sizeof(PolicyGlobal); + return policy; +} + +// The simplest test using LowLevelPolicy it should test a single opcode which +// does a exact string comparison. +TEST(PolicyEngineTest, SimpleStrMatch) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF, 0, L"z:\\Directory\\domo.txt", CASE_INSENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::PING2; + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = L"Z:\\Directory\\domo.txt"; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"Z:\\Directory\\domo.txt.tmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatch) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::PING2; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\Microsoft\\"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatchWild1) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::NTCREATEFILE; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, SimpleIfNotStrMatchWild2) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*.txt", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::NTCREATEFILE; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\Microsoft\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\domo.bmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, IfNotStrMatchTwoRulesWild1) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF_NOT, 0, L"c:\\Microsoft\\*", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 1, 24, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::NTCREATEFILE; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + uint32_t access = 0; + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(access) // Argument 1 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\Microsoft\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Microsoft\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\MicroNerd\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Micronesia\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, IfNotStrMatchTwoRulesWild2) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE(pr.AddNumberMatch(IF, 1, 24, EQUAL)); + EXPECT_TRUE( + pr.AddStringMatch(IF_NOT, 0, L"c:\\GoogleV?\\*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 2, 66, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + const IpcTag kFakeService = IpcTag::NTCREATEFILE; + LowLevelPolicy policyGen(policy); + + EXPECT_TRUE(policyGen.AddRule(kFakeService, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = nullptr; + uint32_t access = 0; + uint32_t sharing = 66; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(access) // Argument 1 + POLPARAM(sharing) // Argument 2 + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kFakeService)]); + + filename = L"c:\\GoogleV2\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\GoogleV2\\domo.bmp"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\GoogleV23\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\GoogleV2\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Google\\domo.txt"; + access = 24; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Micronesia\\domo.txt"; + access = 42; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\GoogleV2\\domo.bmp"; + access = 24; + sharing = 0; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete[] reinterpret_cast<char*>(policy); +} + +// Testing one single rule in one single service. The service is made to +// resemble NtCreateFile. +TEST(PolicyEngineTest, OneRuleTest) { + SetupNtdllImports(); + PolicyRule pr(ASK_BROKER); + EXPECT_TRUE( + pr.AddStringMatch(IF, 0, L"c:\\*Microsoft*\\*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr.AddNumberMatch(IF_NOT, 1, CREATE_ALWAYS, EQUAL)); + EXPECT_TRUE(pr.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + PolicyGlobal* policy = MakePolicyMemory(); + + const IpcTag kNtFakeCreateFile = IpcTag::NTCREATEFILE; + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* filename = L"c:\\Documents and Settings\\Microsoft\\BLAH.txt"; + uint32_t creation_mode = OPEN_EXISTING; + uint32_t flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = nullptr; + + POLPARAMS_BEGIN(eval_params) + POLPARAM(filename) // Argument 0 + POLPARAM(creation_mode) // Argument 1 + POLPARAM(flags) // Argument 2 + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev(policy->entry[static_cast<size_t>(kNtFakeCreateFile)]); + + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + creation_mode = CREATE_ALWAYS; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + creation_mode = OPEN_EXISTING; + filename = L"c:\\Other\\Path\\Microsoft\\Another file.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Other\\Path\\Microsoft\\Another file.txt.tmp"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags = FILE_ATTRIBUTE_DEVICE; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Other\\Macrosoft\\Another file.txt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\Microsoft\\1.txt"; + flags = FILE_ATTRIBUTE_NORMAL; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev.GetAction()); + + filename = L"c:\\Microsoft\\1.ttt"; + result = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + delete[] reinterpret_cast<char*>(policy); +} + +// Testing 3 rules in 3 services. Two of the services resemble File services. +TEST(PolicyEngineTest, ThreeRulesTest) { + SetupNtdllImports(); + PolicyRule pr_pipe(FAKE_SUCCESS); + EXPECT_TRUE(pr_pipe.AddStringMatch(IF, 0, L"\\\\/?/?\\Pipe\\Chrome.*", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_pipe.AddNumberMatch(IF, 1, OPEN_EXISTING, EQUAL)); + EXPECT_TRUE(pr_pipe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc1 = pr_pipe.GetOpcodeCount(); + EXPECT_EQ(3u, opc1); + + PolicyRule pr_dump(ASK_BROKER); + EXPECT_TRUE(pr_dump.AddStringMatch(IF, 0, L"\\\\/?/?\\*\\Crash Reports\\*", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_dump.AddNumberMatch(IF, 1, CREATE_ALWAYS, EQUAL)); + EXPECT_TRUE(pr_dump.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc2 = pr_dump.GetOpcodeCount(); + EXPECT_EQ(4u, opc2); + + PolicyRule pr_winexe(SIGNAL_ALARM); + EXPECT_TRUE(pr_winexe.AddStringMatch(IF, 0, L"\\\\/?/?\\C:\\Windows\\*.exe", + CASE_INSENSITIVE)); + EXPECT_TRUE(pr_winexe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc3 = pr_winexe.GetOpcodeCount(); + EXPECT_EQ(3u, opc3); + + PolicyRule pr_adobe(GIVE_CACHED); + EXPECT_TRUE( + pr_adobe.AddStringMatch(IF, 0, L"c:\\adobe\\ver?.?\\", CASE_SENSITIVE)); + EXPECT_TRUE(pr_adobe.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_NORMAL, EQUAL)); + + size_t opc4 = pr_adobe.GetOpcodeCount(); + EXPECT_EQ(4u, opc4); + + PolicyRule pr_none(GIVE_FIRST); + EXPECT_TRUE(pr_none.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_READONLY, AND)); + EXPECT_TRUE(pr_none.AddNumberMatch(IF, 2, FILE_ATTRIBUTE_SYSTEM, AND)); + + size_t opc5 = pr_none.GetOpcodeCount(); + EXPECT_EQ(2u, opc5); + + PolicyGlobal* policy = MakePolicyMemory(); + + // These do not match the real tag values. + const IpcTag kNtFakeNone = static_cast<IpcTag>(4); + const IpcTag kNtFakeCreateFile = static_cast<IpcTag>(5); + const IpcTag kNtFakeOpenFile = static_cast<IpcTag>(6); + + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_pipe)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_dump)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeCreateFile, &pr_winexe)); + + EXPECT_TRUE(policyGen.AddRule(kNtFakeOpenFile, &pr_adobe)); + EXPECT_TRUE(policyGen.AddRule(kNtFakeOpenFile, &pr_pipe)); + + EXPECT_TRUE(policyGen.AddRule(kNtFakeNone, &pr_none)); + + EXPECT_TRUE(policyGen.Done()); + + // Inspect the policy structure manually. + EXPECT_FALSE(policy->entry[0]); + EXPECT_FALSE(policy->entry[1]); + EXPECT_FALSE(policy->entry[2]); + EXPECT_FALSE(policy->entry[3]); + EXPECT_TRUE(policy->entry[4]); // kNtFakeNone. + EXPECT_TRUE(policy->entry[5]); // kNtFakeCreateFile. + EXPECT_TRUE(policy->entry[6]); // kNtFakeOpenFile. + EXPECT_FALSE(policy->entry[7]); + + // The total per service opcode counts now must take in account one + // extra opcode (action opcode) per rule. + ++opc1; + ++opc2; + ++opc3; + ++opc4; + ++opc5; + + size_t tc1 = policy->entry[static_cast<size_t>(kNtFakeNone)]->opcode_count; + size_t tc2 = + policy->entry[static_cast<size_t>(kNtFakeCreateFile)]->opcode_count; + size_t tc3 = + policy->entry[static_cast<size_t>(kNtFakeOpenFile)]->opcode_count; + + EXPECT_EQ(opc5, tc1); + EXPECT_EQ((opc1 + opc2 + opc3), tc2); + EXPECT_EQ((opc1 + opc4), tc3); + + // Check the type of the first and last opcode of each service. + + EXPECT_EQ( + OP_NUMBER_AND_MATCH, + policy->entry[static_cast<size_t>(kNtFakeNone)]->opcodes[0].GetID()); + EXPECT_EQ(OP_ACTION, policy->entry[static_cast<size_t>(kNtFakeNone)] + ->opcodes[tc1 - 1] + .GetID()); + EXPECT_EQ(OP_WSTRING_MATCH, + policy->entry[static_cast<size_t>(kNtFakeCreateFile)] + ->opcodes[0] + .GetID()); + EXPECT_EQ(OP_ACTION, policy->entry[static_cast<size_t>(kNtFakeCreateFile)] + ->opcodes[tc2 - 1] + .GetID()); + EXPECT_EQ( + OP_WSTRING_MATCH, + policy->entry[static_cast<size_t>(kNtFakeOpenFile)]->opcodes[0].GetID()); + EXPECT_EQ(OP_ACTION, policy->entry[static_cast<size_t>(kNtFakeOpenFile)] + ->opcodes[tc3 - 1] + .GetID()); + + // Test the policy evaluation. + + const wchar_t* filename = L""; + uint32_t creation_mode = OPEN_EXISTING; + uint32_t flags = FILE_ATTRIBUTE_NORMAL; + void* security_descriptor = nullptr; + + POLPARAMS_BEGIN(params) + POLPARAM(filename) // Argument 0 + POLPARAM(creation_mode) // Argument 1 + POLPARAM(flags) // Argument 2 + POLPARAM(security_descriptor) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor eval_CreateFile( + policy->entry[static_cast<size_t>(kNtFakeCreateFile)]); + PolicyProcessor eval_OpenFile( + policy->entry[static_cast<size_t>(kNtFakeOpenFile)]); + PolicyProcessor eval_None(policy->entry[static_cast<size_t>(kNtFakeNone)]); + + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\c:\\Windows\\System32\\calc.exe"; + flags = FILE_ATTRIBUTE_SYSTEM; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags += FILE_ATTRIBUTE_READONLY; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(GIVE_FIRST, eval_None.GetAction()); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + flags = FILE_ATTRIBUTE_NORMAL; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(SIGNAL_ALARM, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"c:\\adobe\\ver3.2\\temp"; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(GIVE_CACHED, eval_OpenFile.GetAction()); + + filename = L"c:\\adobe\\ver3.22\\temp"; + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\c:\\some path\\other path\\crash reports\\some path"; + creation_mode = CREATE_ALWAYS; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + filename = L"\\\\??\\Pipe\\Chrome.12345"; + creation_mode = OPEN_EXISTING; + result = eval_CreateFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(FAKE_SUCCESS, eval_CreateFile.GetAction()); + result = eval_None.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + result = eval_OpenFile.Evaluate(kShortEval, params, _countof(params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(FAKE_SUCCESS, eval_OpenFile.GetAction()); + + delete[] reinterpret_cast<char*>(policy); +} + +TEST(PolicyEngineTest, PolicyRuleCopyConstructorTwoStrings) { + SetupNtdllImports(); + // Both pr_orig and pr_copy should allow hello.* but not *.txt files. + PolicyRule pr_orig(ASK_BROKER); + EXPECT_TRUE(pr_orig.AddStringMatch(IF, 0, L"hello.*", CASE_SENSITIVE)); + + PolicyRule pr_copy(pr_orig); + EXPECT_TRUE(pr_orig.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr_copy.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + LowLevelPolicy policyGen(policy); + EXPECT_TRUE(policyGen.AddRule(IpcTag::PING1, &pr_orig)); + EXPECT_TRUE(policyGen.AddRule(IpcTag::PING2, &pr_copy)); + EXPECT_TRUE(policyGen.Done()); + + const wchar_t* name = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(name) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev_orig(policy->entry[1]); + name = L"domo.txt"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_orig.GetAction()); + + PolicyProcessor pol_ev_copy(policy->entry[2]); + name = L"domo.txt"; + result = pol_ev_copy.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_copy.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_copy.GetAction()); +} + +TEST(PolicyEngineTest, PolicyGenDoneCalledTwice) { + SetupNtdllImports(); + // The specific rules here are not important. + PolicyRule pr_orig(ASK_BROKER); + EXPECT_TRUE(pr_orig.AddStringMatch(IF, 0, L"hello.*", CASE_SENSITIVE)); + + PolicyRule pr_copy(pr_orig); + EXPECT_TRUE(pr_orig.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + EXPECT_TRUE(pr_copy.AddStringMatch(IF_NOT, 0, L"*.txt", CASE_SENSITIVE)); + + PolicyGlobal* policy = MakePolicyMemory(); + LowLevelPolicy policyGen(policy); + const IpcTag tag1 = IpcTag::PING1; + const IpcTag tag2 = IpcTag::PING2; + EXPECT_TRUE(policyGen.AddRule(tag1, &pr_orig)); + EXPECT_TRUE(policyGen.AddRule(tag2, &pr_copy)); + EXPECT_TRUE(policyGen.Done()); + + // Obtain opcode counts. + size_t tc1 = policy->entry[static_cast<size_t>(IpcTag::PING1)]->opcode_count; + size_t tc2 = policy->entry[static_cast<size_t>(IpcTag::PING2)]->opcode_count; + + // Call Done() again. + EXPECT_TRUE(policyGen.Done()); + + // Expect same opcode counts. + EXPECT_EQ(tc1, + policy->entry[static_cast<size_t>(IpcTag::PING1)]->opcode_count); + EXPECT_EQ(tc2, + policy->entry[static_cast<size_t>(IpcTag::PING2)]->opcode_count); + + // Confirm the rules work as before. + const wchar_t* name = nullptr; + POLPARAMS_BEGIN(eval_params) + POLPARAM(name) + POLPARAMS_END; + + PolicyResult result; + PolicyProcessor pol_ev_orig(policy->entry[1]); + name = L"domo.txt"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(NO_POLICY_MATCH, result); + + name = L"hello.bmp"; + result = pol_ev_orig.Evaluate(kShortEval, eval_params, _countof(eval_params)); + EXPECT_EQ(POLICY_MATCH, result); + EXPECT_EQ(ASK_BROKER, pol_ev_orig.GetAction()); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_opcodes_unittest.cc b/security/sandbox/chromium/sandbox/win/src/policy_opcodes_unittest.cc new file mode 100644 index 0000000000..c83efcba18 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_opcodes_unittest.cc @@ -0,0 +1,364 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_engine_opcodes.h" + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define INIT_GLOBAL_RTL(member) \ + g_nt.member = \ + reinterpret_cast<member##Function>(::GetProcAddress(ntdll, #member)); \ + if (!g_nt.member) \ + return false + +namespace sandbox { + +const size_t kOpcodeMemory = 1024; + +SANDBOX_INTERCEPT NtExports g_nt; + +bool SetupNtdllImports() { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + + INIT_GLOBAL_RTL(RtlAllocateHeap); + INIT_GLOBAL_RTL(RtlAnsiStringToUnicodeString); + INIT_GLOBAL_RTL(RtlCompareUnicodeString); + INIT_GLOBAL_RTL(RtlCreateHeap); + INIT_GLOBAL_RTL(RtlDestroyHeap); + INIT_GLOBAL_RTL(RtlFreeHeap); + INIT_GLOBAL_RTL(_strnicmp); + INIT_GLOBAL_RTL(strlen); + INIT_GLOBAL_RTL(wcslen); + + return true; +} + +TEST(PolicyEngineTest, ParameterSetTest) { + void* pv1 = reinterpret_cast<void*>(0x477EAA5); + const void* pv2 = reinterpret_cast<void*>(0x987654); + ParameterSet pset1 = ParamPickerMake(pv1); + ParameterSet pset2 = ParamPickerMake(pv2); + + // Test that we can store and retrieve a void pointer: + const void* result1 = 0; + uint32_t result2 = 0; + EXPECT_TRUE(pset1.Get(&result1)); + EXPECT_TRUE(pv1 == result1); + EXPECT_FALSE(pset1.Get(&result2)); + EXPECT_TRUE(pset2.Get(&result1)); + EXPECT_TRUE(pv2 == result1); + EXPECT_FALSE(pset2.Get(&result2)); + + // Test that we can store and retrieve a uint32_t: + uint32_t number = 12747; + ParameterSet pset3 = ParamPickerMake(number); + EXPECT_FALSE(pset3.Get(&result1)); + EXPECT_TRUE(pset3.Get(&result2)); + EXPECT_EQ(number, result2); + + // Test that we can store and retrieve a string: + const wchar_t* txt = L"S231L"; + ParameterSet pset4 = ParamPickerMake(txt); + const wchar_t* result3 = nullptr; + EXPECT_TRUE(pset4.Get(&result3)); + EXPECT_EQ(0, wcscmp(txt, result3)); +} + +TEST(PolicyEngineTest, OpcodeConstraints) { + // Test that PolicyOpcode has no virtual functions + // because these objects are copied over to other processes + // so they cannot have vtables. + EXPECT_FALSE(__is_polymorphic(PolicyOpcode)); + // Keep developers from adding smarts to the opcodes which should + // be pretty much a bag of bytes with a OO interface. + EXPECT_TRUE(__has_trivial_destructor(PolicyOpcode)); + EXPECT_TRUE(__has_trivial_constructor(PolicyOpcode)); + EXPECT_TRUE(__has_trivial_copy(PolicyOpcode)); +} + +TEST(PolicyEngineTest, TrueFalseOpcodes) { + void* dummy = nullptr; + ParameterSet ppb1 = ParamPickerMake(dummy); + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + // This opcode always evaluates to true. + PolicyOpcode* op1 = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_NE(nullptr, op1); + EXPECT_EQ(EVAL_FALSE, op1->Evaluate(&ppb1, 1, nullptr)); + EXPECT_FALSE(op1->IsAction()); + + // This opcode always evaluates to false. + PolicyOpcode* op2 = opcode_maker.MakeOpAlwaysTrue(kPolNone); + ASSERT_NE(nullptr, op2); + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 1, nullptr)); + + // Nulls not allowed on the params. + EXPECT_EQ(EVAL_ERROR, op2->Evaluate(nullptr, 0, nullptr)); + EXPECT_EQ(EVAL_ERROR, op2->Evaluate(nullptr, 1, nullptr)); + + // True and False opcodes do not 'require' a number of parameters + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 0, nullptr)); + EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 1, nullptr)); + + // Test Inverting the logic. Note that inversion is done outside + // any particular opcode evaluation so no need to repeat for all + // opcodes. + PolicyOpcode* op3 = opcode_maker.MakeOpAlwaysFalse(kPolNegateEval); + ASSERT_NE(nullptr, op3); + EXPECT_EQ(EVAL_TRUE, op3->Evaluate(&ppb1, 1, nullptr)); + PolicyOpcode* op4 = opcode_maker.MakeOpAlwaysTrue(kPolNegateEval); + ASSERT_NE(nullptr, op4); + EXPECT_EQ(EVAL_FALSE, op4->Evaluate(&ppb1, 1, nullptr)); + + // Test that we clear the match context + PolicyOpcode* op5 = opcode_maker.MakeOpAlwaysTrue(kPolClearContext); + ASSERT_NE(nullptr, op5); + MatchContext context; + context.position = 1; + context.options = kPolUseOREval; + EXPECT_EQ(EVAL_TRUE, op5->Evaluate(&ppb1, 1, &context)); + EXPECT_EQ(0u, context.position); + MatchContext context2; + EXPECT_EQ(context2.options, context.options); +} + +TEST(PolicyEngineTest, OpcodeMakerCase1) { + // Testing that the opcode maker does not overrun the + // supplied buffer. It should only be able to make 'count' opcodes. + void* dummy = nullptr; + ParameterSet ppb1 = ParamPickerMake(dummy); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + size_t count = sizeof(memory) / sizeof(PolicyOpcode); + + for (size_t ix = 0; ix != count; ++ix) { + PolicyOpcode* op = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_NE(nullptr, op); + EXPECT_EQ(EVAL_FALSE, op->Evaluate(&ppb1, 1, nullptr)); + } + // There should be no room more another opcode: + PolicyOpcode* op1 = opcode_maker.MakeOpAlwaysFalse(kPolNone); + ASSERT_EQ(nullptr, op1); +} + +TEST(PolicyEngineTest, OpcodeMakerCase2) { + SetupNtdllImports(); + // Testing that the opcode maker does not overrun the + // supplied buffer. It should only be able to make 'count' opcodes. + // The difference with the previous test is that this opcodes allocate + // the string 'txt2' inside the same buffer. + const wchar_t* txt1 = L"1234"; + const wchar_t txt2[] = L"123"; + + ParameterSet ppb1 = ParamPickerMake(txt1); + MatchContext mc1; + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + size_t count = sizeof(memory) / (sizeof(PolicyOpcode) + sizeof(txt2)); + + // Test that it does not overrun the buffer. + for (size_t ix = 0; ix != count; ++ix) { + PolicyOpcode* op = opcode_maker.MakeOpWStringMatch( + 0, txt2, 0, CASE_SENSITIVE, kPolClearContext); + ASSERT_NE(nullptr, op); + EXPECT_EQ(EVAL_TRUE, op->Evaluate(&ppb1, 1, &mc1)); + } + + // There should be no room more another opcode: + PolicyOpcode* op1 = + opcode_maker.MakeOpWStringMatch(0, txt2, 0, CASE_SENSITIVE, kPolNone); + ASSERT_EQ(nullptr, op1); +} + +TEST(PolicyEngineTest, IntegerOpcodes) { + const wchar_t* txt = L"abcdef"; + uint32_t num1 = 42; + uint32_t num2 = 113377; + + ParameterSet pp_wrong1 = ParamPickerMake(txt); + ParameterSet pp_num1 = ParamPickerMake(num1); + ParameterSet pp_num2 = ParamPickerMake(num2); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + // Test basic match for uint32s 42 == 42 and 42 != 113377. + PolicyOpcode* op_m42 = opcode_maker.MakeOpNumberMatch(0, 42UL, kPolNone); + ASSERT_NE(nullptr, op_m42); + EXPECT_EQ(EVAL_TRUE, op_m42->Evaluate(&pp_num1, 1, nullptr)); + EXPECT_EQ(EVAL_FALSE, op_m42->Evaluate(&pp_num2, 1, nullptr)); + EXPECT_EQ(EVAL_ERROR, op_m42->Evaluate(&pp_wrong1, 1, nullptr)); + + // Test basic match for void pointers. + const void* vp = nullptr; + ParameterSet pp_num3 = ParamPickerMake(vp); + PolicyOpcode* op_vp_null = + opcode_maker.MakeOpVoidPtrMatch(0, nullptr, kPolNone); + ASSERT_NE(nullptr, op_vp_null); + EXPECT_EQ(EVAL_TRUE, op_vp_null->Evaluate(&pp_num3, 1, nullptr)); + EXPECT_EQ(EVAL_FALSE, op_vp_null->Evaluate(&pp_num1, 1, nullptr)); + EXPECT_EQ(EVAL_ERROR, op_vp_null->Evaluate(&pp_wrong1, 1, nullptr)); + + // Basic range test [41 43] (inclusive). + PolicyOpcode* op_range1 = + opcode_maker.MakeOpNumberMatchRange(0, 41, 43, kPolNone); + ASSERT_NE(nullptr, op_range1); + EXPECT_EQ(EVAL_TRUE, op_range1->Evaluate(&pp_num1, 1, nullptr)); + EXPECT_EQ(EVAL_FALSE, op_range1->Evaluate(&pp_num2, 1, nullptr)); + EXPECT_EQ(EVAL_ERROR, op_range1->Evaluate(&pp_wrong1, 1, nullptr)); +} + +TEST(PolicyEngineTest, LogicalOpcodes) { + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + uint32_t num1 = 0x10100702; + ParameterSet pp_num1 = ParamPickerMake(num1); + + PolicyOpcode* op_and1 = + opcode_maker.MakeOpNumberAndMatch(0, 0x00100000, kPolNone); + ASSERT_NE(nullptr, op_and1); + EXPECT_EQ(EVAL_TRUE, op_and1->Evaluate(&pp_num1, 1, nullptr)); + PolicyOpcode* op_and2 = + opcode_maker.MakeOpNumberAndMatch(0, 0x00000001, kPolNone); + ASSERT_NE(nullptr, op_and2); + EXPECT_EQ(EVAL_FALSE, op_and2->Evaluate(&pp_num1, 1, nullptr)); +} + +TEST(PolicyEngineTest, WCharOpcodes1) { + SetupNtdllImports(); + + const wchar_t* txt1 = L"the quick fox jumps over the lazy dog"; + const wchar_t txt2[] = L"the quick"; + const wchar_t txt3[] = L" fox jumps"; + const wchar_t txt4[] = L"the lazy dog"; + const wchar_t txt5[] = L"jumps over"; + const wchar_t txt6[] = L"g"; + + ParameterSet pp_tc1 = ParamPickerMake(txt1); + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + + PolicyOpcode* op1 = + opcode_maker.MakeOpWStringMatch(0, txt2, 0, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op1); + + // Simplest substring match from pos 0. It should be a successful match + // and the match context should be updated. + MatchContext mc1; + EXPECT_EQ(EVAL_TRUE, op1->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt2) == mc1.position + 1); + + // Matching again should fail and the context should be unmodified. + EXPECT_EQ(EVAL_FALSE, op1->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt2) == mc1.position + 1); + + // Using the same match context we should continue where we left + // in the previous successful match, + PolicyOpcode* op3 = + opcode_maker.MakeOpWStringMatch(0, txt3, 0, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op3); + EXPECT_EQ(EVAL_TRUE, op3->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_TRUE(_countof(txt3) + _countof(txt2) == mc1.position + 2); + + // We now keep on matching but now we skip 6 characters which means + // we skip the string ' over '. And we zero the match context. This is + // the primitive that we use to build '??'. + PolicyOpcode* op4 = opcode_maker.MakeOpWStringMatch( + 0, txt4, 6, CASE_SENSITIVE, kPolClearContext); + ASSERT_NE(nullptr, op4); + EXPECT_EQ(EVAL_TRUE, op4->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(0u, mc1.position); + + // Test that we can properly match the last part of the string + PolicyOpcode* op4b = opcode_maker.MakeOpWStringMatch( + 0, txt4, kSeekToEnd, CASE_SENSITIVE, kPolClearContext); + ASSERT_NE(nullptr, op4b); + EXPECT_EQ(EVAL_TRUE, op4b->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(0u, mc1.position); + + // Test matching 'jumps over' over the entire string. This is the + // primitive we build '*' from. + PolicyOpcode* op5 = opcode_maker.MakeOpWStringMatch(0, txt5, kSeekForward, + CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op5); + EXPECT_EQ(EVAL_TRUE, op5->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(24u, mc1.position); + + // Test that we don't match because it is not at the end of the string + PolicyOpcode* op5b = opcode_maker.MakeOpWStringMatch( + 0, txt5, kSeekToEnd, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op5b); + EXPECT_EQ(EVAL_FALSE, op5b->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(24u, mc1.position); + + // Test that we function if the string does not fit. In this case we + // try to match 'the lazy dog' against 'he lazy dog'. + PolicyOpcode* op6 = + opcode_maker.MakeOpWStringMatch(0, txt4, 2, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op6); + EXPECT_EQ(EVAL_FALSE, op6->Evaluate(&pp_tc1, 1, &mc1)); + + // Testing matching against 'g' which should be the last char. + MatchContext mc2; + PolicyOpcode* op7 = opcode_maker.MakeOpWStringMatch(0, txt6, kSeekForward, + CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op7); + EXPECT_EQ(EVAL_TRUE, op7->Evaluate(&pp_tc1, 1, &mc2)); + EXPECT_EQ(37u, mc2.position); + + // Trying to match again should fail since we are in the last char. + // This also covers a couple of boundary conditions. + EXPECT_EQ(EVAL_FALSE, op7->Evaluate(&pp_tc1, 1, &mc2)); + EXPECT_EQ(37u, mc2.position); +} + +TEST(PolicyEngineTest, WCharOpcodes2) { + SetupNtdllImports(); + + const wchar_t* path1 = L"c:\\documents and settings\\Microsoft\\BLAH.txt"; + const wchar_t txt1[] = L"Settings\\microsoft"; + ParameterSet pp_tc1 = ParamPickerMake(path1); + + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + MatchContext mc1; + + // Testing case-insensitive does not buy us much since it this option + // is just passed to the Microsoft API that we use normally, but just for + // coverage, here it is: + PolicyOpcode* op1s = opcode_maker.MakeOpWStringMatch( + 0, txt1, kSeekForward, CASE_SENSITIVE, kPolNone); + ASSERT_NE(nullptr, op1s); + PolicyOpcode* op1i = opcode_maker.MakeOpWStringMatch( + 0, txt1, kSeekForward, CASE_INSENSITIVE, kPolNone); + ASSERT_NE(nullptr, op1i); + EXPECT_EQ(EVAL_FALSE, op1s->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(EVAL_TRUE, op1i->Evaluate(&pp_tc1, 1, &mc1)); + EXPECT_EQ(35u, mc1.position); +} + +TEST(PolicyEngineTest, ActionOpcodes) { + char memory[kOpcodeMemory]; + OpcodeFactory opcode_maker(memory, sizeof(memory)); + MatchContext mc1; + void* dummy = nullptr; + ParameterSet ppb1 = ParamPickerMake(dummy); + + PolicyOpcode* op1 = opcode_maker.MakeOpAction(ASK_BROKER, kPolNone); + ASSERT_NE(nullptr, op1); + EXPECT_TRUE(op1->IsAction()); + EXPECT_EQ(ASK_BROKER, op1->Evaluate(&ppb1, 1, &mc1)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_params.h b/security/sandbox/chromium/sandbox/win/src/policy_params.h new file mode 100644 index 0000000000..aeec6d7edc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_params.h @@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_POLICY_PARAMS_H__ +#define SANDBOX_SRC_POLICY_PARAMS_H__ + +#include "sandbox/win/src/policy_engine_params.h" + +namespace sandbox { + +class ParameterSet; + +// Warning: The following macros store the address to the actual variables, in +// other words, the values are not copied. +#define POLPARAMS_BEGIN(type) class type { public: enum Args { +#define POLPARAM(arg) arg, +#define POLPARAMS_END(type) PolParamLast }; }; \ + typedef sandbox::ParameterSet type##Array [type::PolParamLast]; + +// Policy parameters for file open / create. +POLPARAMS_BEGIN(OpenFile) + POLPARAM(NAME) + POLPARAM(BROKER) // true if called from the broker. + POLPARAM(ACCESS) + POLPARAM(DISPOSITION) + POLPARAM(OPTIONS) +POLPARAMS_END(OpenFile) + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(FileName) + POLPARAM(NAME) + POLPARAM(BROKER) // true if called from the broker. +POLPARAMS_END(FileName) + +static_assert(OpenFile::NAME == static_cast<int>(FileName::NAME), + "to simplify fs policies"); +static_assert(OpenFile::BROKER == static_cast<int>(FileName::BROKER), + "to simplify fs policies"); + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(NameBased) + POLPARAM(NAME) +POLPARAMS_END(NameBased) + +// Policy parameters for open event. +POLPARAMS_BEGIN(OpenEventParams) + POLPARAM(NAME) + POLPARAM(ACCESS) +POLPARAMS_END(OpenEventParams) + +// Policy Parameters for reg open / create. +POLPARAMS_BEGIN(OpenKey) + POLPARAM(NAME) + POLPARAM(ACCESS) +POLPARAMS_END(OpenKey) + +// Policy parameter for name-based policies. +POLPARAMS_BEGIN(HandleTarget) + POLPARAM(NAME) + POLPARAM(TARGET) +POLPARAMS_END(HandleTarget) + +// Policy parameters where no parameter based checks are done. +POLPARAMS_BEGIN(EmptyParams) +POLPARAMS_END(EmptyParams) + +} // namespace sandbox + +#endif // SANDBOX_SRC_POLICY_PARAMS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_target.cc b/security/sandbox/chromium/sandbox/win/src/policy_target.cc new file mode 100644 index 0000000000..1727d622f8 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_target.cc @@ -0,0 +1,138 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/policy_target.h" + +#include <stddef.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +// Handle for our private heap. +extern void* g_heap; + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +// Policy data. +extern void* volatile g_shared_policy_memory; +SANDBOX_INTERCEPT size_t g_shared_policy_size; + +bool QueryBroker(IpcTag ipc_id, CountedParameterSetBase* params) { + DCHECK_NT(static_cast<size_t>(ipc_id) < kMaxServiceCount); + DCHECK_NT(g_shared_policy_memory); + DCHECK_NT(g_shared_policy_size > 0); + + if (static_cast<size_t>(ipc_id) >= kMaxServiceCount) + return false; + + PolicyGlobal* global_policy = + reinterpret_cast<PolicyGlobal*>(g_shared_policy_memory); + + if (!global_policy->entry[static_cast<size_t>(ipc_id)]) + return false; + + PolicyBuffer* policy = reinterpret_cast<PolicyBuffer*>( + reinterpret_cast<char*>(g_shared_policy_memory) + + reinterpret_cast<size_t>( + global_policy->entry[static_cast<size_t>(ipc_id)])); + + if ((reinterpret_cast<size_t>( + global_policy->entry[static_cast<size_t>(ipc_id)]) > + global_policy->data_size) || + (g_shared_policy_size < global_policy->data_size)) { + NOTREACHED_NT(); + return false; + } + + for (size_t i = 0; i < params->count; i++) { + if (!params->parameters[i].IsValid()) { + NOTREACHED_NT(); + return false; + } + } + + PolicyProcessor processor(policy); + PolicyResult result = + processor.Evaluate(kShortEval, params->parameters, params->count); + DCHECK_NT(POLICY_ERROR != result); + + return POLICY_MATCH == result && ASK_BROKER == processor.GetAction(); +} + +// ----------------------------------------------------------------------- + +// Hooks NtSetInformationThread to block RevertToSelf from being +// called before the actual call to LowerToken. +NTSTATUS WINAPI TargetNtSetInformationThread( + NtSetInformationThreadFunction orig_SetInformationThread, + HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, + PVOID thread_information, + ULONG thread_information_bytes) { + do { + if (SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + break; + if (ThreadImpersonationToken != thread_info_class) + break; + if (!thread_information) + break; + HANDLE token; + if (sizeof(token) > thread_information_bytes) + break; + + NTSTATUS ret = CopyData(&token, thread_information, sizeof(token)); + if (!NT_SUCCESS(ret) || NULL != token) + break; + + // This is a revert to self. + return STATUS_SUCCESS; + } while (false); + + return orig_SetInformationThread( + thread, thread_info_class, thread_information, thread_information_bytes); +} + +// Hooks NtOpenThreadToken to force the open_as_self parameter to be set to +// false if we are still running with the impersonation token. open_as_self set +// to true means that the token will be open using the process token instead of +// the impersonation token. This is bad because the process token does not have +// access to open the thread token. +NTSTATUS WINAPI +TargetNtOpenThreadToken(NtOpenThreadTokenFunction orig_OpenThreadToken, + HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + PHANDLE token) { + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + open_as_self = false; + + return orig_OpenThreadToken(thread, desired_access, open_as_self, token); +} + +// See comment for TargetNtOpenThreadToken +NTSTATUS WINAPI +TargetNtOpenThreadTokenEx(NtOpenThreadTokenExFunction orig_OpenThreadTokenEx, + HANDLE thread, + ACCESS_MASK desired_access, + BOOLEAN open_as_self, + ULONG handle_attributes, + PHANDLE token) { + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) + open_as_self = false; + + return orig_OpenThreadTokenEx(thread, desired_access, open_as_self, + handle_attributes, token); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/policy_target.h b/security/sandbox/chromium/sandbox/win/src/policy_target.h new file mode 100644 index 0000000000..62686aaab2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_target.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_POLICY_TARGET_H_ +#define SANDBOX_WIN_SRC_POLICY_TARGET_H_ + +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +struct CountedParameterSetBase; + +// Performs a policy lookup and returns true if the request should be passed to +// the broker process. +bool QueryBroker(IpcTag ipc_id, CountedParameterSetBase* params); + +extern "C" { + +// Interception of NtSetInformationThread on the child process. +// It should never be called directly. +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtSetInformationThread( + NtSetInformationThreadFunction orig_SetInformationThread, HANDLE thread, + NT_THREAD_INFORMATION_CLASS thread_info_class, PVOID thread_information, + ULONG thread_information_bytes); + +// Interception of NtOpenThreadToken on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadToken( + NtOpenThreadTokenFunction orig_OpenThreadToken, HANDLE thread, + ACCESS_MASK desired_access, BOOLEAN open_as_self, PHANDLE token); + +// Interception of NtOpenThreadTokenEx on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenThreadTokenEx( + NtOpenThreadTokenExFunction orig_OpenThreadTokenEx, HANDLE thread, + ACCESS_MASK desired_access, BOOLEAN open_as_self, ULONG handle_attributes, + PHANDLE token); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_POLICY_TARGET_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/policy_target_test.cc b/security/sandbox/chromium/sandbox/win/src/policy_target_test.cc new file mode 100644 index 0000000000..4ee03e19f6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/policy_target_test.cc @@ -0,0 +1,486 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/read_only_shared_memory_region.h" +#include "base/memory/writable_shared_memory_region.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/win/win_util.h" +#endif + +namespace sandbox { + +#define BINDNTDLL(name) \ + name##Function name = reinterpret_cast<name##Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +// Reverts to self and verify that SetInformationToken was faked. Returns +// SBOX_TEST_SUCCEEDED if faked and SBOX_TEST_FAILED if not faked. +SBOX_TESTS_COMMAND int PolicyTargetTest_token(int argc, wchar_t** argv) { + HANDLE thread_token; + // Get the thread token, using impersonation. + if (!::OpenThreadToken(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, false, + &thread_token)) + return ::GetLastError(); + + ::RevertToSelf(); + ::CloseHandle(thread_token); + + int ret = SBOX_TEST_FAILED; + if (::OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + false, &thread_token)) { + ret = SBOX_TEST_SUCCEEDED; + ::CloseHandle(thread_token); + } + return ret; +} + +// Stores the high privilege token on a static variable, change impersonation +// again to that one and verify that we are not interfering anymore with +// RevertToSelf. +SBOX_TESTS_COMMAND int PolicyTargetTest_steal(int argc, wchar_t** argv) { + static HANDLE thread_token; + if (!SandboxFactory::GetTargetServices()->GetState()->RevertedToSelf()) { + if (!::OpenThreadToken(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, false, + &thread_token)) + return ::GetLastError(); + } else { + if (!::SetThreadToken(nullptr, thread_token)) + return ::GetLastError(); + + // See if we fake the call again. + int ret = PolicyTargetTest_token(argc, argv); + ::CloseHandle(thread_token); + return ret; + } + return 0; +} + +// Opens the thread token with and without impersonation. +SBOX_TESTS_COMMAND int PolicyTargetTest_token2(int argc, wchar_t** argv) { + HANDLE thread_token; + // Get the thread token, using impersonation. + if (!::OpenThreadToken(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, false, + &thread_token)) + return ::GetLastError(); + ::CloseHandle(thread_token); + + // Get the thread token, without impersonation. + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + true, &thread_token)) + return ::GetLastError(); + ::CloseHandle(thread_token); + return SBOX_TEST_SUCCEEDED; +} + +// Opens the thread token with and without impersonation, using +// NtOpenThreadTokenEX. +SBOX_TESTS_COMMAND int PolicyTargetTest_token3(int argc, wchar_t** argv) { + BINDNTDLL(NtOpenThreadTokenEx); + if (!NtOpenThreadTokenEx) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + HANDLE thread_token; + // Get the thread token, using impersonation. + NTSTATUS status = NtOpenThreadTokenEx(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, + false, 0, &thread_token); + if (status == STATUS_NO_TOKEN) + return ERROR_NO_TOKEN; + if (!NT_SUCCESS(status)) + return SBOX_TEST_FAILED; + + ::CloseHandle(thread_token); + + // Get the thread token, without impersonation. + status = NtOpenThreadTokenEx(GetCurrentThread(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE, true, 0, + &thread_token); + if (!NT_SUCCESS(status)) + return SBOX_TEST_FAILED; + + ::CloseHandle(thread_token); + return SBOX_TEST_SUCCEEDED; +} + +// Tests that we can open the current thread. +SBOX_TESTS_COMMAND int PolicyTargetTest_thread(int argc, wchar_t** argv) { + DWORD thread_id = ::GetCurrentThreadId(); + HANDLE thread = ::OpenThread(SYNCHRONIZE, false, thread_id); + if (!thread) + return ::GetLastError(); + if (!::CloseHandle(thread)) + return ::GetLastError(); + + return SBOX_TEST_SUCCEEDED; +} + +// New thread entry point: do nothing. +DWORD WINAPI PolicyTargetTest_thread_main(void* param) { + ::Sleep(INFINITE); + return 0; +} + +// Tests that we can create a new thread, and open it. +SBOX_TESTS_COMMAND int PolicyTargetTest_thread2(int argc, wchar_t** argv) { + // Use default values to create a new thread. + DWORD thread_id; + HANDLE thread = ::CreateThread(nullptr, 0, &PolicyTargetTest_thread_main, 0, + 0, &thread_id); + if (!thread) + return ::GetLastError(); + if (!::CloseHandle(thread)) + return ::GetLastError(); + + thread = ::OpenThread(SYNCHRONIZE, false, thread_id); + if (!thread) + return ::GetLastError(); + + if (!::CloseHandle(thread)) + return ::GetLastError(); + + return SBOX_TEST_SUCCEEDED; +} + +// Tests that we can call CreateProcess. +SBOX_TESTS_COMMAND int PolicyTargetTest_process(int argc, wchar_t** argv) { + // Use default values to create a new process. + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + PROCESS_INFORMATION temp_process_info = {}; + // Note: CreateProcessW() can write to its lpCommandLine, don't pass a + // raw string literal. + std::wstring writable_cmdline_str(L"foo.exe"); + if (!::CreateProcessW(L"foo.exe", &writable_cmdline_str[0], nullptr, nullptr, + false, 0, nullptr, nullptr, &startup_info, + &temp_process_info)) + return SBOX_TEST_SUCCEEDED; + base::win::ScopedProcessInformation process_info(temp_process_info); + return SBOX_TEST_FAILED; +} + +TEST(PolicyTargetTest, SetInformationThread) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token")); + + runner.SetTestState(EVERY_STATE); + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"PolicyTargetTest_steal")); +} + +TEST(PolicyTargetTest, OpenThreadToken) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token2")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token2")); +} + +TEST(PolicyTargetTest, OpenThreadTokenEx) { + TestRunner runner; + + runner.SetTestState(BEFORE_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_token3")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(ERROR_NO_TOKEN, runner.RunTest(L"PolicyTargetTest_token3")); +} + +TEST(PolicyTargetTest, OpenThread) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread")) + << "Opens the current thread"; + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread2")) + << "Creates a new thread and opens it"; +} + +TEST(PolicyTargetTest, OpenProcess) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_process")) + << "Opens a process"; +} + +TEST(PolicyTargetTest, PolicyBaseNoJobLifetime) { + TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + runner.SetReleasePolicyInRun(true); + // TargetPolicy and its SharedMemIPCServer should continue to exist until + // the child process dies. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"PolicyTargetTest_thread")) + << "Opens the current thread"; +} + +// Launches the app in the sandbox and ask it to wait in an +// infinite loop. Waits for 2 seconds and then check if the +// desktop associated with the app thread is not the same as the +// current desktop. +TEST(PolicyTargetTest, DesktopPolicy) { + BrokerServices* broker = GetBroker(); + + // Precreate the desktop. + scoped_refptr<TargetPolicy> temp_policy = broker->CreatePolicy(); + temp_policy->CreateAlternateDesktop(false); + temp_policy = nullptr; + + ASSERT_TRUE(broker); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(nullptr, prog_name, MAX_PATH); + + std::wstring arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 wait"; // Don't care about the "state" argument. + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + ResultCode warning_result = SBOX_ALL_OK; + DWORD last_error = ERROR_SUCCESS; + base::win::ScopedProcessInformation target; + + scoped_refptr<TargetPolicy> policy = broker->CreatePolicy(); + policy->SetAlternateDesktop(false); + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + result = + broker->SpawnTarget(prog_name, arguments.c_str(), policy, &warning_result, + &last_error, &temp_process_info); + std::wstring desktop_name = policy->GetAlternateDesktop(); + policy = nullptr; + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1u, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), + ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_NE(::GetThreadDesktop(target.thread_id()), + ::GetThreadDesktop(::GetCurrentThreadId())); + + HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE); + EXPECT_TRUE(desk); + EXPECT_TRUE(::CloseDesktop(desk)); + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); + + // Close the desktop handle. + temp_policy = broker->CreatePolicy(); + temp_policy->DestroyAlternateDesktop(); + temp_policy = nullptr; + + // Make sure the desktop does not exist anymore. + desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE); + EXPECT_FALSE(desk); +} + +// Launches the app in the sandbox and ask it to wait in an +// infinite loop. Waits for 2 seconds and then check if the +// winstation associated with the app thread is not the same as the +// current desktop. +TEST(PolicyTargetTest, WinstaPolicy) { + BrokerServices* broker = GetBroker(); + + // Precreate the desktop. + scoped_refptr<TargetPolicy> temp_policy = broker->CreatePolicy(); + temp_policy->CreateAlternateDesktop(true); + temp_policy = nullptr; + + ASSERT_TRUE(broker); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(nullptr, prog_name, MAX_PATH); + + std::wstring arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 wait"; // Don't care about the "state" argument. + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + ResultCode warning_result = SBOX_ALL_OK; + base::win::ScopedProcessInformation target; + + scoped_refptr<TargetPolicy> policy = broker->CreatePolicy(); + policy->SetAlternateDesktop(true); + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + DWORD last_error = ERROR_SUCCESS; + result = + broker->SpawnTarget(prog_name, arguments.c_str(), policy, &warning_result, + &last_error, &temp_process_info); + std::wstring desktop_name = policy->GetAlternateDesktop(); + policy = nullptr; + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1u, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), + ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_NE(::GetThreadDesktop(target.thread_id()), + ::GetThreadDesktop(::GetCurrentThreadId())); + + ASSERT_FALSE(desktop_name.empty()); + + // Make sure there is a backslash, for the window station name. + EXPECT_NE(desktop_name.find_first_of(L'\\'), std::wstring::npos); + + // Isolate the desktop name. + desktop_name = desktop_name.substr(desktop_name.find_first_of(L'\\') + 1); + + HDESK desk = ::OpenDesktop(desktop_name.c_str(), 0, false, DESKTOP_ENUMERATE); + // This should fail if the desktop is really on another window station. + EXPECT_FALSE(desk); + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); + + // Close the desktop handle. + temp_policy = broker->CreatePolicy(); + temp_policy->DestroyAlternateDesktop(); + temp_policy = nullptr; +} + +// Creates multiple policies, with alternate desktops on both local and +// alternate winstations. +TEST(PolicyTargetTest, BothLocalAndAlternateWinstationDesktop) { + BrokerServices* broker = GetBroker(); + + scoped_refptr<TargetPolicy> policy1 = broker->CreatePolicy(); + scoped_refptr<TargetPolicy> policy2 = broker->CreatePolicy(); + scoped_refptr<TargetPolicy> policy3 = broker->CreatePolicy(); + + ResultCode result; + result = policy1->SetAlternateDesktop(false); + EXPECT_EQ(SBOX_ALL_OK, result); + result = policy2->SetAlternateDesktop(true); + EXPECT_EQ(SBOX_ALL_OK, result); + result = policy3->SetAlternateDesktop(false); + EXPECT_EQ(SBOX_ALL_OK, result); + + std::wstring policy1_desktop_name = policy1->GetAlternateDesktop(); + std::wstring policy2_desktop_name = policy2->GetAlternateDesktop(); + + // Extract only the "desktop name" portion of + // "{winstation name}\\{desktop name}" + EXPECT_NE(policy1_desktop_name.substr( + policy1_desktop_name.find_first_of(L'\\') + 1), + policy2_desktop_name.substr( + policy2_desktop_name.find_first_of(L'\\') + 1)); + + policy1->DestroyAlternateDesktop(); + policy2->DestroyAlternateDesktop(); + policy3->DestroyAlternateDesktop(); +} + +// Launches the app in the sandbox and share a handle with it. The app should +// be able to use the handle. +TEST(PolicyTargetTest, ShareHandleTest) { + BrokerServices* broker = GetBroker(); + ASSERT_TRUE(broker); + + base::StringPiece contents = "Hello World"; + base::WritableSharedMemoryRegion writable_region = + base::WritableSharedMemoryRegion::Create(contents.size()); + ASSERT_TRUE(writable_region.IsValid()); + base::WritableSharedMemoryMapping writable_mapping = writable_region.Map(); + ASSERT_TRUE(writable_mapping.IsValid()); + memcpy(writable_mapping.memory(), contents.data(), contents.size()); + + // Get the path to the sandboxed app. + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(nullptr, prog_name, MAX_PATH); + + base::ReadOnlySharedMemoryRegion read_only_region = + base::WritableSharedMemoryRegion::ConvertToReadOnly( + std::move(writable_region)); + ASSERT_TRUE(read_only_region.IsValid()); + + scoped_refptr<TargetPolicy> policy = broker->CreatePolicy(); + policy->AddHandleToShare(read_only_region.GetPlatformHandle()); + + std::wstring arguments(L"\""); + arguments += prog_name; + arguments += L"\" -child 0 shared_memory_handle "; + arguments += base::AsWString(base::NumberToString16( + base::win::HandleToUint32(read_only_region.GetPlatformHandle()))); + + // Launch the app. + ResultCode result = SBOX_ALL_OK; + ResultCode warning_result = SBOX_ALL_OK; + base::win::ScopedProcessInformation target; + + policy->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + PROCESS_INFORMATION temp_process_info = {}; + DWORD last_error = ERROR_SUCCESS; + result = + broker->SpawnTarget(prog_name, arguments.c_str(), policy, &warning_result, + &last_error, &temp_process_info); + policy = nullptr; + + EXPECT_EQ(SBOX_ALL_OK, result); + if (result == SBOX_ALL_OK) + target.Set(temp_process_info); + + EXPECT_EQ(1u, ::ResumeThread(target.thread_handle())); + + EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), + ::WaitForSingleObject(target.process_handle(), 2000)); + + EXPECT_TRUE(::TerminateProcess(target.process_handle(), 0)); + + ::WaitForSingleObject(target.process_handle(), INFINITE); +} + +// Dummy target that just reports that's it spawned correctly. +SBOX_TESTS_COMMAND int PolicyTargetTest_SetEffectiveToken(int argc, + wchar_t** argv) { + return SBOX_TEST_SUCCEEDED; +} + +// Test whether after using SetEffectiveToken spawning a target works as +// expected. +TEST(PolicyTargetTest, SetEffectiveToken) { + TestRunner runner; + HANDLE token; + + // Get current process token. + EXPECT_TRUE( + ::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)); + + // Setup token guard. + base::win::ScopedHandle token_guard(token); + + // Set token and run target. + runner.GetPolicy()->SetEffectiveToken(token_guard.Get()); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"PolicyTargetTest_SetEffectiveToken")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations.cc b/security/sandbox/chromium/sandbox/win/src/process_mitigations.cc new file mode 100644 index 0000000000..557296c93f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations.cc @@ -0,0 +1,622 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_mitigations.h" + +#include <stddef.h> +#include <windows.h> +#include <wow64apiset.h> + +#include <algorithm> + +#include "base/win/windows_version.h" +#include "build/build_config.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox_rand.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// API defined in libloaderapi.h >= Win8. +using SetDefaultDllDirectoriesFunction = decltype(&SetDefaultDllDirectories); + +// APIs defined in processthreadsapi.h >= Win8. +using SetProcessMitigationPolicyFunction = + decltype(&SetProcessMitigationPolicy); +using GetProcessMitigationPolicyFunction = + decltype(&GetProcessMitigationPolicy); +using SetThreadInformationFunction = decltype(&SetThreadInformation); + +// Returns a two-element array of mitigation flags supported on this machine. +// - This function is only useful on >= base::win::Version::WIN8. +const ULONG64* GetSupportedMitigations() { + static ULONG64 mitigations[2] = {}; + + // This static variable will only be initialized once. + if (!mitigations[0] && !mitigations[1]) { + GetProcessMitigationPolicyFunction get_process_mitigation_policy = + reinterpret_cast<GetProcessMitigationPolicyFunction>(::GetProcAddress( + ::GetModuleHandleA("kernel32.dll"), "GetProcessMitigationPolicy")); + if (get_process_mitigation_policy) { + // NOTE: the two-element-sized input array is only supported on >= Win10 + // RS2. + // If an earlier version, the second element will be left 0. + size_t mits_size = + (base::win::GetVersion() >= base::win::Version::WIN10_RS2) + ? (sizeof(mitigations[0]) * 2) + : sizeof(mitigations[0]); + if (!get_process_mitigation_policy(::GetCurrentProcess(), + ProcessMitigationOptionsMask, + &mitigations, mits_size)) { + NOTREACHED(); + } + } + } + + return &mitigations[0]; +} + +// Returns true if this is 32-bit Chrome running on ARM64 with emulation. +// Needed because ACG does not work with emulated code. +// See +// https://docs.microsoft.com/en-us/windows/uwp/porting/apps-on-arm-troubleshooting-x86. +// See https://crbug.com/977723. +// TODO(wfh): Move this code into base. See https://crbug.com/978257. +bool IsRunning32bitEmulatedOnArm64() { +#if defined(ARCH_CPU_X86) + using IsWow64Process2Function = decltype(&IsWow64Process2); + + IsWow64Process2Function is_wow64_process2 = + reinterpret_cast<IsWow64Process2Function>(::GetProcAddress( + ::GetModuleHandleA("kernel32.dll"), "IsWow64Process2")); + if (!is_wow64_process2) + return false; + USHORT process_machine; + USHORT native_machine; + bool retval = is_wow64_process2(::GetCurrentProcess(), &process_machine, + &native_machine); + if (!retval) + return false; + if (native_machine == IMAGE_FILE_MACHINE_ARM64) + return true; +#endif // defined(ARCH_CPU_X86) + return false; +} + +// Returns true if user-mode Hardware-enforced Stack Protection is available for +// the Win32 environment. +bool IsUserCetWin32Available() { + static bool cetAvailable = []() -> bool { + using IsUserCetAvailableInEnvironmentFunction = + decltype(&IsUserCetAvailableInEnvironment); + + IsUserCetAvailableInEnvironmentFunction is_user_cet_available = + reinterpret_cast<IsUserCetAvailableInEnvironmentFunction>( + ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), + "IsUserCetAvailableInEnvironment")); + if (!is_user_cet_available) { + return false; + } + + return is_user_cet_available(USER_CET_ENVIRONMENT_WIN32_PROCESS); + }(); + + return cetAvailable; +} + +} // namespace + +namespace sandbox { + +bool ApplyProcessMitigationsToCurrentProcess(MitigationFlags flags) { + if (!CanSetProcessMitigationsPostStartup(flags)) + return false; + + base::win::Version version = base::win::GetVersion(); + HMODULE module = ::GetModuleHandleA("kernel32.dll"); + + if (flags & MITIGATION_DLL_SEARCH_ORDER) { + SetDefaultDllDirectoriesFunction set_default_dll_directories = + reinterpret_cast<SetDefaultDllDirectoriesFunction>( + ::GetProcAddress(module, "SetDefaultDllDirectories")); + + // Check for SetDefaultDllDirectories since it requires KB2533623. + if (set_default_dll_directories) { + if (!set_default_dll_directories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + } + + // Set the heap to terminate on corruption + if (flags & MITIGATION_HEAP_TERMINATE) { + if (!::HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, + nullptr, 0) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (flags & MITIGATION_HARDEN_TOKEN_IL_POLICY) { + DWORD error = HardenProcessIntegrityLevelPolicy(); + if ((error != ERROR_SUCCESS) && (error != ERROR_ACCESS_DENIED)) + return false; + } + +#if !defined(_WIN64) // DEP is always enabled on 64-bit. + if (flags & MITIGATION_DEP) { + DWORD dep_flags = PROCESS_DEP_ENABLE; + + if (flags & MITIGATION_DEP_NO_ATL_THUNK) + dep_flags |= PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION; + + if (!::SetProcessDEPPolicy(dep_flags) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } +#endif + + // This is all we can do in Win7 and below. + if (version < base::win::Version::WIN8) + return true; + + SetProcessMitigationPolicyFunction set_process_mitigation_policy = + reinterpret_cast<SetProcessMitigationPolicyFunction>( + ::GetProcAddress(module, "SetProcessMitigationPolicy")); + if (!set_process_mitigation_policy) + return false; + + // Enable ASLR policies. + if (flags & MITIGATION_RELOCATE_IMAGE) { + PROCESS_MITIGATION_ASLR_POLICY policy = {}; + policy.EnableForceRelocateImages = true; + policy.DisallowStrippedImages = + (flags & MITIGATION_RELOCATE_IMAGE_REQUIRED) == + MITIGATION_RELOCATE_IMAGE_REQUIRED; + + if (!set_process_mitigation_policy(ProcessASLRPolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable strict handle policies. + if (flags & MITIGATION_STRICT_HANDLE_CHECKS) { + PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY policy = {}; + policy.HandleExceptionsPermanentlyEnabled = + policy.RaiseExceptionOnInvalidHandleReference = true; + + if (!set_process_mitigation_policy(ProcessStrictHandleCheckPolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable system call policies. + if (flags & MITIGATION_WIN32K_DISABLE) { + PROCESS_MITIGATION_SYSTEM_CALL_DISABLE_POLICY policy = {}; + policy.DisallowWin32kSystemCalls = true; + + if (!set_process_mitigation_policy(ProcessSystemCallDisablePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable extension point policies. + if (flags & MITIGATION_EXTENSION_POINT_DISABLE) { + PROCESS_MITIGATION_EXTENSION_POINT_DISABLE_POLICY policy = {}; + policy.DisableExtensionPoints = true; + + if (!set_process_mitigation_policy(ProcessExtensionPointDisablePolicy, + &policy, sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version < base::win::Version::WIN8_1) + return true; + + // Enable dynamic code policies. + if (!IsRunning32bitEmulatedOnArm64() && + (flags & MITIGATION_DYNAMIC_CODE_DISABLE)) { + // Verify caller is not accidentally setting both mutually exclusive + // policies. + DCHECK(!(flags & MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT)); + PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy = {}; + policy.ProhibitDynamicCode = true; + + if (!set_process_mitigation_policy(ProcessDynamicCodePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version < base::win::Version::WIN10) + return true; + + // Enable font policies. + if (flags & MITIGATION_NONSYSTEM_FONT_DISABLE) { + PROCESS_MITIGATION_FONT_DISABLE_POLICY policy = {}; + policy.DisableNonSystemFonts = true; + + if (!set_process_mitigation_policy(ProcessFontDisablePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version < base::win::Version::WIN10_TH2) + return true; + + // Enable binary signing policies. + if (flags & MITIGATION_FORCE_MS_SIGNED_BINS) { + PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = {}; + // Allow only MS signed binaries. + policy.MicrosoftSignedOnly = true; + // NOTE: there are two other flags available to allow + // 1) Only Windows Store signed. + // 2) MS-signed, Win Store signed, and WHQL signed binaries. + // Support not added at the moment. + if (!set_process_mitigation_policy(ProcessSignaturePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + // Enable image load policies. + if (flags & MITIGATION_IMAGE_LOAD_NO_REMOTE || + flags & MITIGATION_IMAGE_LOAD_NO_LOW_LABEL || + flags & MITIGATION_IMAGE_LOAD_PREFER_SYS32) { + PROCESS_MITIGATION_IMAGE_LOAD_POLICY policy = {}; + if (flags & MITIGATION_IMAGE_LOAD_NO_REMOTE) + policy.NoRemoteImages = true; + if (flags & MITIGATION_IMAGE_LOAD_NO_LOW_LABEL) + policy.NoLowMandatoryLabelImages = true; + // PreferSystem32 is only supported on >= Anniversary. + if (version >= base::win::Version::WIN10_RS1 && + flags & MITIGATION_IMAGE_LOAD_PREFER_SYS32) { + policy.PreferSystem32Images = true; + } + + if (!set_process_mitigation_policy(ProcessImageLoadPolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + if (version < base::win::Version::WIN10_RS1) + return true; + + // Enable dynamic code policies. + // Per-thread opt-out is only supported on >= Anniversary (RS1). + if (!IsRunning32bitEmulatedOnArm64() && + (flags & MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT)) { + // Verify caller is not accidentally setting both mutually exclusive + // policies. + DCHECK(!(flags & MITIGATION_DYNAMIC_CODE_DISABLE)); + PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy = {}; + policy.ProhibitDynamicCode = true; + policy.AllowThreadOptOut = true; + + if (!set_process_mitigation_policy(ProcessDynamicCodePolicy, &policy, + sizeof(policy)) && + ERROR_ACCESS_DENIED != ::GetLastError()) { + return false; + } + } + + return true; +} + +bool ApplyMitigationsToCurrentThread(MitigationFlags flags) { + if (!CanSetMitigationsPerThread(flags)) + return false; + + base::win::Version version = base::win::GetVersion(); + + if (version < base::win::Version::WIN10_RS1) + return true; + + // Enable dynamic code per-thread policies. + if (flags & MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD) { + DWORD thread_policy = THREAD_DYNAMIC_CODE_ALLOW; + + // NOTE: SetThreadInformation API only exists on >= Win8. Dynamically + // get function handle. + SetThreadInformationFunction set_thread_info_function = + reinterpret_cast<SetThreadInformationFunction>(::GetProcAddress( + ::GetModuleHandleA("kernel32.dll"), "SetThreadInformation")); + if (!set_thread_info_function) + return false; + + // NOTE: Must use the pseudo-handle here, a thread HANDLE won't work. + if (!set_thread_info_function(::GetCurrentThread(), ThreadDynamicCodePolicy, + &thread_policy, sizeof(thread_policy))) { + return false; + } + } + + return true; +} + +void ConvertProcessMitigationsToPolicy(MitigationFlags flags, + DWORD64* policy_flags, + size_t* size) { + base::win::Version version = base::win::GetVersion(); + + // |policy_flags| is a two-element array of DWORD64s. Ensure mitigation flags + // from PROCESS_CREATION_MITIGATION_POLICY2_* go into the second value. If + // any flags are set in value 2, update |size| to include both elements. + DWORD64* policy_value_1 = &policy_flags[0]; + DWORD64* policy_value_2 = &policy_flags[1]; + *policy_value_1 = 0; + *policy_value_2 = 0; + +#if defined(_WIN64) + *size = sizeof(*policy_flags); +#elif defined(_M_IX86) + // A 64-bit flags attribute is illegal on 32-bit Win 7. + if (version < base::win::Version::WIN8) + *size = sizeof(DWORD); + else + *size = sizeof(*policy_flags); +#else +#error This platform is not supported. +#endif + +// DEP and SEHOP are not valid for 64-bit Windows +#if !defined(_WIN64) + if (flags & MITIGATION_DEP) { + *policy_value_1 |= PROCESS_CREATION_MITIGATION_POLICY_DEP_ENABLE; + if (!(flags & MITIGATION_DEP_NO_ATL_THUNK)) + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_DEP_ATL_THUNK_ENABLE; + } + + if (flags & MITIGATION_SEHOP) + *policy_value_1 |= PROCESS_CREATION_MITIGATION_POLICY_SEHOP_ENABLE; +#endif + + // Win 7 + if (version < base::win::Version::WIN8) + return; + + // Everything >= Win8, do not return before the end of the function where + // the final policy bitmap is sanity checked against what is supported on this + // machine. The API required to do so is only available since Win8. + + // Mitigations >= Win8: + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN8) { + if (flags & MITIGATION_RELOCATE_IMAGE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON; + if (flags & MITIGATION_RELOCATE_IMAGE_REQUIRED) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS; + } + } + + if (flags & MITIGATION_HEAP_TERMINATE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON; + } + + if (flags & MITIGATION_BOTTOM_UP_ASLR) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON; + } + + if (flags & MITIGATION_HIGH_ENTROPY_ASLR) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON; + } + + if (flags & MITIGATION_STRICT_HANDLE_CHECKS) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON; + } + + if (flags & MITIGATION_WIN32K_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON; + } + + if (flags & MITIGATION_EXTENSION_POINT_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON; + } + } + + // Mitigations >= Win8.1: + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN8_1) { + if (flags & MITIGATION_DYNAMIC_CODE_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON; + } + + if (flags & MITIGATION_CONTROL_FLOW_GUARD_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF; + } + } + + // Mitigations >= Win10: + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10) { + if (flags & MITIGATION_NONSYSTEM_FONT_DISABLE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_FONT_DISABLE_ALWAYS_ON; + } + } + + // Mitigations >= Win10 TH2: + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10_TH2) { + if (flags & MITIGATION_FORCE_MS_SIGNED_BINS) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON; + } + + if (flags & MITIGATION_IMAGE_LOAD_NO_REMOTE) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_REMOTE_ALWAYS_ON; + } + + if (flags & MITIGATION_IMAGE_LOAD_NO_LOW_LABEL) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_LOW_LABEL_ALWAYS_ON; + } + } + + // Mitigations >= Win10 RS1 ("Anniversary"): + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10_RS1) { + if (flags & MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON_ALLOW_OPT_OUT; + } + + if (flags & MITIGATION_IMAGE_LOAD_PREFER_SYS32) { + *policy_value_1 |= + PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON; + } + } + + // Mitigations >= Win10 RS3 ("Fall Creator's"): + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10_RS3) { + // Note: This mitigation requires not only Win10 1709, but also the January + // 2018 security updates and any applicable firmware updates from the + // OEM device manufacturer. + // Note: Applying this mitigation attribute on creation will succeed, even + // if + // the underlying hardware does not support the implementation. + // Windows just does its best under the hood for the given hardware. + if (flags & MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION) { + *policy_value_2 |= + PROCESS_CREATION_MITIGATION_POLICY2_RESTRICT_INDIRECT_BRANCH_PREDICTION_ALWAYS_ON; + } + } + + // Mitigations >= Win10 20H1 + //---------------------------------------------------------------------------- + if (version >= base::win::Version::WIN10_20H1) { + if (flags & MITIGATION_CET_COMPAT_MODE && IsUserCetWin32Available()) { + *policy_value_2 |= + PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_ON; + } + } + + // When done setting policy flags, sanity check supported policies on this + // machine, and then update |size|. + + const ULONG64* supported = GetSupportedMitigations(); + + *policy_value_1 = *policy_value_1 & supported[0]; + *policy_value_2 = *policy_value_2 & supported[1]; + + // Only include the second element in |size| if it is non-zero. Else, + // UpdateProcThreadAttribute() will return a failure when setting policies. + if (*policy_value_2 && version >= base::win::Version::WIN10_RS2) { + *size = sizeof(*policy_flags) * 2; + } + + return; +} + +MitigationFlags FilterPostStartupProcessMitigations(MitigationFlags flags) { + base::win::Version version = base::win::GetVersion(); + + // Windows 7. + if (version < base::win::Version::WIN8) { + return flags & (MITIGATION_BOTTOM_UP_ASLR | MITIGATION_DLL_SEARCH_ORDER | + MITIGATION_HEAP_TERMINATE); + } + + // Windows 8 and above. + return flags & (MITIGATION_BOTTOM_UP_ASLR | MITIGATION_DLL_SEARCH_ORDER); +} + +bool ApplyProcessMitigationsToSuspendedProcess(HANDLE process, + MitigationFlags flags) { +// This is a hack to fake a weak bottom-up ASLR on 32-bit Windows. +#if !defined(_WIN64) + if (flags & MITIGATION_BOTTOM_UP_ASLR) { + unsigned int limit; + GetRandom(&limit); + char* ptr = 0; + const size_t kMask64k = 0xFFFF; + // Random range (512k-16.5mb) in 64k steps. + const char* end = ptr + ((((limit % 16384) + 512) * 1024) & ~kMask64k); + while (ptr < end) { + MEMORY_BASIC_INFORMATION memory_info; + if (!::VirtualQueryEx(process, ptr, &memory_info, sizeof(memory_info))) + break; + size_t size = std::min((memory_info.RegionSize + kMask64k) & ~kMask64k, + static_cast<SIZE_T>(end - ptr)); + if (ptr && memory_info.State == MEM_FREE) + ::VirtualAllocEx(process, ptr, size, MEM_RESERVE, PAGE_NOACCESS); + ptr += size; + } + } +#endif + + return true; +} + +MitigationFlags GetAllowedPostStartupProcessMitigations() { + return MITIGATION_HEAP_TERMINATE | + MITIGATION_DEP | + MITIGATION_DEP_NO_ATL_THUNK | + MITIGATION_RELOCATE_IMAGE | + MITIGATION_RELOCATE_IMAGE_REQUIRED | + MITIGATION_BOTTOM_UP_ASLR | + MITIGATION_STRICT_HANDLE_CHECKS | + MITIGATION_EXTENSION_POINT_DISABLE | + MITIGATION_DLL_SEARCH_ORDER | + MITIGATION_HARDEN_TOKEN_IL_POLICY | + MITIGATION_WIN32K_DISABLE | + MITIGATION_DYNAMIC_CODE_DISABLE | + MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT | + MITIGATION_FORCE_MS_SIGNED_BINS | + MITIGATION_NONSYSTEM_FONT_DISABLE | + MITIGATION_IMAGE_LOAD_NO_REMOTE | + MITIGATION_IMAGE_LOAD_NO_LOW_LABEL | + MITIGATION_IMAGE_LOAD_PREFER_SYS32; +} + +bool CanSetProcessMitigationsPostStartup(MitigationFlags flags) { + // All of these mitigations can be enabled after startup. + return !(flags & ~GetAllowedPostStartupProcessMitigations()); +} + +bool CanSetProcessMitigationsPreStartup(MitigationFlags flags) { + // These mitigations cannot be enabled prior to startup. + return !(flags & + (MITIGATION_STRICT_HANDLE_CHECKS | MITIGATION_DLL_SEARCH_ORDER)); +} + +bool CanSetMitigationsPerThread(MitigationFlags flags) { + // If any flags EXCEPT these are set, fail. + if (flags & ~(MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD)) + return false; + + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations.h b/security/sandbox/chromium/sandbox/win/src/process_mitigations.h new file mode 100644 index 0000000000..26dc42e358 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ +#define SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ + +#include <windows.h> + +#include <stddef.h> + +#include "sandbox/win/src/security_level.h" + +namespace sandbox { + +// Sets the mitigation policy for the current process, ignoring any settings +// that are invalid for the current version of Windows. +bool ApplyProcessMitigationsToCurrentProcess(MitigationFlags flags); + +// Sets the mitigation policy for the current thread, ignoring any settings +// that are invalid for the current version of Windows. +bool ApplyMitigationsToCurrentThread(MitigationFlags flags); + +// Returns the flags that must be enforced after startup for the current OS +// version. +MitigationFlags FilterPostStartupProcessMitigations(MitigationFlags flags); + +// Converts sandbox flags to the PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES +// policy flags used by UpdateProcThreadAttribute(). +// - |policy_flags| must be a two-element DWORD64 array. +// - |size| is a size_t so that it can be passed directly into +// UpdateProcThreadAttribute(). +void ConvertProcessMitigationsToPolicy(MitigationFlags flags, + DWORD64* policy_flags, + size_t* size); + +// Adds mitigations that need to be performed on the suspended target process +// before execution begins. +bool ApplyProcessMitigationsToSuspendedProcess(HANDLE process, + MitigationFlags flags); + +// Returns the list of process mitigations which can be enabled post startup. +MitigationFlags GetAllowedPostStartupProcessMitigations(); + +// Returns true if all the supplied flags can be set after a process starts. +bool CanSetProcessMitigationsPostStartup(MitigationFlags flags); + +// Returns true if all the supplied flags can be set before a process starts. +bool CanSetProcessMitigationsPreStartup(MitigationFlags flags); + +// Returns true if all the supplied flags can be set on the current thread. +bool CanSetMitigationsPerThread(MitigationFlags flags); + +} // namespace sandbox + +#endif // SANDBOX_SRC_WIN_PROCESS_MITIGATIONS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc new file mode 100644 index 0000000000..4d2d94b865 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.cc @@ -0,0 +1,592 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_mitigations_win32k_dispatcher.h" + +#include <algorithm> +#include <string> + +#include "base/memory/platform_shared_memory_region.h" +#include "base/memory/unsafe_shared_memory_region.h" +#include "base/unguessable_token.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/process_mitigations_win32k_interception.h" +#include "sandbox/win/src/process_mitigations_win32k_policy.h" + +namespace sandbox { + +namespace { + +base::UnsafeSharedMemoryRegion GetSharedMemoryRegion( + const ClientInfo& client_info, + HANDLE handle, + size_t size) { + HANDLE dup_handle = nullptr; + intptr_t handle_int = reinterpret_cast<intptr_t>(handle); + if (handle_int <= 0 || + !::DuplicateHandle(client_info.process, handle, ::GetCurrentProcess(), + &dup_handle, 0, false, DUPLICATE_SAME_ACCESS)) { + return {}; + } + // The raw handle returned from ::DuplicateHandle() must be wrapped in a + // base::PlatformSharedMemoryRegion. + base::subtle::PlatformSharedMemoryRegion platform_region = + base::subtle::PlatformSharedMemoryRegion::Take( + base::win::ScopedHandle(dup_handle), + base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe, size, + base::UnguessableToken::Create()); + // |platform_region| can now be wrapped in a base::UnsafeSharedMemoryRegion. + return base::UnsafeSharedMemoryRegion::Deserialize( + std::move(platform_region)); +} + +} // namespace + +ProtectedVideoOutput::~ProtectedVideoOutput() { + ProcessMitigationsWin32KLockdownPolicy::DestroyOPMProtectedOutputAction( + handle_); +} + +scoped_refptr<ProtectedVideoOutput> +ProcessMitigationsWin32KDispatcher::GetProtectedVideoOutput( + HANDLE handle, + bool destroy_output) { + base::AutoLock lock(protected_outputs_lock_); + scoped_refptr<ProtectedVideoOutput> result; + auto it = protected_outputs_.find(handle); + if (it != protected_outputs_.end()) { + result = it->second; + if (destroy_output) + protected_outputs_.erase(it); + } + return result; +} + +ProcessMitigationsWin32KDispatcher::ProcessMitigationsWin32KDispatcher( + PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall enum_display_monitors_params = { + {IpcTag::USER_ENUMDISPLAYMONITORS, {INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::EnumDisplayMonitors)}; + static const IPCCall get_monitor_info_params = { + {IpcTag::USER_GETMONITORINFO, {VOIDPTR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetMonitorInfo)}; + static const IPCCall get_suggested_output_size_params = { + {IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE, {WCHAR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher:: + GetSuggestedOPMProtectedOutputArraySize)}; + static const IPCCall create_protected_outputs_params = { + {IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS, {WCHAR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::CreateOPMProtectedOutputs)}; + static const IPCCall get_cert_size_params = { + {IpcTag::GDI_GETCERTIFICATESIZE, {WCHAR_TYPE, VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetCertificateSize)}; + static const IPCCall get_cert_params = { + {IpcTag::GDI_GETCERTIFICATE, + {WCHAR_TYPE, VOIDPTR_TYPE, VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetCertificate)}; + static const IPCCall destroy_protected_output_params = { + {IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT, {VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::DestroyOPMProtectedOutput)}; + static const IPCCall get_random_number_params = { + {IpcTag::GDI_GETOPMRANDOMNUMBER, {VOIDPTR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetOPMRandomNumber)}; + static const IPCCall set_signing_key_params = { + {IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS, + {VOIDPTR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher:: + SetOPMSigningKeyAndSequenceNumbers)}; + static const IPCCall configure_protected_output_params = { + {IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT, {VOIDPTR_TYPE, VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::ConfigureOPMProtectedOutput)}; + static const IPCCall get_information_params = { + {IpcTag::GDI_GETOPMINFORMATION, {VOIDPTR_TYPE, VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ProcessMitigationsWin32KDispatcher::GetOPMInformation)}; + + ipc_calls_.push_back(enum_display_monitors_params); + ipc_calls_.push_back(get_monitor_info_params); + ipc_calls_.push_back(get_suggested_output_size_params); + ipc_calls_.push_back(create_protected_outputs_params); + ipc_calls_.push_back(get_cert_size_params); + ipc_calls_.push_back(get_cert_params); + ipc_calls_.push_back(destroy_protected_output_params); + ipc_calls_.push_back(get_random_number_params); + ipc_calls_.push_back(set_signing_key_params); + ipc_calls_.push_back(configure_protected_output_params); + ipc_calls_.push_back(get_information_params); +} + +ProcessMitigationsWin32KDispatcher::~ProcessMitigationsWin32KDispatcher() {} + +bool ProcessMitigationsWin32KDispatcher::SetupService( + InterceptionManager* manager, + IpcTag service) { + if (!(policy_base_->GetProcessMitigations() & + sandbox::MITIGATION_WIN32K_DISABLE)) { + return false; + } + + switch (service) { + case IpcTag::GDI_GDIDLLINITIALIZE: { + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GdiDllInitialize, + GDIINITIALIZE_ID, 12)) { + return false; + } + return true; + } + + case IpcTag::GDI_GETSTOCKOBJECT: { + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetStockObject, + GETSTOCKOBJECT_ID, 8)) { + return false; + } + return true; + } + + case IpcTag::USER_REGISTERCLASSW: { + if (!INTERCEPT_EAT(manager, L"user32.dll", RegisterClassW, + REGISTERCLASSW_ID, 8)) { + return false; + } + return true; + } + + case IpcTag::USER_ENUMDISPLAYMONITORS: { + if (!INTERCEPT_EAT(manager, L"user32.dll", EnumDisplayMonitors, + ENUMDISPLAYMONITORS_ID, 20)) { + return false; + } + return true; + } + + case IpcTag::USER_ENUMDISPLAYDEVICES: { + if (!INTERCEPT_EAT(manager, L"user32.dll", EnumDisplayDevicesA, + ENUMDISPLAYDEVICESA_ID, 20)) { + return false; + } + return true; + } + + case IpcTag::USER_GETMONITORINFO: { + if (!INTERCEPT_EAT(manager, L"user32.dll", GetMonitorInfoA, + GETMONITORINFOA_ID, 12)) { + return false; + } + + if (!INTERCEPT_EAT(manager, L"user32.dll", GetMonitorInfoW, + GETMONITORINFOW_ID, 12)) { + return false; + } + return true; + } + + case IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", CreateOPMProtectedOutputs, + CREATEOPMPROTECTEDOUTPUTS_ID, 24)) { + return false; + } + return true; + + case IpcTag::GDI_GETCERTIFICATE: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetCertificate, + GETCERTIFICATE_ID, 20)) { + return false; + } + if (base::win::GetVersion() < base::win::Version::WIN10_TH2) + return true; + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetCertificateByHandle, + GETCERTIFICATEBYHANDLE_ID, 20)) { + return false; + } + return true; + + case IpcTag::GDI_GETCERTIFICATESIZE: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetCertificateSize, + GETCERTIFICATESIZE_ID, 16)) { + return false; + } + if (base::win::GetVersion() < base::win::Version::WIN10_TH2) + return true; + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetCertificateSizeByHandle, + GETCERTIFICATESIZEBYHANDLE_ID, 16)) { + return false; + } + return true; + + case IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", DestroyOPMProtectedOutput, + DESTROYOPMPROTECTEDOUTPUT_ID, 8)) { + return false; + } + return true; + + case IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", ConfigureOPMProtectedOutput, + CONFIGUREOPMPROTECTEDOUTPUT_ID, 20)) { + return false; + } + return true; + + case IpcTag::GDI_GETOPMINFORMATION: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetOPMInformation, + GETOPMINFORMATION_ID, 16)) { + return false; + } + return true; + + case IpcTag::GDI_GETOPMRANDOMNUMBER: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", GetOPMRandomNumber, + GETOPMRANDOMNUMBER_ID, 12)) { + return false; + } + return true; + + case IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", + GetSuggestedOPMProtectedOutputArraySize, + GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE_ID, 12)) { + return false; + } + return true; + + case IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS: + if (!INTERCEPT_EAT(manager, L"gdi32.dll", + SetOPMSigningKeyAndSequenceNumbers, + SETOPMSIGNINGKEYANDSEQUENCENUMBERS_ID, 12)) { + return false; + } + return true; + + default: + break; + } + return false; +} + +bool ProcessMitigationsWin32KDispatcher::EnumDisplayMonitors( + IPCInfo* ipc, + CountedBuffer* buffer) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.win32_result = ERROR_ACCESS_DENIED; + return true; + } + + if (buffer->Size() != sizeof(EnumMonitorsResult)) { + ipc->return_info.win32_result = ERROR_INVALID_PARAMETER; + return true; + } + HMONITOR monitor_list[kMaxEnumMonitors] = {}; + + uint32_t monitor_list_count = + ProcessMitigationsWin32KLockdownPolicy::EnumDisplayMonitorsAction( + *ipc->client_info, monitor_list, kMaxEnumMonitors); + DCHECK(monitor_list_count <= kMaxEnumMonitors); + + EnumMonitorsResult* result = + static_cast<EnumMonitorsResult*>(buffer->Buffer()); + for (uint32_t monitor_pos = 0; monitor_pos < monitor_list_count; + ++monitor_pos) { + result->monitors[monitor_pos] = monitor_list[monitor_pos]; + } + result->monitor_count = monitor_list_count; + ipc->return_info.win32_result = 0; + + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetMonitorInfo(IPCInfo* ipc, + void* monitor, + CountedBuffer* buffer) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.win32_result = ERROR_ACCESS_DENIED; + return true; + } + if (buffer->Size() != sizeof(MONITORINFOEXW)) { + ipc->return_info.win32_result = ERROR_INVALID_PARAMETER; + return true; + } + MONITORINFO* monitor_info = static_cast<MONITORINFO*>(buffer->Buffer()); + // Ensure size is valid and represents what we've been passed. + monitor_info->cbSize = buffer->Size(); + HMONITOR monitor_handle = static_cast<HMONITOR>(monitor); + bool success = ProcessMitigationsWin32KLockdownPolicy::GetMonitorInfoAction( + *ipc->client_info, monitor_handle, monitor_info); + ipc->return_info.win32_result = + success ? ERROR_SUCCESS : ERROR_INVALID_PARAMETER; + return true; +} + +bool ProcessMitigationsWin32KDispatcher:: + GetSuggestedOPMProtectedOutputArraySize(IPCInfo* ipc, + std::wstring* device_name) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + NTSTATUS status = ProcessMitigationsWin32KLockdownPolicy:: + GetSuggestedOPMProtectedOutputArraySizeAction( + *ipc->client_info, *device_name, + &ipc->return_info.extended[0].unsigned_int); + if (!status) { + ipc->return_info.extended_count = 1; + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::CreateOPMProtectedOutputs( + IPCInfo* ipc, + std::wstring* device_name, + CountedBuffer* protected_outputs) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + uint32_t output_array_size = 0; + uint32_t input_array_size = protected_outputs->Size() / sizeof(HANDLE); + HANDLE* handles = static_cast<HANDLE*>(protected_outputs->Buffer()); + NTSTATUS status = + ProcessMitigationsWin32KLockdownPolicy::CreateOPMProtectedOutputsAction( + *ipc->client_info, *device_name, handles, input_array_size, + &output_array_size); + if (!status && (output_array_size <= input_array_size)) { + base::AutoLock lock(protected_outputs_lock_); + ipc->return_info.extended_count = 1; + ipc->return_info.extended[0].unsigned_int = output_array_size; + for (uint32_t handle_pos = 0; handle_pos < output_array_size; + handle_pos++) { + HANDLE handle = handles[handle_pos]; + protected_outputs_[handle] = new ProtectedVideoOutput(handle); + } + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetCertificateSize( + IPCInfo* ipc, + std::wstring* device_name, + void* protected_output) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + NTSTATUS status = STATUS_INVALID_PARAMETER; + if (device_name->size() > 0) { + status = ProcessMitigationsWin32KLockdownPolicy::GetCertificateSizeAction( + *ipc->client_info, *device_name, + &ipc->return_info.extended[0].unsigned_int); + } else { + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + if (output) { + status = ProcessMitigationsWin32KLockdownPolicy:: + GetCertificateSizeByHandleAction( + *ipc->client_info, output.get()->handle(), + &ipc->return_info.extended[0].unsigned_int); + } + } + if (!status) { + ipc->return_info.extended_count = 1; + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetCertificate( + IPCInfo* ipc, + std::wstring* device_name, + void* protected_output, + void* shared_buffer_handle, + uint32_t shared_buffer_size) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + // Don't let caller map an arbitrarily large buffer into memory. + if (shared_buffer_size > kProtectedVideoOutputSectionSize) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + base::UnsafeSharedMemoryRegion region = GetSharedMemoryRegion( + *ipc->client_info, shared_buffer_handle, shared_buffer_size); + if (!region.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + base::WritableSharedMemoryMapping cert_data = region.Map(); + if (!cert_data.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + NTSTATUS status = STATUS_INVALID_PARAMETER; + if (device_name->size() > 0) { + status = ProcessMitigationsWin32KLockdownPolicy::GetCertificateAction( + *ipc->client_info, *device_name, + cert_data.GetMemoryAsSpan<BYTE>().data(), shared_buffer_size); + } else { + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + if (output) { + status = + ProcessMitigationsWin32KLockdownPolicy::GetCertificateByHandleAction( + *ipc->client_info, output.get()->handle(), + cert_data.GetMemoryAsSpan<BYTE>().data(), shared_buffer_size); + } + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::DestroyOPMProtectedOutput( + IPCInfo* ipc, + void* protected_output) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, true); + NTSTATUS status = STATUS_INVALID_HANDLE; + if (output) + status = STATUS_SUCCESS; + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetOPMRandomNumber( + IPCInfo* ipc, + void* protected_output, + CountedBuffer* random_number) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + NTSTATUS status = STATUS_INVALID_PARAMETER; + if (!output || random_number->Size() != sizeof(DXGKMDT_OPM_RANDOM_NUMBER)) { + status = STATUS_INVALID_PARAMETER; + } else { + status = ProcessMitigationsWin32KLockdownPolicy::GetOPMRandomNumberAction( + *ipc->client_info, output.get()->handle(), random_number->Buffer()); + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::SetOPMSigningKeyAndSequenceNumbers( + IPCInfo* ipc, + void* protected_output, + CountedBuffer* parameters) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + NTSTATUS status = STATUS_INVALID_PARAMETER; + if (!output || + parameters->Size() != sizeof(DXGKMDT_OPM_ENCRYPTED_PARAMETERS)) { + status = STATUS_INVALID_PARAMETER; + } else { + status = ProcessMitigationsWin32KLockdownPolicy:: + SetOPMSigningKeyAndSequenceNumbersAction( + *ipc->client_info, output.get()->handle(), parameters->Buffer()); + } + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::ConfigureOPMProtectedOutput( + IPCInfo* ipc, + void* protected_output, + void* shared_buffer_handle) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + if (!output) { + ipc->return_info.nt_status = STATUS_INVALID_HANDLE; + return true; + }; + base::UnsafeSharedMemoryRegion region = + GetSharedMemoryRegion(*ipc->client_info, shared_buffer_handle, + sizeof(DXGKMDT_OPM_CONFIGURE_PARAMETERS)); + if (!region.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + base::WritableSharedMemoryMapping buffer = region.Map(); + if (!buffer.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + NTSTATUS status = + ProcessMitigationsWin32KLockdownPolicy::ConfigureOPMProtectedOutputAction( + *ipc->client_info, output.get()->handle(), buffer.memory()); + ipc->return_info.nt_status = status; + return true; +} + +bool ProcessMitigationsWin32KDispatcher::GetOPMInformation( + IPCInfo* ipc, + void* protected_output, + void* shared_buffer_handle) { + if (!policy_base_->GetEnableOPMRedirection()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + scoped_refptr<ProtectedVideoOutput> output = + GetProtectedVideoOutput(protected_output, false); + if (!output) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + size_t shared_buffer_size = + std::max(sizeof(DXGKMDT_OPM_GET_INFO_PARAMETERS), + sizeof(DXGKMDT_OPM_REQUESTED_INFORMATION)); + + base::UnsafeSharedMemoryRegion region = GetSharedMemoryRegion( + *ipc->client_info, shared_buffer_handle, shared_buffer_size); + if (!region.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + base::WritableSharedMemoryMapping buffer = region.Map(); + if (!buffer.IsValid()) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + DXGKMDT_OPM_REQUESTED_INFORMATION requested_info = {}; + NTSTATUS status = + ProcessMitigationsWin32KLockdownPolicy::GetOPMInformationAction( + *ipc->client_info, output.get()->handle(), buffer.memory(), + &requested_info); + if (!status) { + memcpy(buffer.memory(), &requested_info, + sizeof(DXGKMDT_OPM_REQUESTED_INFORMATION)); + } + ipc->return_info.nt_status = status; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h new file mode 100644 index 0000000000..0714191fcf --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_dispatcher.h @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ + +#include <map> +#include <string> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// Class to maintain a reference to a OPM protected output handle. +class ProtectedVideoOutput + : public base::RefCountedThreadSafe<ProtectedVideoOutput> { + public: + ProtectedVideoOutput(HANDLE handle) : handle_(handle) {} + HANDLE handle() { return handle_; } + + private: + friend class base::RefCountedThreadSafe<ProtectedVideoOutput>; + ~ProtectedVideoOutput(); + + HANDLE handle_; + + DISALLOW_COPY_AND_ASSIGN(ProtectedVideoOutput); +}; + +// This class sets up intercepts for the Win32K lockdown policy which is set +// on Windows 8 and beyond. +class ProcessMitigationsWin32KDispatcher : public Dispatcher { + public: + explicit ProcessMitigationsWin32KDispatcher(PolicyBase* policy_base); + ~ProcessMitigationsWin32KDispatcher() override; + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + bool EnumDisplayMonitors(IPCInfo* ipc, CountedBuffer* buffer); + bool GetMonitorInfo(IPCInfo* ipc, void* monitor, CountedBuffer* buffer); + bool GetSuggestedOPMProtectedOutputArraySize(IPCInfo* ipc, + std::wstring* device_name); + bool CreateOPMProtectedOutputs(IPCInfo* ipc, + std::wstring* device_name, + CountedBuffer* protected_outputs); + bool GetCertificateSize(IPCInfo* ipc, + std::wstring* device_name, + void* protected_output); + bool GetCertificate(IPCInfo* ipc, + std::wstring* device_name, + void* protected_output, + void* shared_buffer_handle, + uint32_t shared_buffer_size); + bool DestroyOPMProtectedOutput(IPCInfo* ipc, void* protected_output); + bool GetOPMRandomNumber(IPCInfo* ipc, + void* protected_output, + CountedBuffer* random_number); + bool SetOPMSigningKeyAndSequenceNumbers(IPCInfo* ipc, + void* protected_output, + CountedBuffer* parameters); + bool ConfigureOPMProtectedOutput(IPCInfo* ipc, + void* protected_output, + void* shared_buffer_handle); + bool GetOPMInformation(IPCInfo* ipc, + void* protected_output, + void* shared_buffer_handle); + + private: + scoped_refptr<ProtectedVideoOutput> GetProtectedVideoOutput( + HANDLE handle, + bool destroy_output); + + PolicyBase* policy_base_; + std::map<HANDLE, scoped_refptr<ProtectedVideoOutput>> protected_outputs_; + base::Lock protected_outputs_lock_; + + DISALLOW_COPY_AND_ASSIGN(ProcessMitigationsWin32KDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc new file mode 100644 index 0000000000..1a16d85adf --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.cc @@ -0,0 +1,523 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_mitigations_win32k_interception.h" + +#include <algorithm> + +#include "base/numerics/safe_conversions.h" +#include "base/numerics/safe_math.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +namespace { + +// Implement a simple shared memory class as we can't use the base one. +class ScopedSharedMemory { + public: + ScopedSharedMemory(uint32_t size) : memory_(nullptr) { + handle_.Set(::CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, + PAGE_READWRITE | SEC_COMMIT, 0, size, + nullptr)); + if (handle_.IsValid()) { + memory_ = ::MapViewOfFile(handle_.Get(), FILE_MAP_READ | FILE_MAP_WRITE, + 0, 0, size); + } + } + ~ScopedSharedMemory() { + if (memory_) + ::UnmapViewOfFile(memory_); + } + + void* handle() { return handle_.Get(); } + void* memory() { return memory_; } + bool IsValid() { return handle_.IsValid() && memory_; } + + private: + base::win::ScopedHandle handle_; + void* memory_; +}; + +void UnicodeStringToString(PUNICODE_STRING unicode_string, + std::wstring* result) { + *result = std::wstring( + unicode_string->Buffer, + unicode_string->Buffer + + (unicode_string->Length / sizeof(unicode_string->Buffer[0]))); +} + +bool CallMonitorInfo(HMONITOR monitor, MONITORINFOEXW* monitor_info_ptr) { + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return false; + + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return false; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + InOutCountedBuffer buffer(monitor_info_ptr, sizeof(*monitor_info_ptr)); + ResultCode code = CrossCall(ipc, IpcTag::USER_GETMONITORINFO, + static_cast<void*>(monitor), buffer, &answer); + + if (code != SBOX_ALL_OK) + return false; + + if (answer.win32_result != ERROR_SUCCESS) + return false; + + return true; +} + +} // namespace + +BOOL WINAPI +TargetGdiDllInitialize(GdiDllInitializeFunction orig_gdi_dll_initialize, + HANDLE dll, + DWORD reason) { + return true; +} + +HGDIOBJ WINAPI +TargetGetStockObject(GetStockObjectFunction orig_get_stock_object, int object) { + return nullptr; +} + +ATOM WINAPI +TargetRegisterClassW(RegisterClassWFunction orig_register_class_function, + const WNDCLASS* wnd_class) { + return true; +} + +BOOL WINAPI TargetEnumDisplayMonitors(EnumDisplayMonitorsFunction, + HDC hdc, + LPCRECT lprcClip, + MONITORENUMPROC lpfnEnum, + LPARAM dwData) { + if (!lpfnEnum || hdc || lprcClip) { + return false; + } + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return false; + + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return false; + + CrossCallReturn answer = {0}; + answer.nt_status = 0; + EnumMonitorsResult result = {}; + InOutCountedBuffer result_buffer(&result, sizeof(result)); + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::USER_ENUMDISPLAYMONITORS, result_buffer, &answer); + + if (code != SBOX_ALL_OK) + return false; + + if (answer.win32_result) + return false; + + if (result.monitor_count > kMaxEnumMonitors) + return false; + + for (uint32_t monitor_pos = 0; monitor_pos < result.monitor_count; + ++monitor_pos) { + bool continue_enum = + lpfnEnum(result.monitors[monitor_pos], nullptr, nullptr, dwData); + if (!continue_enum) + return false; + } + + return true; +} + +BOOL WINAPI TargetEnumDisplayDevicesA(EnumDisplayDevicesAFunction, + LPCSTR lpDevice, + DWORD iDevNum, + PDISPLAY_DEVICEA lpDisplayDevice, + DWORD dwFlags) { + return false; +} + +BOOL WINAPI TargetGetMonitorInfoA(GetMonitorInfoAFunction, + HMONITOR monitor, + MONITORINFO* monitor_info_ptr) { + if (!monitor_info_ptr) + return false; + DWORD size = monitor_info_ptr->cbSize; + if (size != sizeof(MONITORINFO) && size != sizeof(MONITORINFOEXA)) + return false; + MONITORINFOEXW monitor_info_tmp = {}; + monitor_info_tmp.cbSize = sizeof(monitor_info_tmp); + bool success = CallMonitorInfo(monitor, &monitor_info_tmp); + if (!success) + return false; + memcpy(monitor_info_ptr, &monitor_info_tmp, sizeof(*monitor_info_ptr)); + if (size == sizeof(MONITORINFOEXA)) { + MONITORINFOEXA* monitor_info_exa = + reinterpret_cast<MONITORINFOEXA*>(monitor_info_ptr); + if (!::WideCharToMultiByte(CP_ACP, 0, monitor_info_tmp.szDevice, -1, + monitor_info_exa->szDevice, + sizeof(monitor_info_exa->szDevice), nullptr, + nullptr)) { + return false; + } + } + return true; +} + +BOOL WINAPI TargetGetMonitorInfoW(GetMonitorInfoWFunction, + HMONITOR monitor, + LPMONITORINFO monitor_info_ptr) { + if (!monitor_info_ptr) + return false; + DWORD size = monitor_info_ptr->cbSize; + if (size != sizeof(MONITORINFO) && size != sizeof(MONITORINFOEXW)) + return false; + MONITORINFOEXW monitor_info_tmp = {}; + monitor_info_tmp.cbSize = sizeof(monitor_info_tmp); + if (!CallMonitorInfo(monitor, &monitor_info_tmp)) + return false; + memcpy(monitor_info_ptr, &monitor_info_tmp, size); + return true; +} + +static NTSTATUS GetCertificateCommon( + PUNICODE_STRING device_name, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_size) { + // Don't support arbitrarily large certificate buffers. + if (certificate_size > kProtectedVideoOutputSectionSize) + return STATUS_INVALID_PARAMETER; + if (certificate_type != DXGKMDT_OPM_CERTIFICATE) + return STATUS_INVALID_PARAMETER; + if (device_name && device_name->Length == 0) + return STATUS_INVALID_PARAMETER; + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + ScopedSharedMemory buffer(certificate_size); + if (!buffer.IsValid()) + return STATUS_INVALID_PARAMETER; + std::wstring device_name_str; + void* protected_output_handle = nullptr; + if (device_name) { + if (device_name->Length == 0) + return STATUS_INVALID_PARAMETER; + UnicodeStringToString(device_name, &device_name_str); + } else { + protected_output_handle = protected_output; + } + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETCERTIFICATE, device_name_str.c_str(), + protected_output_handle, buffer.handle(), + static_cast<uint32_t>(certificate_size), &answer); + + if (code != SBOX_ALL_OK) { + return STATUS_ACCESS_DENIED; + } + + if (!answer.nt_status) + memcpy(certificate, buffer.memory(), certificate_size); + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetGetCertificate(GetCertificateFunction, + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_size) { + return GetCertificateCommon(device_name, nullptr, certificate_type, + certificate, certificate_size); +} + +static NTSTATUS GetCertificateSizeCommon( + PUNICODE_STRING device_name, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + if (certificate_type != DXGKMDT_OPM_CERTIFICATE) + return STATUS_INVALID_PARAMETER; + if (device_name && device_name->Length == 0) + return STATUS_INVALID_PARAMETER; + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + std::wstring device_name_str; + void* protected_output_handle = nullptr; + if (device_name) { + UnicodeStringToString(device_name, &device_name_str); + } else { + protected_output_handle = protected_output; + } + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETCERTIFICATESIZE, device_name_str.c_str(), + protected_output_handle, &answer); + + if (code != SBOX_ALL_OK) { + return STATUS_ACCESS_DENIED; + } + + if (!answer.nt_status) + *certificate_length = answer.extended[0].unsigned_int; + + return answer.nt_status; +} + +NTSTATUS WINAPI +TargetGetCertificateSize(GetCertificateSizeFunction, + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + return GetCertificateSizeCommon(device_name, nullptr, certificate_type, + certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateByHandle( + GetCertificateByHandleFunction orig_get_certificate_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length) { + return GetCertificateCommon(nullptr, protected_output, certificate_type, + certificate, certificate_length); +} + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateSizeByHandle( + GetCertificateSizeByHandleFunction orig_get_certificate_size_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length) { + return GetCertificateSizeCommon(nullptr, protected_output, certificate_type, + certificate_length); +} + +NTSTATUS WINAPI +TargetDestroyOPMProtectedOutput(DestroyOPMProtectedOutputFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = CrossCall(ipc, IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT, + static_cast<void*>(protected_output), &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetConfigureOPMProtectedOutput( + ConfigureOPMProtectedOutputFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters) { + // Don't support additional parameters. + if (additional_parameters_size > 0) + return STATUS_INVALID_PARAMETER; + + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + ScopedSharedMemory buffer(sizeof(*parameters)); + if (!buffer.IsValid()) + return STATUS_INVALID_PARAMETER; + memcpy(buffer.memory(), parameters, sizeof(*parameters)); + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT, + static_cast<void*>(protected_output), buffer.handle(), &answer); + + if (code != SBOX_ALL_OK) { + return STATUS_ACCESS_DENIED; + } + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetGetOPMInformation( + GetOPMInformationFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_information) { + size_t max_size = + std::max(sizeof(*parameters), sizeof(*requested_information)); + + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + ScopedSharedMemory buffer(base::checked_cast<uint32_t>(max_size)); + if (!buffer.IsValid()) + return STATUS_INVALID_PARAMETER; + memcpy(buffer.memory(), parameters, sizeof(*parameters)); + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETOPMINFORMATION, + static_cast<void*>(protected_output), buffer.handle(), &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + if (!answer.nt_status) { + memcpy(requested_information, buffer.memory(), + sizeof(*requested_information)); + } + + return answer.nt_status; +} + +NTSTATUS WINAPI +TargetGetOPMRandomNumber(GetOPMRandomNumberFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + InOutCountedBuffer buffer(random_number, sizeof(*random_number)); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETOPMRANDOMNUMBER, + static_cast<void*>(protected_output), buffer, &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetGetSuggestedOPMProtectedOutputArraySize( + GetSuggestedOPMProtectedOutputArraySizeFunction, + PUNICODE_STRING device_name, + DWORD* suggested_output_size) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + std::wstring device_name_str; + UnicodeStringToString(device_name, &device_name_str); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE, + device_name_str.c_str(), &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + if (!answer.nt_status) + *suggested_output_size = answer.extended[0].unsigned_int; + + return answer.nt_status; +} + +NTSTATUS WINAPI TargetSetOPMSigningKeyAndSequenceNumbers( + SetOPMSigningKeyAndSequenceNumbersFunction, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters) { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + DXGKMDT_OPM_ENCRYPTED_PARAMETERS temp_parameters = *parameters; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + InOutCountedBuffer buffer(&temp_parameters, sizeof(temp_parameters)); + ResultCode code = + CrossCall(ipc, IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS, + static_cast<void*>(protected_output), buffer, &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + return answer.nt_status; +} + +NTSTATUS WINAPI +TargetCreateOPMProtectedOutputs(CreateOPMProtectedOutputsFunction, + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD outputs_array_size, + DWORD* output_size, + OPM_PROTECTED_OUTPUT_HANDLE* outputs_array) { + if (vos != DXGKMDT_OPM_VOS_OPM_SEMANTICS) + return STATUS_INVALID_PARAMETER; + + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return STATUS_ACCESS_DENIED; + void* ipc_memory = GetGlobalIPCMemory(); + if (!ipc_memory) + return STATUS_ACCESS_DENIED; + + CrossCallReturn answer = {}; + SharedMemIPCClient ipc(ipc_memory); + base::CheckedNumeric<uint32_t> array_size = outputs_array_size; + array_size *= sizeof(HANDLE); + if (!array_size.IsValid()) + return STATUS_INVALID_PARAMETER; + + InOutCountedBuffer buffer(outputs_array, array_size.ValueOrDie()); + std::wstring device_name_str; + UnicodeStringToString(device_name, &device_name_str); + ResultCode code = CrossCall(ipc, IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS, + device_name_str.c_str(), buffer, &answer); + + if (code != SBOX_ALL_OK) + return STATUS_ACCESS_DENIED; + + if (!answer.nt_status) + *output_size = answer.extended[0].unsigned_int; + + return answer.nt_status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.h b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.h new file mode 100644 index 0000000000..befcda2767 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_interception.h @@ -0,0 +1,151 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ + +#include <windows.h> + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +const DWORD kProtectedVideoOutputSectionSize = 16 * 1024; +const DWORD kMaxEnumMonitors = 32; + +struct EnumMonitorsResult { + DWORD monitor_count; + HMONITOR monitors[kMaxEnumMonitors]; +}; + +typedef BOOL(WINAPI* GdiDllInitializeFunction)(HANDLE dll, + DWORD reason, + LPVOID reserved); + +using GetStockObjectFunction = decltype(&::GetStockObject); + +using RegisterClassWFunction = decltype(&::RegisterClassW); + +using EnumDisplayMonitorsFunction = decltype(&::EnumDisplayMonitors); + +using EnumDisplayDevicesAFunction = decltype(&::EnumDisplayDevicesA); + +using GetMonitorInfoWFunction = decltype(&::GetMonitorInfoW); +using GetMonitorInfoAFunction = decltype(&::GetMonitorInfoA); + +extern "C" { + +// Interceptor for the GdiDllInitialize function. +SANDBOX_INTERCEPT BOOL WINAPI +TargetGdiDllInitialize(GdiDllInitializeFunction orig_gdi_dll_initialize, + HANDLE dll, + DWORD reason); + +// Interceptor for the GetStockObject function. +SANDBOX_INTERCEPT HGDIOBJ WINAPI +TargetGetStockObject(GetStockObjectFunction orig_get_stock_object, int object); + +// Interceptor for the RegisterClassW function. +SANDBOX_INTERCEPT ATOM WINAPI +TargetRegisterClassW(RegisterClassWFunction orig_register_class_function, + const WNDCLASS* wnd_class); + +SANDBOX_INTERCEPT BOOL WINAPI TargetEnumDisplayMonitors( + EnumDisplayMonitorsFunction orig_enum_display_monitors_function, + HDC hdc, + LPCRECT clip_rect, + MONITORENUMPROC enum_function, + LPARAM data); + +SANDBOX_INTERCEPT BOOL WINAPI TargetEnumDisplayDevicesA( + EnumDisplayDevicesAFunction orig_enum_display_devices_function, + LPCSTR device, + DWORD device_number, + PDISPLAY_DEVICEA display_device, + DWORD flags); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetGetMonitorInfoA(GetMonitorInfoAFunction orig_get_monitor_info_function, + HMONITOR monitor, + LPMONITORINFO monitor_info); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetGetMonitorInfoW(GetMonitorInfoWFunction orig_get_monitor_info_function, + HMONITOR monitor, + LPMONITORINFO monitor_info); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetCreateOPMProtectedOutputs( + CreateOPMProtectedOutputsFunction orig_create_proceted_outputs_function, + PUNICODE_STRING device_name, + DXGKMDT_OPM_VIDEO_OUTPUT_SEMANTICS vos, + DWORD protected_output_array_size, + DWORD* output_array_size, + OPM_PROTECTED_OUTPUT_HANDLE* protected_outputs); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetGetCertificate(GetCertificateFunction orig_get_certificate_function, + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateSize( + GetCertificateSizeFunction orig_get_certificate_size_function, + PUNICODE_STRING device_name, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateByHandle( + GetCertificateByHandleFunction orig_get_certificate_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + BYTE* certificate, + ULONG certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetCertificateSizeByHandle( + GetCertificateSizeByHandleFunction orig_get_certificate_size_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_CERTIFICATE_TYPE certificate_type, + ULONG* certificate_length); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetDestroyOPMProtectedOutput( + DestroyOPMProtectedOutputFunction orig_destroy_protected_output_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetConfigureOPMProtectedOutput( + ConfigureOPMProtectedOutputFunction + origin_configure_protected_output_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_CONFIGURE_PARAMETERS* parameters, + ULONG additional_parameters_size, + const BYTE* additional_parameters); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetOPMInformation( + GetOPMInformationFunction origin_get_information_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_GET_INFO_PARAMETERS* parameters, + DXGKMDT_OPM_REQUESTED_INFORMATION* requested_information); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetOPMRandomNumber( + GetOPMRandomNumberFunction orig_get_random_number_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + DXGKMDT_OPM_RANDOM_NUMBER* random_number); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetGetSuggestedOPMProtectedOutputArraySize( + GetSuggestedOPMProtectedOutputArraySizeFunction + orig_get_suggested_size_function, + PUNICODE_STRING device_name, + DWORD* suggested_output_array_size); + +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetSetOPMSigningKeyAndSequenceNumbers( + SetOPMSigningKeyAndSequenceNumbersFunction orig_set_signing_keys_function, + OPM_PROTECTED_OUTPUT_HANDLE protected_output, + const DXGKMDT_OPM_ENCRYPTED_PARAMETERS* parameters); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc new file mode 100644 index 0000000000..8ab915ec33 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.cc @@ -0,0 +1,410 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_mitigations_win32k_policy.h" + +#include <stddef.h> + +#include "sandbox/win/src/process_mitigations_win32k_interception.h" + +namespace sandbox { + +namespace { + +// Define GUIDs for OPM APIs +const GUID DXGKMDT_OPM_GET_CONNECTOR_TYPE = { + 0x81d0bfd5, + 0x6afe, + 0x48c2, + {0x99, 0xc0, 0x95, 0xa0, 0x8f, 0x97, 0xc5, 0xda}}; +const GUID DXGKMDT_OPM_GET_SUPPORTED_PROTECTION_TYPES = { + 0x38f2a801, + 0x9a6c, + 0x48bb, + {0x91, 0x07, 0xb6, 0x69, 0x6e, 0x6f, 0x17, 0x97}}; +const GUID DXGKMDT_OPM_GET_VIRTUAL_PROTECTION_LEVEL = { + 0xb2075857, + 0x3eda, + 0x4d5d, + {0x88, 0xdb, 0x74, 0x8f, 0x8c, 0x1a, 0x05, 0x49}}; +const GUID DXGKMDT_OPM_GET_ACTUAL_PROTECTION_LEVEL = { + 0x1957210a, + 0x7766, + 0x452a, + {0xb9, 0x9a, 0xd2, 0x7a, 0xed, 0x54, 0xf0, 0x3a}}; +const GUID DXGKMDT_OPM_SET_PROTECTION_LEVEL = { + 0x9bb9327c, + 0x4eb5, + 0x4727, + {0x9f, 0x00, 0xb4, 0x2b, 0x09, 0x19, 0xc0, 0xda}}; + +void StringToUnicodeString(PUNICODE_STRING unicode_string, + const std::wstring& device_name) { + static RtlInitUnicodeStringFunction RtlInitUnicodeString; + if (!RtlInitUnicodeString) { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + RtlInitUnicodeString = reinterpret_cast<RtlInitUnicodeStringFunction>( + GetProcAddress(ntdll, "RtlInitUnicodeString")); + } + RtlInitUnicodeString(unicode_string, device_name.c_str()); +} + +struct MonitorListState { + HMONITOR* monitor_list; + uint32_t monitor_list_size; + uint32_t monitor_list_pos; +}; + +BOOL CALLBACK DisplayMonitorEnumProc(HMONITOR monitor, + HDC hdc_monitor, + LPRECT rect_monitor, + LPARAM data) { + MonitorListState* state = reinterpret_cast<MonitorListState*>(data); + if (state->monitor_list_pos >= state->monitor_list_size) + return false; + state->monitor_list[state->monitor_list_pos++] = monitor; + return true; +} + +template <typename T> +T GetExportedFunc(const wchar_t* libname, const char* name) { + OverrideForTestFunction test_override = + ProcessMitigationsWin32KLockdownPolicy::GetOverrideForTestCallback(); + if (test_override) + return reinterpret_cast<T>(test_override(name)); + + static T func = nullptr; + if (!func) { + func = + reinterpret_cast<T>(::GetProcAddress(::GetModuleHandle(libname), name)); + DCHECK(!!func); + } + return func; +} + +#define GDIFUNC(name) GetExportedFunc<name##Function>(L"gdi32.dll", #name) +#define USERFUNC(name) GetExportedFunc<name##Function>(L"user32.dll", #name) + +struct ValidateMonitorParams { + HMONITOR monitor; + std::wstring device_name; + bool result; +}; + +bool GetMonitorDeviceName(HMONITOR monitor, std::wstring* device_name) { + MONITORINFOEXW monitor_info = {}; + monitor_info.cbSize = sizeof(monitor_info); + if (!USERFUNC(GetMonitorInfoW)(monitor, &monitor_info)) + return false; + if (monitor_info.szDevice[CCHDEVICENAME - 1] != 0) + return false; + *device_name = monitor_info.szDevice; + return true; +} + +BOOL CALLBACK ValidateMonitorEnumProc(HMONITOR monitor, + HDC, + LPRECT, + LPARAM data) { + ValidateMonitorParams* valid_params = + reinterpret_cast<ValidateMonitorParams*>(data); + std::wstring device_name; + bool result = false; + if (valid_params->device_name.empty()) { + result = monitor == valid_params->monitor; + } else if (GetMonitorDeviceName(monitor, &device_name)) { + result = device_name == valid_params->device_name; + } + valid_params->result = result; + if (!result) + return true; + return false; +} + +bool IsValidMonitorOrDeviceName(HMONITOR monitor, const wchar_t* device_name) { + ValidateMonitorParams params = {}; + params.monitor = monitor; + if (device_name) + params.device_name = device_name; + USERFUNC(EnumDisplayMonitors) + (nullptr, nullptr, ValidateMonitorEnumProc, + reinterpret_cast<LPARAM>(¶ms)); + return params.result; +} + +} // namespace + +OverrideForTestFunction + ProcessMitigationsWin32KLockdownPolicy::override_callback_; + +bool ProcessMitigationsWin32KLockdownPolicy::GenerateRules( + const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + PolicyRule rule(FAKE_SUCCESS); + if (!policy->AddRule(IpcTag::GDI_GDIDLLINITIALIZE, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETSTOCKOBJECT, &rule)) + return false; + if (!policy->AddRule(IpcTag::USER_REGISTERCLASSW, &rule)) + return false; + if (semantics != TargetPolicy::IMPLEMENT_OPM_APIS) + return true; + if (!policy->AddRule(IpcTag::USER_ENUMDISPLAYMONITORS, &rule)) + return false; + if (!policy->AddRule(IpcTag::USER_ENUMDISPLAYDEVICES, &rule)) + return false; + if (!policy->AddRule(IpcTag::USER_GETMONITORINFO, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETCERTIFICATE, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETCERTIFICATESIZE, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETOPMINFORMATION, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETOPMRANDOMNUMBER, &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE, + &rule)) + return false; + if (!policy->AddRule(IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS, &rule)) + return false; + return true; +} + +uint32_t ProcessMitigationsWin32KLockdownPolicy::EnumDisplayMonitorsAction( + const ClientInfo& client_info, + HMONITOR* monitor_list, + uint32_t monitor_list_size) { + MonitorListState state = {monitor_list, monitor_list_size, 0}; + USERFUNC(EnumDisplayMonitors) + (nullptr, nullptr, DisplayMonitorEnumProc, reinterpret_cast<LPARAM>(&state)); + return state.monitor_list_pos; +} + +bool ProcessMitigationsWin32KLockdownPolicy::GetMonitorInfoAction( + const ClientInfo& client_info, + HMONITOR monitor, + MONITORINFO* monitor_info_ptr) { + if (!IsValidMonitorOrDeviceName(monitor, nullptr)) + return false; + MONITORINFOEXW monitor_info = {}; + monitor_info.cbSize = sizeof(MONITORINFOEXW); + + bool success = USERFUNC(GetMonitorInfoW)( + monitor, reinterpret_cast<MONITORINFO*>(&monitor_info)); + if (success) + memcpy(monitor_info_ptr, &monitor_info, sizeof(monitor_info)); + return success; +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy:: + GetSuggestedOPMProtectedOutputArraySizeAction( + const ClientInfo& client_info, + const std::wstring& device_name, + uint32_t* suggested_array_size) { + if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { + return STATUS_ACCESS_DENIED; + } + UNICODE_STRING unicode_device_name; + StringToUnicodeString(&unicode_device_name, device_name); + DWORD suggested_array_size_dword = 0; + NTSTATUS status = GDIFUNC(GetSuggestedOPMProtectedOutputArraySize)( + &unicode_device_name, &suggested_array_size_dword); + if (!status) + *suggested_array_size = suggested_array_size_dword; + return status; +} + +NTSTATUS +ProcessMitigationsWin32KLockdownPolicy::CreateOPMProtectedOutputsAction( + const ClientInfo& client_info, + const std::wstring& device_name, + HANDLE* protected_outputs, + uint32_t array_input_size, + uint32_t* array_output_size) { + if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { + return STATUS_ACCESS_DENIED; + } + + UNICODE_STRING unicode_device_name; + StringToUnicodeString(&unicode_device_name, device_name); + DWORD output_size = 0; + + NTSTATUS status = GDIFUNC(CreateOPMProtectedOutputs)( + &unicode_device_name, DXGKMDT_OPM_VOS_OPM_SEMANTICS, array_input_size, + &output_size, + reinterpret_cast<OPM_PROTECTED_OUTPUT_HANDLE*>(protected_outputs)); + if (!status) + *array_output_size = output_size; + return status; +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateSizeAction( + const ClientInfo& client_info, + const std::wstring& device_name, + uint32_t* cert_size) { + if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { + return STATUS_ACCESS_DENIED; + } + UNICODE_STRING unicode_device_name; + StringToUnicodeString(&unicode_device_name, device_name); + + return GDIFUNC(GetCertificateSize)(&unicode_device_name, + DXGKMDT_OPM_CERTIFICATE, + reinterpret_cast<DWORD*>(cert_size)); +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateAction( + const ClientInfo& client_info, + const std::wstring& device_name, + BYTE* cert_data, + uint32_t cert_size) { + if (!IsValidMonitorOrDeviceName(nullptr, device_name.c_str())) { + return STATUS_ACCESS_DENIED; + } + UNICODE_STRING unicode_device_name; + StringToUnicodeString(&unicode_device_name, device_name); + + return GDIFUNC(GetCertificate)(&unicode_device_name, DXGKMDT_OPM_CERTIFICATE, + cert_data, cert_size); +} + +NTSTATUS +ProcessMitigationsWin32KLockdownPolicy::GetCertificateSizeByHandleAction( + const ClientInfo& client_info, + HANDLE protected_output, + uint32_t* cert_size) { + auto get_certificate_size_func = GDIFUNC(GetCertificateSizeByHandle); + if (get_certificate_size_func) { + return get_certificate_size_func(protected_output, DXGKMDT_OPM_CERTIFICATE, + reinterpret_cast<DWORD*>(cert_size)); + } + return STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetCertificateByHandleAction( + const ClientInfo& client_info, + HANDLE protected_output, + BYTE* cert_data, + uint32_t cert_size) { + auto get_certificate_func = GDIFUNC(GetCertificateByHandle); + if (get_certificate_func) { + return get_certificate_func(protected_output, DXGKMDT_OPM_CERTIFICATE, + cert_data, cert_size); + } + return STATUS_NOT_IMPLEMENTED; +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetOPMRandomNumberAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* random_number) { + return GDIFUNC(GetOPMRandomNumber)( + protected_output, static_cast<DXGKMDT_OPM_RANDOM_NUMBER*>(random_number)); +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy:: + SetOPMSigningKeyAndSequenceNumbersAction(const ClientInfo& client_info, + HANDLE protected_output, + void* parameters) { + return GDIFUNC(SetOPMSigningKeyAndSequenceNumbers)( + protected_output, + static_cast<DXGKMDT_OPM_ENCRYPTED_PARAMETERS*>(parameters)); +} + +NTSTATUS +ProcessMitigationsWin32KLockdownPolicy::ConfigureOPMProtectedOutputAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* parameters_ptr) { + DXGKMDT_OPM_CONFIGURE_PARAMETERS parameters; + memcpy(¶meters, parameters_ptr, sizeof(parameters)); + if (parameters.guidSetting != DXGKMDT_OPM_SET_PROTECTION_LEVEL || + parameters.cbParametersSize != + sizeof(DXGKMDT_OPM_SET_PROTECTION_LEVEL_PARAMETERS)) { + return STATUS_INVALID_PARAMETER; + } + + DXGKMDT_OPM_SET_PROTECTION_LEVEL_PARAMETERS prot_level; + memcpy(&prot_level, parameters.abParameters, sizeof(prot_level)); + if (prot_level.Reserved || prot_level.Reserved2) + return STATUS_INVALID_PARAMETER; + + if (prot_level.ulProtectionType != DXGKMDT_OPM_PROTECTION_TYPE_HDCP && + prot_level.ulProtectionType != DXGKMDT_OPM_PROTECTION_TYPE_DPCP) { + return STATUS_INVALID_PARAMETER; + } + + // Protection levels are same for HDCP and DPCP. + if (prot_level.ulProtectionLevel != DXGKMDT_OPM_HDCP_OFF && + prot_level.ulProtectionLevel != DXGKMDT_OPM_HDCP_ON) { + return STATUS_INVALID_PARAMETER; + } + + return GDIFUNC(ConfigureOPMProtectedOutput)(protected_output, ¶meters, 0, + nullptr); +} + +NTSTATUS ProcessMitigationsWin32KLockdownPolicy::GetOPMInformationAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* parameters_ptr, + void* requested_info_ptr) { + DXGKMDT_OPM_GET_INFO_PARAMETERS parameters; + memcpy(¶meters, parameters_ptr, sizeof(parameters)); + + bool valid_parameters = false; + // Validate sizes based on the type being requested. + if ((parameters.guidInformation == DXGKMDT_OPM_GET_CONNECTOR_TYPE || + parameters.guidInformation == + DXGKMDT_OPM_GET_SUPPORTED_PROTECTION_TYPES) && + parameters.cbParametersSize == 0) { + valid_parameters = true; + } else if ((parameters.guidInformation == + DXGKMDT_OPM_GET_VIRTUAL_PROTECTION_LEVEL || + parameters.guidInformation == + DXGKMDT_OPM_GET_ACTUAL_PROTECTION_LEVEL) && + parameters.cbParametersSize == sizeof(uint32_t)) { + uint32_t param_value; + memcpy(¶m_value, parameters.abParameters, sizeof(param_value)); + if (param_value == DXGKMDT_OPM_PROTECTION_TYPE_HDCP || + param_value == DXGKMDT_OPM_PROTECTION_TYPE_DPCP) { + valid_parameters = true; + } + } + if (!valid_parameters) + return STATUS_INVALID_PARAMETER; + DXGKMDT_OPM_REQUESTED_INFORMATION requested_info = {}; + NTSTATUS status = GDIFUNC(GetOPMInformation)(protected_output, ¶meters, + &requested_info); + if (!status) + memcpy(requested_info_ptr, &requested_info, sizeof(requested_info)); + + return status; +} + +NTSTATUS +ProcessMitigationsWin32KLockdownPolicy::DestroyOPMProtectedOutputAction( + HANDLE protected_output) { + return GDIFUNC(DestroyOPMProtectedOutput)(protected_output); +} + +void ProcessMitigationsWin32KLockdownPolicy::SetOverrideForTestCallback( + OverrideForTestFunction callback) { + override_callback_ = callback; +} + +OverrideForTestFunction +ProcessMitigationsWin32KLockdownPolicy::GetOverrideForTestCallback() { + return override_callback_; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.h b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.h new file mode 100644 index 0000000000..0c2b2dff95 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_mitigations_win32k_policy.h @@ -0,0 +1,91 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ +#define SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// A callback function type to get a function for testing. +typedef void* (*OverrideForTestFunction)(const char* name); + +// This class centralizes most of the knowledge related to the process +// mitigations Win32K lockdown policy. +class ProcessMitigationsWin32KLockdownPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for the Win32K process mitigation policy. + // name is the object name, semantics is the desired semantics for the + // open or create and policy is the policy generator to which the rules are + // going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + static uint32_t EnumDisplayMonitorsAction(const ClientInfo& client_info, + HMONITOR* monitor_list, + uint32_t monitor_list_size); + static bool GetMonitorInfoAction(const ClientInfo& client_info, + HMONITOR monitor, + MONITORINFO* monitor_info); + + static NTSTATUS GetSuggestedOPMProtectedOutputArraySizeAction( + const ClientInfo& client_info, + const std::wstring& device_name, + uint32_t* suggested_array_size); + + static NTSTATUS CreateOPMProtectedOutputsAction( + const ClientInfo& client_info, + const std::wstring& device_name, + HANDLE* protected_outputs, + uint32_t array_input_size, + uint32_t* array_output_size); + + static NTSTATUS GetCertificateSizeAction(const ClientInfo& client_info, + const std::wstring& device_name, + uint32_t* cert_size); + static NTSTATUS GetCertificateAction(const ClientInfo& client_info, + const std::wstring& device_name, + BYTE* cert_data, + uint32_t cert_size); + static NTSTATUS GetCertificateSizeByHandleAction( + const ClientInfo& client_info, + HANDLE protected_output, + uint32_t* cert_size); + static NTSTATUS GetCertificateByHandleAction(const ClientInfo& client_info, + HANDLE protected_output, + BYTE* cert_data, + uint32_t cert_size); + static NTSTATUS GetOPMRandomNumberAction(const ClientInfo& client_info, + HANDLE protected_output, + void* random_number); + static NTSTATUS SetOPMSigningKeyAndSequenceNumbersAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* parameters); + static NTSTATUS ConfigureOPMProtectedOutputAction( + const ClientInfo& client_info, + HANDLE protected_output, + void* parameters_ptr); + static NTSTATUS GetOPMInformationAction(const ClientInfo& client_info, + HANDLE protected_output, + void* parameters_ptr, + void* requested_information_ptr); + static NTSTATUS DestroyOPMProtectedOutputAction(HANDLE protected_output); + static void SetOverrideForTestCallback(OverrideForTestFunction callback); + static OverrideForTestFunction GetOverrideForTestCallback(); + + private: + static OverrideForTestFunction override_callback_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_MITIGATIONS_WIN32K_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/process_policy_test.cc new file mode 100644 index 0000000000..c321c3a088 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_policy_test.cc @@ -0,0 +1,548 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <memory> +#include <string> + +#include "base/memory/free_deleter.h" +#include "base/strings/sys_string_conversions.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" +#include "build/build_config.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Creates a process with the |exe| and |command| parameter using the +// unicode and ascii version of the api. +sandbox::SboxTestResult CreateProcessHelper(const std::wstring& exe, + const std::wstring& command) { + base::win::ScopedProcessInformation pi; + STARTUPINFOW si = {sizeof(si)}; + const wchar_t* exe_name = nullptr; + if (!exe.empty()) + exe_name = exe.c_str(); + + std::unique_ptr<wchar_t, base::FreeDeleter> writable_command( + _wcsdup(command.c_str())); + + // Create the process with the unicode version of the API. + sandbox::SboxTestResult ret1 = sandbox::SBOX_TEST_FAILED; + PROCESS_INFORMATION temp_process_info = {}; + if (::CreateProcessW( + exe_name, command.empty() ? nullptr : writable_command.get(), nullptr, + nullptr, false, 0, nullptr, nullptr, &si, &temp_process_info)) { + pi.Set(temp_process_info); + ret1 = sandbox::SBOX_TEST_SUCCEEDED; + } else { + DWORD last_error = GetLastError(); + if ((ERROR_NOT_ENOUGH_QUOTA == last_error) || + (ERROR_ACCESS_DENIED == last_error) || + (ERROR_FILE_NOT_FOUND == last_error)) { + ret1 = sandbox::SBOX_TEST_DENIED; + } else { + ret1 = sandbox::SBOX_TEST_FAILED; + } + } + + pi.Close(); + + // Do the same with the ansi version of the api + STARTUPINFOA sia = {sizeof(sia)}; + sandbox::SboxTestResult ret2 = sandbox::SBOX_TEST_FAILED; + + std::string narrow_cmd_line = + base::SysWideToMultiByte(command.c_str(), CP_UTF8); + if (::CreateProcessA( + exe_name ? base::SysWideToMultiByte(exe_name, CP_UTF8).c_str() + : nullptr, + command.empty() ? nullptr : &narrow_cmd_line[0], nullptr, nullptr, + false, 0, nullptr, nullptr, &sia, &temp_process_info)) { + pi.Set(temp_process_info); + ret2 = sandbox::SBOX_TEST_SUCCEEDED; + } else { + DWORD last_error = GetLastError(); + if ((ERROR_NOT_ENOUGH_QUOTA == last_error) || + (ERROR_ACCESS_DENIED == last_error) || + (ERROR_FILE_NOT_FOUND == last_error)) { + ret2 = sandbox::SBOX_TEST_DENIED; + } else { + ret2 = sandbox::SBOX_TEST_FAILED; + } + } + + if (ret1 == ret2) + return ret1; + + return sandbox::SBOX_TEST_FAILED; +} + +} // namespace + +namespace sandbox { + +SBOX_TESTS_COMMAND int Process_RunApp1(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + std::wstring path = MakePathToSys(argv[0], false); + + // TEST 1: Try with the path in the app_name. + return CreateProcessHelper(path, std::wstring()); +} + +SBOX_TESTS_COMMAND int Process_RunApp2(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + std::wstring path = MakePathToSys(argv[0], false); + + // TEST 2: Try with the path in the cmd_line. + std::wstring cmd_line = L"\""; + cmd_line += path; + cmd_line += L"\""; + return CreateProcessHelper(std::wstring(), cmd_line); +} + +SBOX_TESTS_COMMAND int Process_RunApp3(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + // TEST 3: Try file name in the cmd_line. + return CreateProcessHelper(std::wstring(), argv[0]); +} + +SBOX_TESTS_COMMAND int Process_RunApp4(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + // TEST 4: Try file name in the app_name and current directory sets correctly. + std::wstring system32 = MakePathToSys(L"", false); + wchar_t current_directory[MAX_PATH + 1]; + DWORD ret = ::GetCurrentDirectory(MAX_PATH, current_directory); + if (!ret) + return SBOX_TEST_FIRST_ERROR; + if (ret >= MAX_PATH) + return SBOX_TEST_FAILED; + + current_directory[ret] = L'\\'; + current_directory[ret + 1] = L'\0'; + if (!::SetCurrentDirectory(system32.c_str())) + return SBOX_TEST_SECOND_ERROR; + + const int result4 = CreateProcessHelper(argv[0], std::wstring()); + return ::SetCurrentDirectory(current_directory) ? result4 : SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int Process_RunApp5(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + std::wstring path = MakePathToSys(argv[0], false); + + // TEST 5: Try with the path in the cmd_line and arguments. + std::wstring cmd_line = L"\""; + cmd_line += path; + cmd_line += L"\" /I"; + return CreateProcessHelper(std::wstring(), cmd_line); +} + +SBOX_TESTS_COMMAND int Process_RunApp6(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + // TEST 6: Try with the file_name in the cmd_line and arguments. + std::wstring cmd_line = argv[0]; + cmd_line += L" /I"; + return CreateProcessHelper(std::wstring(), cmd_line); +} + +// Creates a process and checks if it's possible to get a handle to it's token. +SBOX_TESTS_COMMAND int Process_GetChildProcessToken(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring path = MakePathToSys(argv[0], false); + + STARTUPINFOW si = {sizeof(si)}; + + PROCESS_INFORMATION temp_process_info = {}; + if (!::CreateProcessW(path.c_str(), nullptr, nullptr, nullptr, false, + CREATE_SUSPENDED, nullptr, nullptr, &si, + &temp_process_info)) { + return SBOX_TEST_FAILED; + } + base::win::ScopedProcessInformation pi(temp_process_info); + + HANDLE token = nullptr; + bool result = + ::OpenProcessToken(pi.process_handle(), TOKEN_IMPERSONATE, &token); + DWORD error = ::GetLastError(); + + base::win::ScopedHandle token_handle(token); + + if (!::TerminateProcess(pi.process_handle(), 0)) + return SBOX_TEST_FAILED; + + if (result && token) + return SBOX_TEST_SUCCEEDED; + + if (ERROR_ACCESS_DENIED == error) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + +// Creates a suspended process using CreateProcessA then kill it. +SBOX_TESTS_COMMAND int Process_CreateProcessA(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (!argv || !argv[0]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + STARTUPINFOA si = {sizeof(si)}; + + std::wstring path = MakePathToSys(argv[0], false); + + PROCESS_INFORMATION temp_process_info = {}; + // Create suspended to avoid popping calc. + if (!::CreateProcessA(base::SysWideToMultiByte(path, CP_UTF8).c_str(), + nullptr, nullptr, nullptr, false, CREATE_SUSPENDED, + nullptr, nullptr, &si, &temp_process_info)) { + return SBOX_TEST_FAILED; + } + base::win::ScopedProcessInformation pi(temp_process_info); + + if (!::TerminateProcess(pi.process_handle(), 0)) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +SBOX_TESTS_COMMAND int Process_OpenToken(int argc, wchar_t** argv) { + HANDLE token; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) { + if (ERROR_ACCESS_DENIED == ::GetLastError()) { + return SBOX_TEST_DENIED; + } + } else { + ::CloseHandle(token); + return SBOX_TEST_SUCCEEDED; + } + + return SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int Process_Crash(int argc, wchar_t** argv) { + __debugbreak(); + return SBOX_TEST_FAILED; +} +// Generate a event name, used to test thread creation. +std::wstring GenerateEventName(DWORD pid) { + wchar_t buff[30] = {0}; + int res = swprintf_s(buff, sizeof(buff) / sizeof(buff[0]), + L"ProcessPolicyTest_%08x", pid); + if (-1 != res) { + return std::wstring(buff); + } + return std::wstring(); +} + +// This is the function that is called when testing thread creation. +// It is expected to set an event that the caller is waiting on. +DWORD WINAPI TestThreadFunc(LPVOID lpdwThreadParam) { + std::wstring event_name = GenerateEventName( + static_cast<DWORD>(reinterpret_cast<uintptr_t>(lpdwThreadParam))); + if (!event_name.length()) + return 1; + HANDLE event = ::OpenEvent(EVENT_ALL_ACCESS | EVENT_MODIFY_STATE, false, + event_name.c_str()); + if (!event) + return 1; + if (!SetEvent(event)) + return 1; + return 0; +} + +SBOX_TESTS_COMMAND int Process_CreateThread(int argc, wchar_t** argv) { + DWORD pid = ::GetCurrentProcessId(); + std::wstring event_name = GenerateEventName(pid); + if (!event_name.length()) + return SBOX_TEST_FIRST_ERROR; + HANDLE event = ::CreateEvent(nullptr, true, false, event_name.c_str()); + if (!event) + return SBOX_TEST_SECOND_ERROR; + + DWORD thread_id = 0; + HANDLE thread = nullptr; + thread = ::CreateThread(nullptr, 0, &TestThreadFunc, + reinterpret_cast<LPVOID>(static_cast<uintptr_t>(pid)), + 0, &thread_id); + + if (!thread) + return SBOX_TEST_THIRD_ERROR; + if (!thread_id) + return SBOX_TEST_FOURTH_ERROR; + if (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0) + return SBOX_TEST_FIFTH_ERROR; + DWORD exit_code = 0; + if (!GetExitCodeThread(thread, &exit_code)) + return SBOX_TEST_SIXTH_ERROR; + if (exit_code) + return SBOX_TEST_SEVENTH_ERROR; + if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0) + return SBOX_TEST_FAILED; + return SBOX_TEST_SUCCEEDED; +} + +// Creates a process and checks its exit code. Succeeds on exit code 0. +SBOX_TESTS_COMMAND int Process_CheckExitCode(int argc, wchar_t** argv) { + if (argc != 3) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + if (!argv || !argv[0] || !argv[1] || !argv[2]) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + std::wstring path = MakePathToSys(argv[0], false); + std::wstring cmdline = argv[1]; + std::wstring cwd = argv[2]; + + STARTUPINFOW si = {sizeof(si)}; + + PROCESS_INFORMATION temp_process_info = {}; + if (!::CreateProcessW(path.c_str(), &cmdline[0], nullptr, nullptr, false, 0, + nullptr, cwd.c_str(), &si, &temp_process_info)) { + return SBOX_TEST_FAILED; + } + base::win::ScopedProcessInformation pi(temp_process_info); + DWORD ret = WaitForSingleObject(pi.process_handle(), 1000); + if (ret != WAIT_OBJECT_0) + return SBOX_TEST_FAILED; + + DWORD exit_code; + if (!GetExitCodeProcess(pi.process_handle(), &exit_code)) + return SBOX_TEST_FAILED; + + if (exit_code != 0) + return SBOX_TEST_FAILED; + + return SBOX_TEST_SUCCEEDED; +} + +TEST(ProcessPolicyTest, TestAllAccess) { + // Check if the "all access" rule fails to be added when the token is too + // powerful. + TestRunner runner; + + // Check the failing case. + runner.GetPolicy()->SetTokenLevel(USER_INTERACTIVE, USER_LOCKDOWN); + EXPECT_EQ(SBOX_ERROR_UNSUPPORTED, + runner.GetPolicy()->AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, + L"this is not important")); + + // Check the working case. + runner.GetPolicy()->SetTokenLevel(USER_INTERACTIVE, USER_INTERACTIVE); + + EXPECT_EQ(SBOX_ALL_OK, + runner.GetPolicy()->AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, + L"this is not important")); +} + +TEST(ProcessPolicyTest, CreateProcessAW) { + TestRunner runner; + std::wstring maybe_virtual_exe_path = MakePathToSys(L"findstr.exe", false); + std::wstring non_virtual_exe_path = MakePathToSys32(L"findstr.exe", false); + ASSERT_TRUE(!maybe_virtual_exe_path.empty()); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, + maybe_virtual_exe_path.c_str())); + + if (non_virtual_exe_path != maybe_virtual_exe_path) { + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, + non_virtual_exe_path.c_str())); + } + + // Need to add directory rules for the directories that we use in + // SetCurrentDirectory. + EXPECT_TRUE(runner.AddRuleSys32(TargetPolicy::FILES_ALLOW_DIR_ANY, L"")); + + wchar_t current_directory[MAX_PATH]; + DWORD ret = ::GetCurrentDirectory(MAX_PATH, current_directory); + ASSERT_TRUE(0 != ret && ret < MAX_PATH); + + wcscat_s(current_directory, MAX_PATH, L"\\"); + EXPECT_TRUE( + runner.AddFsRule(TargetPolicy::FILES_ALLOW_DIR_ANY, current_directory)); + + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp1 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp2 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp3 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp4 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp5 calc.exe")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Process_RunApp6 calc.exe")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp1 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp2 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp3 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp4 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp5 findstr.exe")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_RunApp6 findstr.exe")); +} + +// Tests that the broker correctly handles a process crashing within the job. +// Fails on Windows ARM64: https://crbug.com/905526 +#if defined(ARCH_CPU_ARM64) +#define MAYBE_CreateProcessCrashy DISABLED_CreateProcessCrashy +#else +#define MAYBE_CreateProcessCrashy CreateProcessCrashy +#endif +TEST(ProcessPolicyTest, MAYBE_CreateProcessCrashy) { + TestRunner runner; + EXPECT_EQ(static_cast<int>(STATUS_BREAKPOINT), + runner.RunTest(L"Process_Crash")); +} + +TEST(ProcessPolicyTest, CreateProcessWithCWD) { + TestRunner runner; + std::wstring sys_path = MakePathToSys(L"", false); + while (!sys_path.empty() && sys_path.back() == L'\\') + sys_path.erase(sys_path.length() - 1); + + std::wstring exe_path = MakePathToSys(L"cmd.exe", false); + std::wstring cmd_line = + L"\"/c if \\\"%CD%\\\" NEQ \\\"" + sys_path + L"\\\" exit 1\""; + + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, exe_path.c_str())); + + std::wstring command = + L"Process_CheckExitCode cmd.exe " + cmd_line + L" " + sys_path; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(command.c_str())); +} + +TEST(ProcessPolicyTest, OpenToken) { + TestRunner runner; + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Process_OpenToken")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMinAccess) { + TestRunner runner; + std::wstring exe_path = MakePathToSys(L"findstr.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccess) { + TestRunner runner(JOB_UNPROTECTED, USER_INTERACTIVE, USER_INTERACTIVE); + std::wstring exe_path = MakePathToSys(L"findstr.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMinAccessNoJob) { + TestRunner runner(JOB_NONE, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); + std::wstring exe_path = MakePathToSys(L"findstr.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_MIN_EXEC, exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestGetProcessTokenMaxAccessNoJob) { + TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE); + std::wstring exe_path = MakePathToSys(L"findstr.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, exe_path.c_str())); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_GetChildProcessToken findstr.exe")); +} + +TEST(ProcessPolicyTest, TestCreateProcessA) { + TestRunner runner; + sandbox::TargetPolicy* policy = runner.GetPolicy(); + policy->SetJobLevel(JOB_NONE, 0); + policy->SetTokenLevel(USER_UNPROTECTED, USER_UNPROTECTED); + std::wstring exe_path = MakePathToSys(L"calc.exe", false); + ASSERT_TRUE(!exe_path.empty()); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_PROCESS, + TargetPolicy::PROCESS_ALL_EXEC, exe_path.c_str())); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Process_CreateProcessA calc.exe")); +} + +// This tests that the CreateThread works with CSRSS not locked down. +// In other words, that the interception passes through OK. +TEST(ProcessPolicyTest, TestCreateThreadWithCsrss) { + TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE); + runner.SetDisableCsrss(false); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Process_CreateThread")); +} + +// This tests that the CreateThread works with CSRSS locked down. +// In other words, that the interception correctly works. +TEST(ProcessPolicyTest, TestCreateThreadWithoutCsrss) { + TestRunner runner(JOB_NONE, USER_INTERACTIVE, USER_INTERACTIVE); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Process_CreateThread")); +} + +// This tests that our CreateThread interceptors works when called directly. +TEST(ProcessPolicyTest, TestCreateThreadOutsideSandbox) { + DWORD pid = ::GetCurrentProcessId(); + std::wstring event_name = GenerateEventName(pid); + ASSERT_STRNE(nullptr, event_name.c_str()); + HANDLE event = ::CreateEvent(nullptr, true, false, event_name.c_str()); + EXPECT_NE(static_cast<HANDLE>(nullptr), event); + + DWORD thread_id = 0; + HANDLE thread = nullptr; + thread = TargetCreateThread( + ::CreateThread, nullptr, 0, &TestThreadFunc, + reinterpret_cast<LPVOID>(static_cast<uintptr_t>(pid)), 0, &thread_id); + EXPECT_NE(static_cast<HANDLE>(nullptr), thread); + EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(thread, INFINITE)); + EXPECT_EQ(WAIT_OBJECT_0, WaitForSingleObject(event, INFINITE)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.cc new file mode 100644 index 0000000000..7f0b0a6542 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.cc @@ -0,0 +1,275 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_thread_dispatcher.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/logging.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/process_thread_interception.h" +#include "sandbox/win/src/process_thread_policy.h" +#include "sandbox/win/src/sandbox.h" + +namespace { + +// Extracts the application name from a command line. +// +// The application name is the first element of the command line. If +// there is no quotes, the first element is delimited by the first space. +// If there are quotes, the first element is delimited by the quotes. +// +// The create process call is smarter than us. It tries really hard to launch +// the process even if the command line is wrong. For example: +// "c:\program files\test param" will first try to launch c:\program.exe then +// c:\program files\test.exe. We don't do that, we stop after at the first +// space when there is no quotes. +std::wstring GetPathFromCmdLine(const std::wstring& cmd_line) { + std::wstring exe_name; + // Check if it starts with '"'. + if (cmd_line[0] == L'\"') { + // Find the position of the second '"', this terminates the path. + std::wstring::size_type pos = cmd_line.find(L'\"', 1); + if (std::wstring::npos == pos) + return cmd_line; + exe_name = cmd_line.substr(1, pos - 1); + } else { + // There is no '"', that means that the appname is terminated at the + // first space. + std::wstring::size_type pos = cmd_line.find(L' '); + if (std::wstring::npos == pos) { + // There is no space, the cmd_line contains only the app_name + exe_name = cmd_line; + } else { + exe_name = cmd_line.substr(0, pos); + } + } + + return exe_name; +} + +// Returns true is the path in parameter is relative. False if it's +// absolute. +bool IsPathRelative(const std::wstring& path) { + // A path is Relative if it's not a UNC path beginnning with \\ or a + // path beginning with a drive. (i.e. X:\) + if (path.find(L"\\\\") == 0 || path.find(L":\\") == 1) + return false; + return true; +} + +// Converts a relative path to an absolute path. +bool ConvertToAbsolutePath(const std::wstring& child_current_directory, + bool use_env_path, + std::wstring* path) { + wchar_t file_buffer[MAX_PATH]; + wchar_t* file_part = nullptr; + + // Here we should start by looking at the path where the child application was + // started. We don't have this information yet. + DWORD result = 0; + if (use_env_path) { + // Try with the complete path + result = ::SearchPath(nullptr, path->c_str(), nullptr, MAX_PATH, + file_buffer, &file_part); + } + + if (0 == result) { + // Try with the current directory of the child + result = ::SearchPath(child_current_directory.c_str(), path->c_str(), + nullptr, MAX_PATH, file_buffer, &file_part); + } + + if (0 == result || result >= MAX_PATH) + return false; + + *path = file_buffer; + return true; +} + +} // namespace +namespace sandbox { + +ThreadProcessDispatcher::ThreadProcessDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall open_thread = { + {IpcTag::NTOPENTHREAD, {UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenThread)}; + + static const IPCCall open_process = { + {IpcTag::NTOPENPROCESS, {UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcess)}; + + static const IPCCall process_token = { + {IpcTag::NTOPENPROCESSTOKEN, {VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcessToken)}; + + static const IPCCall process_tokenex = { + {IpcTag::NTOPENPROCESSTOKENEX, {VOIDPTR_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::NtOpenProcessTokenEx)}; + + static const IPCCall create_params = { + {IpcTag::CREATEPROCESSW, + {WCHAR_TYPE, WCHAR_TYPE, WCHAR_TYPE, WCHAR_TYPE, INOUTPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::CreateProcessW)}; + + // NOTE(liamjm): 2nd param is size_t: Using VOIDPTR_TYPE as they are + // the same size on windows. + static_assert(sizeof(size_t) == sizeof(void*), + "VOIDPTR_TYPE not same size as size_t"); + static const IPCCall create_thread_params = { + {IpcTag::CREATETHREAD, + {VOIDPTR_TYPE, VOIDPTR_TYPE, VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>( + &ThreadProcessDispatcher::CreateThread)}; + + ipc_calls_.push_back(open_thread); + ipc_calls_.push_back(open_process); + ipc_calls_.push_back(process_token); + ipc_calls_.push_back(process_tokenex); + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(create_thread_params); +} + +bool ThreadProcessDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + switch (service) { + case IpcTag::NTOPENTHREAD: + case IpcTag::NTOPENPROCESS: + case IpcTag::NTOPENPROCESSTOKEN: + case IpcTag::NTOPENPROCESSTOKENEX: + case IpcTag::CREATETHREAD: + // There is no explicit policy for these services. + NOTREACHED(); + return false; + + case IpcTag::CREATEPROCESSW: + return INTERCEPT_EAT(manager, kKerneldllName, CreateProcessW, + CREATE_PROCESSW_ID, 44) && + INTERCEPT_EAT(manager, L"kernel32.dll", CreateProcessA, + CREATE_PROCESSA_ID, 44); + + default: + return false; + } +} + +bool ThreadProcessDispatcher::NtOpenThread(IPCInfo* ipc, + uint32_t desired_access, + uint32_t thread_id) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenThreadAction( + *ipc->client_info, desired_access, thread_id, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcess(IPCInfo* ipc, + uint32_t desired_access, + uint32_t process_id) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessAction( + *ipc->client_info, desired_access, process_id, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcessToken(IPCInfo* ipc, + HANDLE process, + uint32_t desired_access) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessTokenAction( + *ipc->client_info, process, desired_access, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::NtOpenProcessTokenEx(IPCInfo* ipc, + HANDLE process, + uint32_t desired_access, + uint32_t attributes) { + HANDLE handle; + NTSTATUS ret = ProcessPolicy::OpenProcessTokenExAction( + *ipc->client_info, process, desired_access, attributes, &handle); + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +bool ThreadProcessDispatcher::CreateProcessW(IPCInfo* ipc, + std::wstring* name, + std::wstring* cmd_line, + std::wstring* cur_dir, + std::wstring* target_cur_dir, + CountedBuffer* info) { + if (sizeof(PROCESS_INFORMATION) != info->Size()) + return false; + + // Check if there is an application name. + std::wstring exe_name; + if (!name->empty()) + exe_name = *name; + else + exe_name = GetPathFromCmdLine(*cmd_line); + + if (IsPathRelative(exe_name)) { + if (!ConvertToAbsolutePath(*cur_dir, name->empty(), &exe_name)) { + // Cannot find the path. Maybe the file does not exist. + ipc->return_info.win32_result = ERROR_FILE_NOT_FOUND; + return true; + } + } + + const wchar_t* const_exe_name = exe_name.c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(const_exe_name); + + EvalResult eval = + policy_base_->EvalPolicy(IpcTag::CREATEPROCESSW, params.GetBase()); + + PROCESS_INFORMATION* proc_info = + reinterpret_cast<PROCESS_INFORMATION*>(info->Buffer()); + // Here we force the app_name to be the one we used for the policy lookup. + // If our logic was wrong, at least we wont allow create a random process. + DWORD ret = ProcessPolicy::CreateProcessWAction( + eval, *ipc->client_info, exe_name, *cmd_line, *target_cur_dir, proc_info); + + ipc->return_info.win32_result = ret; + return true; +} + +bool ThreadProcessDispatcher::CreateThread(IPCInfo* ipc, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + LPVOID parameter, + DWORD creation_flags) { + if (!start_address) { + return false; + } + + HANDLE handle; + DWORD ret = ProcessPolicy::CreateThreadAction( + *ipc->client_info, stack_size, start_address, parameter, creation_flags, + nullptr, &handle); + + ipc->return_info.nt_status = ret; + ipc->return_info.handle = handle; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.h new file mode 100644 index 0000000000..07466c46e2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_dispatcher.h @@ -0,0 +1,69 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ +#define SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles process and thread-related IPC calls. +class ThreadProcessDispatcher : public Dispatcher { + public: + explicit ThreadProcessDispatcher(PolicyBase* policy_base); + ~ThreadProcessDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to NtOpenThread() in the target. + bool NtOpenThread(IPCInfo* ipc, uint32_t desired_access, uint32_t thread_id); + + // Processes IPC requests coming from calls to NtOpenProcess() in the target. + bool NtOpenProcess(IPCInfo* ipc, + uint32_t desired_access, + uint32_t process_id); + + // Processes IPC requests from calls to NtOpenProcessToken() in the target. + bool NtOpenProcessToken(IPCInfo* ipc, + HANDLE process, + uint32_t desired_access); + + // Processes IPC requests from calls to NtOpenProcessTokenEx() in the target. + bool NtOpenProcessTokenEx(IPCInfo* ipc, + HANDLE process, + uint32_t desired_access, + uint32_t attributes); + + // Processes IPC requests coming from calls to CreateProcessW() in the target. + bool CreateProcessW(IPCInfo* ipc, + std::wstring* name, + std::wstring* cmd_line, + std::wstring* cur_dir, + std::wstring* target_cur_dir, + CountedBuffer* info); + + // Processes IPC requests coming from calls to CreateThread() in the target. + bool CreateThread(IPCInfo* ipc, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + LPVOID parameter, + DWORD creation_flags); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(ThreadProcessDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_THREAD_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_interception.cc b/security/sandbox/chromium/sandbox/win/src/process_thread_interception.cc new file mode 100644 index 0000000000..0bd3d5a8d2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_interception.cc @@ -0,0 +1,520 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_thread_interception.h" + +#include <stdint.h> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +// Hooks NtOpenThread and proxy the call to the broker if it's trying to +// open a thread in the same process. +NTSTATUS WINAPI TargetNtOpenThread(NtOpenThreadFunction orig_OpenThread, + PHANDLE thread, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NTSTATUS status = + orig_OpenThread(thread, desired_access, object_attributes, client_id); + if (NT_SUCCESS(status)) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenThread"); + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + if (!client_id) + break; + + uint32_t thread_id = 0; + bool should_break = false; + __try { + // We support only the calls for the current process + if (client_id->UniqueProcess) + should_break = true; + + // Object attributes should be nullptr or empty. + if (!should_break && object_attributes) { + if (object_attributes->Attributes || object_attributes->ObjectName || + object_attributes->RootDirectory || + object_attributes->SecurityDescriptor || + object_attributes->SecurityQualityOfService) { + should_break = true; + } + } + + thread_id = static_cast<uint32_t>( + reinterpret_cast<ULONG_PTR>(client_id->UniqueThread)); + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + if (should_break) + break; + + if (!ValidParameter(thread, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENTHREAD, desired_access, + thread_id, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // The nt_status here is most likely STATUS_INVALID_CID because + // in the broker we set the process id in the CID (client ID) param + // to be the current process. If you try to open a thread from another + // process you will get this INVALID_CID error. On the other hand, if you + // try to open a thread in your own process, it should return success. + // We don't want to return STATUS_INVALID_CID here, so we return the + // return of the original open thread status, which is most likely + // STATUS_ACCESS_DENIED. + break; + + __try { + // Write the output parameters. + *thread = answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + mozilla::sandboxing::LogAllowed("NtOpenThread"); + return answer.nt_status; + } while (false); + + return status; +} + +// Hooks NtOpenProcess and proxy the call to the broker if it's trying to +// open the current process. +NTSTATUS WINAPI TargetNtOpenProcess(NtOpenProcessFunction orig_OpenProcess, + PHANDLE process, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id) { + NTSTATUS status = + orig_OpenProcess(process, desired_access, object_attributes, client_id); + if (NT_SUCCESS(status)) + return status; + + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + if (!client_id) + break; + + uint32_t process_id = 0; + bool should_break = false; + __try { + // Object attributes should be nullptr or empty. + if (!should_break && object_attributes) { + if (object_attributes->Attributes || object_attributes->ObjectName || + object_attributes->RootDirectory || + object_attributes->SecurityDescriptor || + object_attributes->SecurityQualityOfService) { + should_break = true; + } + } + + process_id = static_cast<uint32_t>( + reinterpret_cast<ULONG_PTR>(client_id->UniqueProcess)); + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + if (should_break) + break; + + if (!ValidParameter(process, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENPROCESS, desired_access, + process_id, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *process = answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + return answer.nt_status; + } while (false); + + return status; +} + +NTSTATUS WINAPI +TargetNtOpenProcessToken(NtOpenProcessTokenFunction orig_OpenProcessToken, + HANDLE process, + ACCESS_MASK desired_access, + PHANDLE token) { + NTSTATUS status = orig_OpenProcessToken(process, desired_access, token); + if (NT_SUCCESS(status)) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenProcessToken"); + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + + if (CURRENT_PROCESS != process) + break; + + if (!ValidParameter(token, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENPROCESSTOKEN, process, + desired_access, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *token = answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + mozilla::sandboxing::LogAllowed("NtOpenProcessToken"); + return answer.nt_status; + } while (false); + + return status; +} + +NTSTATUS WINAPI +TargetNtOpenProcessTokenEx(NtOpenProcessTokenExFunction orig_OpenProcessTokenEx, + HANDLE process, + ACCESS_MASK desired_access, + ULONG handle_attributes, + PHANDLE token) { + NTSTATUS status = orig_OpenProcessTokenEx(process, desired_access, + handle_attributes, token); + if (NT_SUCCESS(status)) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenProcessTokenEx"); + do { + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + break; + + if (CURRENT_PROCESS != process) + break; + + if (!ValidParameter(token, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENPROCESSTOKENEX, process, + desired_access, handle_attributes, &answer); + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + return answer.nt_status; + + __try { + // Write the output parameters. + *token = answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + mozilla::sandboxing::LogAllowed("NtOpenProcessTokenEx"); + return answer.nt_status; + } while (false); + + return status; +} + +BOOL WINAPI TargetCreateProcessW(CreateProcessWFunction orig_CreateProcessW, + LPCWSTR application_name, + LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information) { + if (SandboxFactory::GetTargetServices()->GetState()->IsCsrssConnected() && + orig_CreateProcessW(application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, + environment, current_directory, startup_info, + process_information)) { + return true; + } + + mozilla::sandboxing::LogBlocked("CreateProcessW", application_name); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return false; + + // Don't call GetLastError before InitCalled() succeeds because kernel32 may + // not be mapped yet. + DWORD original_error = ::GetLastError(); + + do { + if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + const wchar_t* cur_dir = nullptr; + + wchar_t this_current_directory[MAX_PATH]; + DWORD result = ::GetCurrentDirectory(MAX_PATH, this_current_directory); + if (0 != result && result < MAX_PATH) + cur_dir = this_current_directory; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + InOutCountedBuffer proc_info(process_information, + sizeof(PROCESS_INFORMATION)); + + ResultCode code = + CrossCall(ipc, IpcTag::CREATEPROCESSW, application_name, command_line, + cur_dir, current_directory, proc_info, &answer); + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + if (ERROR_SUCCESS != answer.win32_result) + return false; + + mozilla::sandboxing::LogAllowed("CreateProcessW", application_name); + return true; + } while (false); + + ::SetLastError(original_error); + return false; +} + +BOOL WINAPI TargetCreateProcessA(CreateProcessAFunction orig_CreateProcessA, + LPCSTR application_name, + LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information) { + if (SandboxFactory::GetTargetServices()->GetState()->IsCsrssConnected() && + orig_CreateProcessA(application_name, command_line, process_attributes, + thread_attributes, inherit_handles, flags, + environment, current_directory, startup_info, + process_information)) { + return true; + } + + mozilla::sandboxing::LogBlocked("CreateProcessA", application_name); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return false; + + // Don't call GetLastError before InitCalled() succeeds because kernel32 may + // not be mapped yet. + DWORD original_error = ::GetLastError(); + + do { + if (!ValidParameter(process_information, sizeof(PROCESS_INFORMATION), + WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + // Convert the input params to unicode. + UNICODE_STRING* cmd_unicode = nullptr; + UNICODE_STRING* app_unicode = nullptr; + UNICODE_STRING* cwd_unicode = nullptr; + if (command_line) { + cmd_unicode = AnsiToUnicode(command_line); + if (!cmd_unicode) + break; + } + + if (application_name) { + app_unicode = AnsiToUnicode(application_name); + if (!app_unicode) { + operator delete(cmd_unicode, NT_ALLOC); + break; + } + } + + if (current_directory) { + cwd_unicode = AnsiToUnicode(current_directory); + if (!cwd_unicode) { + operator delete(cmd_unicode, NT_ALLOC); + operator delete(app_unicode, NT_ALLOC); + break; + } + } + + const wchar_t* cmd_line = cmd_unicode ? cmd_unicode->Buffer : nullptr; + const wchar_t* app_name = app_unicode ? app_unicode->Buffer : nullptr; + const wchar_t* cwd = cwd_unicode ? cwd_unicode->Buffer : nullptr; + const wchar_t* cur_dir = nullptr; + + wchar_t target_current_directory[MAX_PATH]; + DWORD result = ::GetCurrentDirectory(MAX_PATH, target_current_directory); + if (0 != result && result < MAX_PATH) + cur_dir = target_current_directory; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + InOutCountedBuffer proc_info(process_information, + sizeof(PROCESS_INFORMATION)); + + ResultCode code = CrossCall(ipc, IpcTag::CREATEPROCESSW, app_name, cmd_line, + cur_dir, cwd, proc_info, &answer); + + operator delete(cmd_unicode, NT_ALLOC); + operator delete(app_unicode, NT_ALLOC); + operator delete(cwd_unicode, NT_ALLOC); + + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + if (ERROR_SUCCESS != answer.win32_result) + return false; + + mozilla::sandboxing::LogAllowed("CreateProcessA", application_name); + return true; + } while (false); + + ::SetLastError(original_error); + return false; +} + +HANDLE WINAPI TargetCreateThread(CreateThreadFunction orig_CreateThread, + LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + LPVOID parameter, + DWORD creation_flags, + LPDWORD thread_id) { + HANDLE hThread = nullptr; + + TargetServices* target_services = SandboxFactory::GetTargetServices(); + if (!target_services || target_services->GetState()->IsCsrssConnected()) { + hThread = orig_CreateThread(thread_attributes, stack_size, start_address, + parameter, creation_flags, thread_id); + if (hThread) + return hThread; + } + + DWORD original_error = ::GetLastError(); + do { + if (!target_services) + break; + + // We don't trust that the IPC can work this early. + if (!target_services->GetState()->InitCalled()) + break; + + __try { + if (thread_id && !ValidParameter(thread_id, sizeof(*thread_id), WRITE)) + break; + + if (!start_address) + break; + // We don't support thread_attributes not being null. + if (thread_attributes) + break; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + // NOTE: we don't pass the thread_attributes through. This matches the + // approach in CreateProcess and in CreateThreadInternal(). + ResultCode code = CrossCall(ipc, IpcTag::CREATETHREAD, + reinterpret_cast<LPVOID>(stack_size), + reinterpret_cast<LPVOID>(start_address), + parameter, creation_flags, &answer); + if (SBOX_ALL_OK != code) + break; + + ::SetLastError(answer.win32_result); + if (ERROR_SUCCESS != answer.win32_result) + return nullptr; + + __try { + if (thread_id) + *thread_id = ::GetThreadId(answer.handle); + return answer.handle; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + ::SetLastError(original_error); + return nullptr; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_interception.h b/security/sandbox/chromium/sandbox/win/src/process_thread_interception.h new file mode 100644 index 0000000000..2608d7d31e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_interception.h @@ -0,0 +1,101 @@ +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_PROCESS_THREAD_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_PROCESS_THREAD_INTERCEPTION_H_ + +#include <windows.h> + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +namespace { + +using CreateProcessWFunction = decltype(&::CreateProcessW); + +using CreateProcessAFunction = decltype(&::CreateProcessA); + +using CreateThreadFunction = decltype(&::CreateThread); + +using GetUserDefaultLCIDFunction = decltype(&::GetUserDefaultLCID); + +} // namespace + +extern "C" { + +// Interception of NtOpenThread on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenThread(NtOpenThreadFunction orig_OpenThread, + PHANDLE thread, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcess on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcess(NtOpenProcessFunction orig_OpenProcess, + PHANDLE process, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PCLIENT_ID client_id); + +// Interception of NtOpenProcessToken on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessToken(NtOpenProcessTokenFunction orig_OpenProcessToken, + HANDLE process, + ACCESS_MASK desired_access, + PHANDLE token); + +// Interception of NtOpenProcessTokenEx on the child process. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenProcessTokenEx(NtOpenProcessTokenExFunction orig_OpenProcessTokenEx, + HANDLE process, + ACCESS_MASK desired_access, + ULONG handle_attributes, + PHANDLE token); + +// Interception of CreateProcessW and A in kernel32.dll. +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessW(CreateProcessWFunction orig_CreateProcessW, + LPCWSTR application_name, + LPWSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCWSTR current_directory, + LPSTARTUPINFOW startup_info, + LPPROCESS_INFORMATION process_information); + +SANDBOX_INTERCEPT BOOL WINAPI +TargetCreateProcessA(CreateProcessAFunction orig_CreateProcessA, + LPCSTR application_name, + LPSTR command_line, + LPSECURITY_ATTRIBUTES process_attributes, + LPSECURITY_ATTRIBUTES thread_attributes, + BOOL inherit_handles, + DWORD flags, + LPVOID environment, + LPCSTR current_directory, + LPSTARTUPINFOA startup_info, + LPPROCESS_INFORMATION process_information); + +// Interception of CreateThread in kernel32.dll. +SANDBOX_INTERCEPT HANDLE WINAPI +TargetCreateThread(CreateThreadFunction orig_CreateThread, + LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + LPVOID parameter, + DWORD creation_flags, + LPDWORD thread_id); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_PROCESS_THREAD_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_policy.cc b/security/sandbox/chromium/sandbox/win/src/process_thread_policy.cc new file mode 100644 index 0000000000..20146a83df --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_policy.cc @@ -0,0 +1,269 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/process_thread_policy.h" + +#include <stdint.h> + +#include <memory> +#include <string> + +#include "base/memory/free_deleter.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// These are the only safe rights that can be given to a sandboxed +// process for the process created by the broker. All others are potential +// vectors of privilege elevation. +const DWORD kProcessRights = SYNCHRONIZE | PROCESS_QUERY_INFORMATION | + PROCESS_QUERY_LIMITED_INFORMATION | + PROCESS_TERMINATE | PROCESS_SUSPEND_RESUME; + +const DWORD kThreadRights = SYNCHRONIZE | THREAD_TERMINATE | + THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION | + THREAD_QUERY_LIMITED_INFORMATION | + THREAD_SET_LIMITED_INFORMATION; + +// Creates a child process and duplicates the handles to 'target_process'. The +// remaining parameters are the same as CreateProcess(). +bool CreateProcessExWHelper(HANDLE target_process, + bool give_full_access, + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + bool bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation) { + if (!::CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, + lpThreadAttributes, bInheritHandles, dwCreationFlags, + lpEnvironment, lpCurrentDirectory, lpStartupInfo, + lpProcessInformation)) { + return false; + } + + DWORD process_access = kProcessRights; + DWORD thread_access = kThreadRights; + if (give_full_access) { + process_access = PROCESS_ALL_ACCESS; + thread_access = THREAD_ALL_ACCESS; + } + if (!::DuplicateHandle(::GetCurrentProcess(), lpProcessInformation->hProcess, + target_process, &lpProcessInformation->hProcess, + process_access, false, DUPLICATE_CLOSE_SOURCE)) { + ::CloseHandle(lpProcessInformation->hThread); + return false; + } + if (!::DuplicateHandle(::GetCurrentProcess(), lpProcessInformation->hThread, + target_process, &lpProcessInformation->hThread, + thread_access, false, DUPLICATE_CLOSE_SOURCE)) { + return false; + } + return true; +} + +} // namespace + +namespace sandbox { + +bool ProcessPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + std::unique_ptr<PolicyRule> process; + switch (semantics) { + case TargetPolicy::PROCESS_MIN_EXEC: { + process.reset(new PolicyRule(GIVE_READONLY)); + break; + }; + case TargetPolicy::PROCESS_ALL_EXEC: { + process.reset(new PolicyRule(GIVE_ALLACCESS)); + break; + }; + default: { return false; }; + } + + if (!process->AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IpcTag::CREATEPROCESSW, process.get())) { + return false; + } + return true; +} + +NTSTATUS ProcessPolicy::OpenThreadAction(const ClientInfo& client_info, + uint32_t desired_access, + uint32_t thread_id, + HANDLE* handle) { + *handle = nullptr; + + NtOpenThreadFunction NtOpenThread = nullptr; + ResolveNTFunctionPtr("NtOpenThread", &NtOpenThread); + + OBJECT_ATTRIBUTES attributes = {0}; + attributes.Length = sizeof(attributes); + CLIENT_ID client_id = {0}; + client_id.UniqueProcess = + reinterpret_cast<PVOID>(static_cast<ULONG_PTR>(client_info.process_id)); + client_id.UniqueThread = + reinterpret_cast<PVOID>(static_cast<ULONG_PTR>(thread_id)); + + HANDLE local_handle = nullptr; + NTSTATUS status = + NtOpenThread(&local_handle, desired_access, &attributes, &client_id); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessAction(const ClientInfo& client_info, + uint32_t desired_access, + uint32_t process_id, + HANDLE* handle) { + *handle = nullptr; + + NtOpenProcessFunction NtOpenProcess = nullptr; + ResolveNTFunctionPtr("NtOpenProcess", &NtOpenProcess); + + if (client_info.process_id != process_id) + return STATUS_ACCESS_DENIED; + + OBJECT_ATTRIBUTES attributes = {0}; + attributes.Length = sizeof(attributes); + CLIENT_ID client_id = {0}; + client_id.UniqueProcess = + reinterpret_cast<PVOID>(static_cast<ULONG_PTR>(client_info.process_id)); + HANDLE local_handle = nullptr; + NTSTATUS status = + NtOpenProcess(&local_handle, desired_access, &attributes, &client_id); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessTokenAction(const ClientInfo& client_info, + HANDLE process, + uint32_t desired_access, + HANDLE* handle) { + *handle = nullptr; + NtOpenProcessTokenFunction NtOpenProcessToken = nullptr; + ResolveNTFunctionPtr("NtOpenProcessToken", &NtOpenProcessToken); + + if (CURRENT_PROCESS != process) + return STATUS_ACCESS_DENIED; + + HANDLE local_handle = nullptr; + NTSTATUS status = + NtOpenProcessToken(client_info.process, desired_access, &local_handle); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + return status; +} + +NTSTATUS ProcessPolicy::OpenProcessTokenExAction(const ClientInfo& client_info, + HANDLE process, + uint32_t desired_access, + uint32_t attributes, + HANDLE* handle) { + *handle = nullptr; + NtOpenProcessTokenExFunction NtOpenProcessTokenEx = nullptr; + ResolveNTFunctionPtr("NtOpenProcessTokenEx", &NtOpenProcessTokenEx); + + if (CURRENT_PROCESS != process) + return STATUS_ACCESS_DENIED; + + HANDLE local_handle = nullptr; + NTSTATUS status = NtOpenProcessTokenEx(client_info.process, desired_access, + attributes, &local_handle); + if (NT_SUCCESS(status)) { + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + } + return status; +} + +DWORD ProcessPolicy::CreateProcessWAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& app_name, + const std::wstring& command_line, + const std::wstring& current_dir, + PROCESS_INFORMATION* process_info) { + // The only action supported is ASK_BROKER which means create the process. + if (GIVE_ALLACCESS != eval_result && GIVE_READONLY != eval_result) { + return ERROR_ACCESS_DENIED; + } + + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + std::unique_ptr<wchar_t, base::FreeDeleter> cmd_line( + _wcsdup(command_line.c_str())); + + bool should_give_full_access = (GIVE_ALLACCESS == eval_result); + + const wchar_t* cwd = current_dir.c_str(); + if (current_dir.empty()) + cwd = nullptr; + + if (!CreateProcessExWHelper(client_info.process, should_give_full_access, + app_name.c_str(), cmd_line.get(), nullptr, + nullptr, false, 0, nullptr, cwd, &startup_info, + process_info)) { + return ERROR_ACCESS_DENIED; + } + return ERROR_SUCCESS; +} + +DWORD ProcessPolicy::CreateThreadAction( + const ClientInfo& client_info, + const SIZE_T stack_size, + const LPTHREAD_START_ROUTINE start_address, + const LPVOID parameter, + const DWORD creation_flags, + LPDWORD thread_id, + HANDLE* handle) { + *handle = nullptr; + HANDLE local_handle = + ::CreateRemoteThread(client_info.process, nullptr, stack_size, + start_address, parameter, creation_flags, thread_id); + if (!local_handle) { + return ::GetLastError(); + } + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return ERROR_ACCESS_DENIED; + } + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/process_thread_policy.h b/security/sandbox/chromium/sandbox/win/src/process_thread_policy.h new file mode 100644 index 0000000000..f6f96dd0ff --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/process_thread_policy.h @@ -0,0 +1,91 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ +#define SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to process execution. +class ProcessPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level. + // policy rule for process creation + // 'name' is the executable to be spawn. + // 'semantics' is the desired semantics. + // 'policy' is the policy generator to which the rules are going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Opens a thread from the child process and returns the handle. + // client_info contains the information about the child process, + // desired_access is the access requested by the child and thread_id + // is the thread_id to be opened. + // The function returns the return value of NtOpenThread. + static NTSTATUS OpenThreadAction(const ClientInfo& client_info, + uint32_t desired_access, + uint32_t thread_id, + HANDLE* handle); + + // Opens the process id passed in and returns the duplicated handle to + // the child. We only allow the child processes to open themselves. Any other + // pid open is denied. + static NTSTATUS OpenProcessAction(const ClientInfo& client_info, + uint32_t desired_access, + uint32_t process_id, + HANDLE* handle); + + // Opens the token associated with the process and returns the duplicated + // handle to the child. We only allow the child processes to open its own + // token (using ::GetCurrentProcess()). + static NTSTATUS OpenProcessTokenAction(const ClientInfo& client_info, + HANDLE process, + uint32_t desired_access, + HANDLE* handle); + + // Opens the token associated with the process and returns the duplicated + // handle to the child. We only allow the child processes to open its own + // token (using ::GetCurrentProcess()). + static NTSTATUS OpenProcessTokenExAction(const ClientInfo& client_info, + HANDLE process, + uint32_t desired_access, + uint32_t attributes, + HANDLE* handle); + + // Processes a 'CreateProcessW()' request from the target. + // 'client_info' : the target process that is making the request. + // 'eval_result' : The desired policy action to accomplish. + // 'app_name' : The full path of the process to be created. + // 'command_line' : The command line passed to the created process. + // 'current_dir' : The CWD with which to spawn the child process. + static DWORD CreateProcessWAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& app_name, + const std::wstring& command_line, + const std::wstring& current_dir, + PROCESS_INFORMATION* process_info); + + // Processes a 'CreateThread()' request from the target. + // 'client_info' : the target process that is making the request. + static DWORD CreateThreadAction(const ClientInfo& client_info, + SIZE_T stack_size, + LPTHREAD_START_ROUTINE start_address, + PVOID parameter, + DWORD creation_flags, + LPDWORD thread_id, + HANDLE* handle); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_PROCESS_THREAD_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.cc new file mode 100644 index 0000000000..26d2bb3ffc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/registry_dispatcher.h" + +#include <stdint.h> + +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/registry_interception.h" +#include "sandbox/win/src/registry_policy.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +// Builds a path using the root directory and the name. +bool GetCompletePath(HANDLE root, + const std::wstring& name, + std::wstring* complete_name) { + if (root) { + if (!sandbox::GetPathFromHandle(root, complete_name)) + return false; + + *complete_name += L"\\"; + *complete_name += name; + } else { + *complete_name = name; + } + + return true; +} + +} // namespace + +namespace sandbox { + +RegistryDispatcher::RegistryDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::NTCREATEKEY, + {WCHAR_TYPE, UINT32_TYPE, VOIDPTR_TYPE, UINT32_TYPE, UINT32_TYPE, + UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&RegistryDispatcher::NtCreateKey)}; + + static const IPCCall open_params = { + {IpcTag::NTOPENKEY, {WCHAR_TYPE, UINT32_TYPE, VOIDPTR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&RegistryDispatcher::NtOpenKey)}; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_params); +} + +bool RegistryDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (IpcTag::NTCREATEKEY == service) + return INTERCEPT_NT(manager, NtCreateKey, CREATE_KEY_ID, 32); + + if (IpcTag::NTOPENKEY == service) { + bool result = INTERCEPT_NT(manager, NtOpenKey, OPEN_KEY_ID, 16); + result &= INTERCEPT_NT(manager, NtOpenKeyEx, OPEN_KEY_EX_ID, 20); + return result; + } + + return false; +} + +bool RegistryDispatcher::NtCreateKey(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + HANDLE root, + uint32_t desired_access, + uint32_t title_index, + uint32_t create_options) { + base::win::ScopedHandle root_handle; + std::wstring real_path = *name; + + // If there is a root directory, we need to duplicate the handle to make + // it valid in this process. + if (root) { + if (!::DuplicateHandle(ipc->client_info->process, root, + ::GetCurrentProcess(), &root, 0, false, + DUPLICATE_SAME_ACCESS)) + return false; + + root_handle.Set(root); + } + + if (!GetCompletePath(root, *name, &real_path)) + return false; + + const wchar_t* regname = real_path.c_str(); + CountedParameterSet<OpenKey> params; + params[OpenKey::NAME] = ParamPickerMake(regname); + params[OpenKey::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTCREATEKEY, params.GetBase()); + + HANDLE handle; + NTSTATUS nt_status; + ULONG disposition = 0; + if (!RegistryPolicy::CreateKeyAction( + result, *ipc->client_info, *name, attributes, root, desired_access, + title_index, create_options, &handle, &nt_status, &disposition)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.extended[0].unsigned_int = disposition; + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +bool RegistryDispatcher::NtOpenKey(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + HANDLE root, + uint32_t desired_access) { + base::win::ScopedHandle root_handle; + std::wstring real_path = *name; + + // If there is a root directory, we need to duplicate the handle to make + // it valid in this process. + if (root) { + if (!::DuplicateHandle(ipc->client_info->process, root, + ::GetCurrentProcess(), &root, 0, false, + DUPLICATE_SAME_ACCESS)) + return false; + root_handle.Set(root); + } + + if (!GetCompletePath(root, *name, &real_path)) + return false; + + const wchar_t* regname = real_path.c_str(); + CountedParameterSet<OpenKey> params; + params[OpenKey::NAME] = ParamPickerMake(regname); + params[OpenKey::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTOPENKEY, params.GetBase()); + HANDLE handle; + NTSTATUS nt_status; + if (!RegistryPolicy::OpenKeyAction(result, *ipc->client_info, *name, + attributes, root, desired_access, &handle, + &nt_status)) { + ipc->return_info.nt_status = STATUS_ACCESS_DENIED; + return true; + } + + // Return operation status on the IPC. + ipc->return_info.nt_status = nt_status; + ipc->return_info.handle = handle; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.h new file mode 100644 index 0000000000..c23b95098b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_dispatcher.h @@ -0,0 +1,51 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_REGISTRY_DISPATCHER_H_ +#define SANDBOX_SRC_REGISTRY_DISPATCHER_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles registry-related IPC calls. +class RegistryDispatcher : public Dispatcher { + public: + explicit RegistryDispatcher(PolicyBase* policy_base); + ~RegistryDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to NtCreateKey in the target. + bool NtCreateKey(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + HANDLE root, + uint32_t desired_access, + uint32_t title_index, + uint32_t create_options); + + // Processes IPC requests coming from calls to NtOpenKey in the target. + bool NtOpenKey(IPCInfo* ipc, + std::wstring* name, + uint32_t attributes, + HANDLE root, + uint32_t desired_access); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(RegistryDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_REGISTRY_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/registry_interception.cc b/security/sandbox/chromium/sandbox/win/src/registry_interception.cc new file mode 100644 index 0000000000..d8bb82949b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_interception.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/registry_interception.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +NTSTATUS WINAPI TargetNtCreateKey(NtCreateKeyFunction orig_CreateKey, + PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG title_index, + PUNICODE_STRING class_name, + ULONG create_options, + PULONG disposition) { + // Check if the process can create it first. + NTSTATUS status = + orig_CreateKey(key, desired_access, object_attributes, title_index, + class_name, create_options, disposition); + if (NT_SUCCESS(status)) + return status; + + if (STATUS_OBJECT_NAME_NOT_FOUND != status) { + mozilla::sandboxing::LogBlocked("NtCreateKey", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(key, sizeof(HANDLE), WRITE)) + break; + + if (disposition && !ValidParameter(disposition, sizeof(ULONG), WRITE)) + break; + + // At this point we don't support class_name. + if (class_name && class_name->Buffer && class_name->Length) + break; + + // We don't support creating link keys, volatile keys and backup/restore. + if (create_options) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + HANDLE root_directory = 0; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + &root_directory); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t desired_access_uint32 = desired_access; + CountedParameterSet<OpenKey> params; + params[OpenKey::ACCESS] = ParamPickerMake(desired_access_uint32); + + bool query_broker = false; + { + std::unique_ptr<wchar_t, NtAllocDeleter> full_name; + const wchar_t* name_ptr = name.get(); + const wchar_t* full_name_ptr = nullptr; + + if (root_directory) { + ret = sandbox::AllocAndGetFullPath(root_directory, name.get(), + &full_name); + if (!NT_SUCCESS(ret) || !full_name) + break; + full_name_ptr = full_name.get(); + params[OpenKey::NAME] = ParamPickerMake(full_name_ptr); + } else { + params[OpenKey::NAME] = ParamPickerMake(name_ptr); + } + + query_broker = QueryBroker(IpcTag::NTCREATEKEY, params.GetBase()); + } + + if (!query_broker) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + ResultCode code = CrossCall(ipc, IpcTag::NTCREATEKEY, name.get(), + attributes, root_directory, desired_access, + title_index, create_options, &answer); + + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // TODO(nsylvain): We should return answer.nt_status here instead + // of status. We can do this only after we checked the policy. + // otherwise we will returns ACCESS_DENIED for all paths + // that are not specified by a policy, even though your token allows + // access to that path, and the original call had a more meaningful + // error. Bug 4369 + break; + + __try { + *key = answer.handle; + + if (disposition) + *disposition = answer.extended[0].unsigned_int; + + status = answer.nt_status; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtCreateKey", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI CommonNtOpenKey(NTSTATUS status, + PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(key, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes; + HANDLE root_directory; + NTSTATUS ret = AllocAndCopyName(object_attributes, &name, &attributes, + &root_directory); + if (!NT_SUCCESS(ret) || !name) + break; + + uint32_t desired_access_uint32 = desired_access; + CountedParameterSet<OpenKey> params; + params[OpenKey::ACCESS] = ParamPickerMake(desired_access_uint32); + + bool query_broker = false; + { + std::unique_ptr<wchar_t, NtAllocDeleter> full_name; + const wchar_t* name_ptr = name.get(); + const wchar_t* full_name_ptr = nullptr; + + if (root_directory) { + ret = sandbox::AllocAndGetFullPath(root_directory, name.get(), + &full_name); + if (!NT_SUCCESS(ret) || !full_name) + break; + full_name_ptr = full_name.get(); + params[OpenKey::NAME] = ParamPickerMake(full_name_ptr); + } else { + params[OpenKey::NAME] = ParamPickerMake(name_ptr); + } + + query_broker = QueryBroker(IpcTag::NTOPENKEY, params.GetBase()); + } + + if (!query_broker) + break; + + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + ResultCode code = CrossCall(ipc, IpcTag::NTOPENKEY, name.get(), attributes, + root_directory, desired_access, &answer); + + if (SBOX_ALL_OK != code) + break; + + if (!NT_SUCCESS(answer.nt_status)) + // TODO(nsylvain): We should return answer.nt_status here instead + // of status. We can do this only after we checked the policy. + // otherwise we will returns ACCESS_DENIED for all paths + // that are not specified by a policy, even though your token allows + // access to that path, and the original call had a more meaningful + // error. Bug 4369 + break; + + __try { + *key = answer.handle; + status = answer.nt_status; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtOpenKey[Ex]", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenKey(NtOpenKeyFunction orig_OpenKey, + PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + // Check if the process can open it first. + NTSTATUS status = orig_OpenKey(key, desired_access, object_attributes); + if (NT_SUCCESS(status)) + return status; + + if (STATUS_OBJECT_NAME_NOT_FOUND != status) { + mozilla::sandboxing::LogBlocked("NtOpenKey", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } + + return CommonNtOpenKey(status, key, desired_access, object_attributes); +} + +NTSTATUS WINAPI TargetNtOpenKeyEx(NtOpenKeyExFunction orig_OpenKeyEx, + PHANDLE key, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + ULONG open_options) { + // Check if the process can open it first. + NTSTATUS status = + orig_OpenKeyEx(key, desired_access, object_attributes, open_options); + + // We do not support open_options at this time. The 2 current known values + // are REG_OPTION_CREATE_LINK, to open a symbolic link, and + // REG_OPTION_BACKUP_RESTORE to open the key with special privileges. + if (NT_SUCCESS(status) || open_options != 0) + return status; + + if (STATUS_OBJECT_NAME_NOT_FOUND != status) { + mozilla::sandboxing::LogBlocked("NtOpenKeyEx", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } + + return CommonNtOpenKey(status, key, desired_access, object_attributes); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/registry_interception.h b/security/sandbox/chromium/sandbox/win/src/registry_interception.h new file mode 100644 index 0000000000..f47e56ce39 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_interception.h @@ -0,0 +1,38 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_REGISTRY_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_REGISTRY_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interception of NtCreateKey on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtCreateKey( + NtCreateKeyFunction orig_CreateKey, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG title_index, + PUNICODE_STRING class_name, ULONG create_options, PULONG disposition); + +// Interception of NtOpenKey on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKey( + NtOpenKeyFunction orig_OpenKey, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +// Interception of NtOpenKeyEx on the child process. +// It should never be called directly +SANDBOX_INTERCEPT NTSTATUS WINAPI TargetNtOpenKeyEx( + NtOpenKeyExFunction orig_OpenKeyEx, PHANDLE key, ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, ULONG open_options); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_REGISTRY_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/registry_policy.cc b/security/sandbox/chromium/sandbox/win/src/registry_policy.cc new file mode 100644 index 0000000000..212ff713c4 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_policy.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/registry_policy.h" + +#include <stdint.h> + +#include <string> + +#include "base/logging.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +static const uint32_t kAllowedRegFlags = + KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_READ | + GENERIC_READ | GENERIC_EXECUTE | READ_CONTROL | KEY_WOW64_64KEY | + KEY_WOW64_32KEY; + +// Opens the key referenced by |obj_attributes| with |access| and +// checks what permission was given. Remove the WRITE flags and update +// |access| with the new value. +NTSTATUS TranslateMaximumAllowed(OBJECT_ATTRIBUTES* obj_attributes, + DWORD* access) { + NtOpenKeyFunction NtOpenKey = nullptr; + ResolveNTFunctionPtr("NtOpenKey", &NtOpenKey); + + NtCloseFunction NtClose = nullptr; + ResolveNTFunctionPtr("NtClose", &NtClose); + + NtQueryObjectFunction NtQueryObject = nullptr; + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + // Open the key. + HANDLE handle; + NTSTATUS status = NtOpenKey(&handle, *access, obj_attributes); + if (!NT_SUCCESS(status)) + return status; + + OBJECT_BASIC_INFORMATION info = {0}; + status = NtQueryObject(handle, ObjectBasicInformation, &info, sizeof(info), + nullptr); + CHECK(NT_SUCCESS(NtClose(handle))); + if (!NT_SUCCESS(status)) + return status; + + *access = info.GrantedAccess & kAllowedRegFlags; + return STATUS_SUCCESS; +} + +NTSTATUS NtCreateKeyInTarget(HANDLE* target_key_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + ULONG title_index, + UNICODE_STRING* class_name, + ULONG create_options, + ULONG* disposition, + HANDLE target_process) { + *target_key_handle = nullptr; + NtCreateKeyFunction NtCreateKey = nullptr; + ResolveNTFunctionPtr("NtCreateKey", &NtCreateKey); + + if (MAXIMUM_ALLOWED & desired_access) { + NTSTATUS status = TranslateMaximumAllowed(obj_attributes, &desired_access); + if (!NT_SUCCESS(status)) + return STATUS_ACCESS_DENIED; + } + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = + NtCreateKey(&local_handle, desired_access, obj_attributes, title_index, + class_name, create_options, disposition); + if (!NT_SUCCESS(status)) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, target_process, + target_key_handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +NTSTATUS NtOpenKeyInTarget(HANDLE* target_key_handle, + ACCESS_MASK desired_access, + OBJECT_ATTRIBUTES* obj_attributes, + HANDLE target_process) { + *target_key_handle = nullptr; + NtOpenKeyFunction NtOpenKey = nullptr; + ResolveNTFunctionPtr("NtOpenKey", &NtOpenKey); + + if (MAXIMUM_ALLOWED & desired_access) { + NTSTATUS status = TranslateMaximumAllowed(obj_attributes, &desired_access); + if (!NT_SUCCESS(status)) + return STATUS_ACCESS_DENIED; + } + + HANDLE local_handle = INVALID_HANDLE_VALUE; + NTSTATUS status = NtOpenKey(&local_handle, desired_access, obj_attributes); + + if (!NT_SUCCESS(status)) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, target_process, + target_key_handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return STATUS_SUCCESS; +} + +} // namespace + +namespace sandbox { + +bool RegistryPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + std::wstring resolved_name(name); + if (resolved_name.empty()) { + return false; + } + + if (!ResolveRegistryName(resolved_name, &resolved_name)) + return false; + + name = resolved_name.c_str(); + + EvalResult result = ASK_BROKER; + + PolicyRule open(result); + PolicyRule create(result); + + switch (semantics) { + case TargetPolicy::REG_ALLOW_READONLY: { + // We consider all flags that are not known to be readonly as potentially + // used for write. Here we also support MAXIMUM_ALLOWED, but we are going + // to expand it to read-only before the call. + uint32_t restricted_flags = ~(kAllowedRegFlags | MAXIMUM_ALLOWED); + open.AddNumberMatch(IF_NOT, OpenKey::ACCESS, restricted_flags, AND); + create.AddNumberMatch(IF_NOT, OpenKey::ACCESS, restricted_flags, AND); + break; + } + case TargetPolicy::REG_ALLOW_ANY: { + break; + } + default: { + NOTREACHED(); + return false; + } + } + + if (!create.AddStringMatch(IF, OpenKey::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTCREATEKEY, &create)) { + return false; + } + + if (!open.AddStringMatch(IF, OpenKey::NAME, name, CASE_INSENSITIVE) || + !policy->AddRule(IpcTag::NTOPENKEY, &open)) { + return false; + } + + return true; +} + +bool RegistryPolicy::CreateKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& key, + uint32_t attributes, + HANDLE root_directory, + uint32_t desired_access, + uint32_t title_index, + uint32_t create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG* disposition) { + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + // We don't support creating link keys, volatile keys or backup/restore. + if (create_options) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitObjectAttribs(key, attributes, root_directory, &obj_attributes, &uni_name, + nullptr); + *nt_status = NtCreateKeyInTarget(handle, desired_access, &obj_attributes, + title_index, nullptr, create_options, + disposition, client_info.process); + return true; +} + +bool RegistryPolicy::OpenKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& key, + uint32_t attributes, + HANDLE root_directory, + uint32_t desired_access, + HANDLE* handle, + NTSTATUS* nt_status) { + // The only action supported is ASK_BROKER which means open the requested + // file as specified. + if (ASK_BROKER != eval_result) { + *nt_status = STATUS_ACCESS_DENIED; + return false; + } + + UNICODE_STRING uni_name = {0}; + OBJECT_ATTRIBUTES obj_attributes = {0}; + InitObjectAttribs(key, attributes, root_directory, &obj_attributes, &uni_name, + nullptr); + *nt_status = NtOpenKeyInTarget(handle, desired_access, &obj_attributes, + client_info.process); + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/registry_policy.h b/security/sandbox/chromium/sandbox/win/src/registry_policy.h new file mode 100644 index 0000000000..9a36932869 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_policy.h @@ -0,0 +1,56 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_REGISTRY_POLICY_H__ +#define SANDBOX_SRC_REGISTRY_POLICY_H__ + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to registry policy +class RegistryPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for registry IO, in particular open or create actions. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Performs the desired policy action on a create request with an + // API that is compatible with the IPC-received parameters. + static bool CreateKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& key, + uint32_t attributes, + HANDLE root_directory, + uint32_t desired_access, + uint32_t title_index, + uint32_t create_options, + HANDLE* handle, + NTSTATUS* nt_status, + ULONG* disposition); + + // Performs the desired policy action on an open request with an + // API that is compatible with the IPC-received parameters. + static bool OpenKeyAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& key, + uint32_t attributes, + HANDLE root_directory, + uint32_t desired_access, + HANDLE* handle, + NTSTATUS* nt_status); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_REGISTRY_POLICY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/registry_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/registry_policy_test.cc new file mode 100644 index 0000000000..83ce586877 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/registry_policy_test.cc @@ -0,0 +1,322 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <shlobj.h> + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/registry_policy.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +static const DWORD kAllowedRegFlags = KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | + KEY_NOTIFY | KEY_READ | GENERIC_READ | + GENERIC_EXECUTE | READ_CONTROL; + +#define BINDNTDLL(name) \ + name##Function name = reinterpret_cast<name##Function>( \ + ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), #name)) + +bool IsKeyOpenForRead(HKEY handle) { + BINDNTDLL(NtQueryObject); + + OBJECT_BASIC_INFORMATION info = {0}; + NTSTATUS status = NtQueryObject(handle, ObjectBasicInformation, &info, + sizeof(info), nullptr); + + if (!NT_SUCCESS(status)) + return false; + + if ((info.GrantedAccess & (~kAllowedRegFlags)) != 0) + return false; + return true; +} + +} // namespace + +namespace sandbox { + +SBOX_TESTS_COMMAND int Reg_OpenKey(int argc, wchar_t** argv) { + if (argc != 4) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + REGSAM desired_access = 0; + ULONG options = 0; + if (wcscmp(argv[1], L"read") == 0) { + desired_access = KEY_READ; + } else if (wcscmp(argv[1], L"write") == 0) { + desired_access = KEY_ALL_ACCESS; + } else if (wcscmp(argv[1], L"link") == 0) { + options = REG_OPTION_CREATE_LINK; + desired_access = KEY_ALL_ACCESS; + } else { + desired_access = MAXIMUM_ALLOWED; + } + + HKEY root = GetReservedKeyFromName(argv[2]); + HKEY key; + LRESULT result = 0; + + if (wcscmp(argv[0], L"create") == 0) + result = ::RegCreateKeyEx(root, argv[3], 0, nullptr, options, + desired_access, nullptr, &key, nullptr); + else + result = ::RegOpenKeyEx(root, argv[3], 0, desired_access, &key); + + if (ERROR_SUCCESS == result) { + if (MAXIMUM_ALLOWED == desired_access) { + if (!IsKeyOpenForRead(key)) { + ::RegCloseKey(key); + return SBOX_TEST_FAILED; + } + } + ::RegCloseKey(key); + return SBOX_TEST_SUCCEEDED; + } else if (ERROR_ACCESS_DENIED == result) { + return SBOX_TEST_DENIED; + } + + return SBOX_TEST_FAILED; +} + +TEST(RegistryPolicyTest, TestKeyAnyAccess) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Microsoft")); + + // Tests read access on key allowed for read-write. + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software\\microsoft")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software\\microsoft")); + + if (::IsUserAnAdmin()) { + // Tests write access on key allowed for read-write. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey create write HKEY_LOCAL_MACHINE " + L"software\\microsoft")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey open write HKEY_LOCAL_MACHINE software\\microsoft")); + } + + // Tests subdirectory access on keys where we don't have subdirectory acess. + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey create read " + L"HKEY_LOCAL_MACHINE software\\microsoft\\Windows")); + + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey open read " + L"HKEY_LOCAL_MACHINE software\\microsoft\\windows")); + + // Tests to see if we can create keys where we dont have subdirectory access. + // This is denied. + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest( + L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Microsoft\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Microsoft\\google_unit_tests"); + + // Tests if we need to handle differently the "\\" at the end. + // This is denied. We need to add both rules. + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest( + L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software\\microsoft\\")); + + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest( + L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software\\microsoft\\")); +} + +TEST(RegistryPolicyTest, TestKeyNoAccess) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + // Tests read access where we don't have access at all. + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey create read HKEY_LOCAL_MACHINE software")); + + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey open read HKEY_LOCAL_MACHINE software")); +} + +TEST(RegistryPolicyTest, TestKeyReadOnlyAccess) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + // Tests subdirectory acess on keys where we have subdirectory acess. + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey create read " + L"HKEY_LOCAL_MACHINE software\\Policies\\microsoft")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey open read " + L"HKEY_LOCAL_MACHINE software\\Policies\\microsoft")); + + // Tests to see if we can create keys where we have subdirectory access. + EXPECT_EQ(SBOX_TEST_DENIED, + runner.RunTest( + L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Policies\\google_unit_tests"); +} + +TEST(RegistryPolicyTest, TestKeyAllAccessSubDir) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + if (::IsUserAnAdmin()) { + // Tests to see if we can create keys where we have subdirectory access. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey create write " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + RegDeleteKey(HKEY_LOCAL_MACHINE, L"software\\Policies\\google_unit_tests"); + } +} + +TEST(RegistryPolicyTest, TestKeyCreateLink) { + TestRunner runner; + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Policies\\*")); + + // Tests to see if we can create a registry link key. + // NOTE: In theory here we should make sure to check for SBOX_TEST_DENIED + // instead of !SBOX_TEST_SUCCEEDED, but unfortunately the result is not + // access denied. Internally RegCreateKeyEx (At least on Vista 64) tries to + // create the link, and we return successfully access denied, then, it + // decides to try to break the path in multiple chunks, and create the links + // one by one. In this scenario, it tries to create "HKLM\Software" as a + // link key, which obviously fail with STATUS_OBJECT_NAME_COLLISION, and + // this is what is returned to the user. + EXPECT_NE(SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey create link " + L"HKEY_LOCAL_MACHINE software\\Policies\\google_unit_tests")); + + // In case our code fails, and the call works, we need to delete the new + // link. There is no api for this, so we need to use the NT call. + HKEY key = nullptr; + LRESULT result = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"software\\Policies\\google_unit_tests", + REG_OPTION_OPEN_LINK, MAXIMUM_ALLOWED, &key); + + if (!result) { + HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); + NtDeleteKeyFunction NtDeleteKey = reinterpret_cast<NtDeleteKeyFunction>( + GetProcAddress(ntdll, "NtDeleteKey")); + NtDeleteKey(key); + } +} + +TEST(RegistryPolicyTest, TestKeyReadOnlyHKCU) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER\\Software")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_USERS\\.default")); + + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_USERS\\.default\\software")); + + // Tests read access where we only have read-only access. + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey create read HKEY_CURRENT_USER software")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest(L"Reg_OpenKey open read HKEY_CURRENT_USER software")); + + // Tests write access where we only have read-only acess. + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey create write HKEY_CURRENT_USER software")); + + EXPECT_EQ( + SBOX_TEST_DENIED, + runner.RunTest(L"Reg_OpenKey open write HKEY_CURRENT_USER software")); + + // Tests maximum allowed access where we only have read-only access. + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey create maximum_allowed HKEY_CURRENT_USER software")); + + EXPECT_EQ( + SBOX_TEST_SUCCEEDED, + runner.RunTest( + L"Reg_OpenKey open maximum_allowed HKEY_CURRENT_USER software")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/resolver.cc b/security/sandbox/chromium/sandbox/win/src/resolver.cc new file mode 100644 index 0000000000..6ed20e9b51 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/resolver.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/resolver.h" + +#include <stddef.h> + +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS ResolverThunk::Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes) { + if (!thunk_storage || 0 == storage_bytes || !target_module || !target_name) + return STATUS_INVALID_PARAMETER; + + if (storage_bytes < GetThunkSize()) + return STATUS_BUFFER_TOO_SMALL; + + NTSTATUS ret = STATUS_SUCCESS; + if (!interceptor_entry_point) { + ret = ResolveInterceptor(interceptor_module, interceptor_name, + &interceptor_entry_point); + if (!NT_SUCCESS(ret)) + return ret; + } + + ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + interceptor_ = interceptor_entry_point; + + return ret; +} + +NTSTATUS ResolverThunk::ResolveInterceptor(const void* interceptor_module, + const char* interceptor_name, + const void** address) { + DCHECK_NT(address); + if (!interceptor_module) + return STATUS_INVALID_PARAMETER; + + base::win::PEImage pe(interceptor_module); + if (!pe.VerifyMagic()) + return STATUS_INVALID_IMAGE_FORMAT; + + *address = reinterpret_cast<void*>(pe.GetProcAddress(interceptor_name)); + + if (!(*address)) + return STATUS_PROCEDURE_NOT_FOUND; + + return STATUS_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/resolver.h b/security/sandbox/chromium/sandbox/win/src/resolver.h new file mode 100644 index 0000000000..3ce427b74b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/resolver.h @@ -0,0 +1,107 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_RESOLVER_H_ +#define SANDBOX_WIN_SRC_RESOLVER_H_ + +// Defines ResolverThunk, the interface for classes that perform interceptions. +// For more details see +// http://dev.chromium.org/developers/design-documents/sandbox . + +#include <stddef.h> + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +// A resolver is the object in charge of performing the actual interception of +// a function. There should be a concrete implementation of a resolver roughly +// per type of interception. +class ResolverThunk { + public: + ResolverThunk() {} + virtual ~ResolverThunk() {} + + // Performs the actual interception of a function. + // target_name is an exported function from the module loaded at + // target_module, and must be replaced by interceptor_name, exported from + // interceptor_module. interceptor_entry_point can be provided instead of + // interceptor_name / interceptor_module. + // thunk_storage must point to a buffer on the child's address space, to hold + // the patch thunk, and related data. If provided, storage_used will receive + // the number of bytes used from thunk_storage. + // + // Example: (without error checking) + // + // size_t size = resolver.GetThunkSize(); + // char* buffer = ::VirtualAllocEx(child_process, nullptr, size, + // MEM_COMMIT, PAGE_READWRITE); + // resolver.Setup(ntdll_module, nullptr, L"NtCreateFile", nullptr, + // &MyReplacementFunction, buffer, size, nullptr); + // + // In general, the idea is to allocate a single big buffer for all + // interceptions on the same dll, and call Setup n times. + // WARNING: This means that any data member that is specific to a single + // interception must be reset within this method. + virtual NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) = 0; + + // Gets the address of function_name inside module (main exe). + virtual NTSTATUS ResolveInterceptor(const void* module, + const char* function_name, + const void** address); + + // Gets the address of an exported function_name inside module. + virtual NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address); + + // Gets the required buffer size for this type of thunk. + virtual size_t GetThunkSize() const = 0; + + protected: + // Performs basic initialization on behalf of a concrete instance of a + // resolver. That is, parameter validation and resolution of the target + // and the interceptor into the member variables. + // + // target_name is an exported function from the module loaded at + // target_module, and must be replaced by interceptor_name, exported from + // interceptor_module. interceptor_entry_point can be provided instead of + // interceptor_name / interceptor_module. + // thunk_storage must point to a buffer on the child's address space, to hold + // the patch thunk, and related data. + virtual NTSTATUS Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes); + + // Gets the required buffer size for the internal part of the thunk. + size_t GetInternalThunkSize() const; + + // Initializes the internal part of the thunk. + // interceptor is the function to be called instead of original_function. + bool SetInternalThunk(void* storage, size_t storage_bytes, + const void* original_function, const void* interceptor); + + // Holds the resolved interception target. + void* target_; + // Holds the resolved interception interceptor. + const void* interceptor_; + + DISALLOW_COPY_AND_ASSIGN(ResolverThunk); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_RESOLVER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/resolver_32.cc b/security/sandbox/chromium/sandbox/win/src/resolver_32.cc new file mode 100644 index 0000000000..ff78f53fb1 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/resolver_32.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/resolver.h" + +#include <stddef.h> + +// For placement new. This file must not depend on the CRT at runtime, but +// placement operator new is inline. +#include <new> + +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +#pragma pack(push, 1) +struct InternalThunk { + // This struct contains roughly the following code: + // sub esp, 8 // Create working space + // push edx // Save register + // mov edx, [esp + 0xc] // Get return adddress + // mov [esp + 8], edx // Store return address + // mov dword ptr [esp + 0xc], 0x7c401200 // Store extra argument + // mov dword ptr [esp + 4], 0x40010203 // Store address to jump to + // pop edx // Restore register + // ret // Jump to interceptor + // + // This code only modifies esp and eip so it must work with to normal calling + // convention. It is assembled as: + // + // 00 83ec08 sub esp,8 + // 03 52 push edx + // 04 8b54240c mov edx,dword ptr [esp + 0Ch] + // 08 89542408 mov dword ptr [esp + 8], edx + // 0c c744240c0012407c mov dword ptr [esp + 0Ch], 7C401200h + // 14 c744240403020140 mov dword ptr [esp + 4], 40010203h + // 1c 5a pop edx + // 1d c3 ret + InternalThunk() { + opcodes_1 = 0x5208ec83; + opcodes_2 = 0x0c24548b; + opcodes_3 = 0x08245489; + opcodes_4 = 0x0c2444c7; + opcodes_5 = 0x042444c7; + opcodes_6 = 0xc35a; + extra_argument = 0; + interceptor_function = 0; + } + ULONG opcodes_1; // = 0x5208ec83 + ULONG opcodes_2; // = 0x0c24548b + ULONG opcodes_3; // = 0x08245489 + ULONG opcodes_4; // = 0x0c2444c7 + ULONG extra_argument; + ULONG opcodes_5; // = 0x042444c7 + ULONG interceptor_function; + USHORT opcodes_6; // = 0xc35a +}; +#pragma pack(pop) + +} // namespace + +namespace sandbox { + +bool ResolverThunk::SetInternalThunk(void* storage, + size_t storage_bytes, + const void* original_function, + const void* interceptor) { + if (storage_bytes < sizeof(InternalThunk)) + return false; + + InternalThunk* thunk = new (storage) InternalThunk; + +#pragma warning(push) +#pragma warning(disable : 4311) + // These casts generate warnings because they are 32 bit specific. + thunk->interceptor_function = reinterpret_cast<ULONG>(interceptor); + thunk->extra_argument = reinterpret_cast<ULONG>(original_function); +#pragma warning(pop) + + return true; +} + +size_t ResolverThunk::GetInternalThunkSize() const { + return sizeof(InternalThunk); +} + +NTSTATUS ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + const void** casted = const_cast<const void**>(address); + return ResolverThunk::ResolveInterceptor(module, function_name, casted); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/resolver_64.cc b/security/sandbox/chromium/sandbox/win/src/resolver_64.cc new file mode 100644 index 0000000000..573b2bc3a9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/resolver_64.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/resolver.h" + +#include <stddef.h> + +// For placement new. This file must not depend on the CRT at runtime, but +// placement operator new is inline. +#include <new> + +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +#if defined(_M_X64) + +const USHORT kMovRax = 0xB848; +const USHORT kJmpRax = 0xe0ff; + +#pragma pack(push, 1) +struct InternalThunk { + // This struct contains roughly the following code: + // 01 48b8f0debc9a78563412 mov rax,123456789ABCDEF0h + // ff e0 jmp rax + // + // The code modifies rax, but that's fine for x64 ABI. + + InternalThunk() { + mov_rax = kMovRax; + jmp_rax = kJmpRax; + interceptor_function = 0; + } + USHORT mov_rax; // = 48 B8 + ULONG_PTR interceptor_function; + USHORT jmp_rax; // = ff e0 +}; +#pragma pack(pop) + +#elif defined(_M_ARM64) + +const ULONG kLdrX16Pc4 = 0x58000050; +const ULONG kBrX16 = 0xD61F0200; + +#pragma pack(push, 4) +struct InternalThunk { + // This struct contains roughly the following code: + // 00 58000050 ldr x16, pc+4 + // 04 D61F0200 br x16 + // 08 123456789ABCDEF0H + + InternalThunk() { + ldr_x16_pc4 = kLdrX16Pc4; + br_x16 = kBrX16; + interceptor_function = 0; + } + ULONG ldr_x16_pc4; + ULONG br_x16; + ULONG_PTR interceptor_function; +}; +#pragma pack(pop) +#else +#error "Unsupported Windows 64-bit Arch" +#endif + +} // namespace. + +namespace sandbox { + +size_t ResolverThunk::GetInternalThunkSize() const { + return sizeof(InternalThunk); +} + +bool ResolverThunk::SetInternalThunk(void* storage, + size_t storage_bytes, + const void* original_function, + const void* interceptor) { + if (storage_bytes < sizeof(InternalThunk)) + return false; + + InternalThunk* thunk = new (storage) InternalThunk; + thunk->interceptor_function = reinterpret_cast<ULONG_PTR>(interceptor); + + return true; +} + +NTSTATUS ResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + // We don't support sidestep & co. + return STATUS_NOT_IMPLEMENTED; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token.cc b/security/sandbox/chromium/sandbox/win/src/restricted_token.cc new file mode 100644 index 0000000000..5b20e88264 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token.cc @@ -0,0 +1,432 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token.h" + +#include <stddef.h> + +#include <memory> +#include <vector> + +#include "base/logging.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +// Wrapper for utility version to unwrap ScopedHandle. +std::unique_ptr<BYTE[]> GetTokenInfo(const base::win::ScopedHandle& token, + TOKEN_INFORMATION_CLASS info_class, + DWORD* error) { + std::unique_ptr<BYTE[]> buffer; + *error = sandbox::GetTokenInformation(token.Get(), info_class, &buffer); + if (*error != ERROR_SUCCESS) + return nullptr; + return buffer; +} + +} // namespace + +namespace sandbox { + +RestrictedToken::RestrictedToken() + : integrity_level_(INTEGRITY_LEVEL_LAST), + init_(false), + lockdown_default_dacl_(false) {} + +RestrictedToken::~RestrictedToken() {} + +DWORD RestrictedToken::Init(const HANDLE effective_token) { + if (init_) + return ERROR_ALREADY_INITIALIZED; + + HANDLE temp_token; + if (effective_token) { + // We duplicate the handle to be able to use it even if the original handle + // is closed. + if (!::DuplicateHandle(::GetCurrentProcess(), effective_token, + ::GetCurrentProcess(), &temp_token, 0, false, + DUPLICATE_SAME_ACCESS)) { + return ::GetLastError(); + } + } else { + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &temp_token)) { + return ::GetLastError(); + } + } + effective_token_.Set(temp_token); + + init_ = true; + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::GetRestrictedToken( + base::win::ScopedHandle* token) const { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + size_t deny_size = sids_for_deny_only_.size(); + size_t restrict_size = sids_to_restrict_.size(); + size_t privileges_size = privileges_to_disable_.size(); + + SID_AND_ATTRIBUTES* deny_only_array = nullptr; + if (deny_size) { + deny_only_array = new SID_AND_ATTRIBUTES[deny_size]; + + for (unsigned int i = 0; i < sids_for_deny_only_.size(); ++i) { + deny_only_array[i].Attributes = SE_GROUP_USE_FOR_DENY_ONLY; + deny_only_array[i].Sid = sids_for_deny_only_[i].GetPSID(); + } + } + + SID_AND_ATTRIBUTES* sids_to_restrict_array = nullptr; + if (restrict_size) { + sids_to_restrict_array = new SID_AND_ATTRIBUTES[restrict_size]; + + for (unsigned int i = 0; i < restrict_size; ++i) { + sids_to_restrict_array[i].Attributes = 0; + sids_to_restrict_array[i].Sid = sids_to_restrict_[i].GetPSID(); + } + } + + LUID_AND_ATTRIBUTES* privileges_to_disable_array = nullptr; + if (privileges_size) { + privileges_to_disable_array = new LUID_AND_ATTRIBUTES[privileges_size]; + + for (unsigned int i = 0; i < privileges_size; ++i) { + privileges_to_disable_array[i].Attributes = 0; + privileges_to_disable_array[i].Luid = privileges_to_disable_[i]; + } + } + + bool result = true; + HANDLE new_token_handle = nullptr; + if (deny_size || restrict_size || privileges_size) { + result = ::CreateRestrictedToken( + effective_token_.Get(), 0, static_cast<DWORD>(deny_size), + deny_only_array, static_cast<DWORD>(privileges_size), + privileges_to_disable_array, static_cast<DWORD>(restrict_size), + sids_to_restrict_array, &new_token_handle); + } else { + // Duplicate the token even if it's not modified at this point + // because any subsequent changes to this token would also affect the + // current process. + result = ::DuplicateTokenEx(effective_token_.Get(), TOKEN_ALL_ACCESS, + nullptr, SecurityIdentification, TokenPrimary, + &new_token_handle); + } + auto last_error = ::GetLastError(); + + if (deny_only_array) + delete[] deny_only_array; + + if (sids_to_restrict_array) + delete[] sids_to_restrict_array; + + if (privileges_to_disable_array) + delete[] privileges_to_disable_array; + + if (!result) + return last_error; + + base::win::ScopedHandle new_token(new_token_handle); + + if (lockdown_default_dacl_) { + // Don't add Restricted sid and also remove logon sid access. + if (!RevokeLogonSidFromDefaultDacl(new_token.Get())) + return ::GetLastError(); + } else { + // Modify the default dacl on the token to contain Restricted. + if (!AddSidToDefaultDacl(new_token.Get(), WinRestrictedCodeSid, + GRANT_ACCESS, GENERIC_ALL)) { + return ::GetLastError(); + } + } + + for (const auto& default_dacl_sid : sids_for_default_dacl_) { + if (!AddSidToDefaultDacl(new_token.Get(), std::get<0>(default_dacl_sid), + std::get<1>(default_dacl_sid), + std::get<2>(default_dacl_sid))) { + return ::GetLastError(); + } + } + + // Add user to default dacl. + if (!AddUserSidToDefaultDacl(new_token.Get(), GENERIC_ALL)) + return ::GetLastError(); + + DWORD error = SetTokenIntegrityLevel(new_token.Get(), integrity_level_); + if (ERROR_SUCCESS != error) + return error; + + HANDLE token_handle; + if (!::DuplicateHandle(::GetCurrentProcess(), new_token.Get(), + ::GetCurrentProcess(), &token_handle, TOKEN_ALL_ACCESS, + false, // Don't inherit. + 0)) { + return ::GetLastError(); + } + + token->Set(token_handle); + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::GetRestrictedTokenForImpersonation( + base::win::ScopedHandle* token) const { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + base::win::ScopedHandle restricted_token; + DWORD err_code = GetRestrictedToken(&restricted_token); + if (ERROR_SUCCESS != err_code) + return err_code; + + HANDLE impersonation_token_handle; + if (!::DuplicateToken(restricted_token.Get(), SecurityImpersonation, + &impersonation_token_handle)) { + return ::GetLastError(); + } + base::win::ScopedHandle impersonation_token(impersonation_token_handle); + + HANDLE token_handle; + if (!::DuplicateHandle(::GetCurrentProcess(), impersonation_token.Get(), + ::GetCurrentProcess(), &token_handle, TOKEN_ALL_ACCESS, + false, // Don't inherit. + 0)) { + return ::GetLastError(); + } + + token->Set(token_handle); + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddAllSidsForDenyOnly(std::vector<Sid>* exceptions) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD error; + std::unique_ptr<BYTE[]> buffer = + GetTokenInfo(effective_token_, TokenGroups, &error); + + if (!buffer) + return error; + + TOKEN_GROUPS* token_groups = reinterpret_cast<TOKEN_GROUPS*>(buffer.get()); + + // Build the list of the deny only group SIDs + for (unsigned int i = 0; i < token_groups->GroupCount; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0 && + (token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == 0) { + bool should_ignore = false; + if (exceptions) { + for (unsigned int j = 0; j < exceptions->size(); ++j) { + if (::EqualSid((*exceptions)[j].GetPSID(), + token_groups->Groups[i].Sid)) { + should_ignore = true; + break; + } + } + } + if (!should_ignore) { + sids_for_deny_only_.push_back( + reinterpret_cast<SID*>(token_groups->Groups[i].Sid)); + } + } + } + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddSidForDenyOnly(const Sid& sid) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + sids_for_deny_only_.push_back(sid); + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddUserSidForDenyOnly() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + std::unique_ptr<BYTE[]> buffer(new BYTE[size]); + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(buffer.get()); + + bool result = ::GetTokenInformation(effective_token_.Get(), TokenUser, + token_user, size, &size); + + if (!result) + return ::GetLastError(); + + Sid user = reinterpret_cast<SID*>(token_user->User.Sid); + sids_for_deny_only_.push_back(user); + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::DeleteAllPrivileges( + const std::vector<std::wstring>* exceptions) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD error; + std::unique_ptr<BYTE[]> buffer = + GetTokenInfo(effective_token_, TokenPrivileges, &error); + + if (!buffer) + return error; + + TOKEN_PRIVILEGES* token_privileges = + reinterpret_cast<TOKEN_PRIVILEGES*>(buffer.get()); + + // Build the list of privileges to disable + for (unsigned int i = 0; i < token_privileges->PrivilegeCount; ++i) { + bool should_ignore = false; + if (exceptions) { + for (unsigned int j = 0; j < exceptions->size(); ++j) { + LUID luid = {0}; + ::LookupPrivilegeValue(nullptr, (*exceptions)[j].c_str(), &luid); + if (token_privileges->Privileges[i].Luid.HighPart == luid.HighPart && + token_privileges->Privileges[i].Luid.LowPart == luid.LowPart) { + should_ignore = true; + break; + } + } + } + if (!should_ignore) { + privileges_to_disable_.push_back(token_privileges->Privileges[i].Luid); + } + } + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::DeletePrivilege(const wchar_t* privilege) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + LUID luid = {0}; + if (LookupPrivilegeValue(nullptr, privilege, &luid)) + privileges_to_disable_.push_back(luid); + else + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddRestrictingSid(const Sid& sid) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + sids_to_restrict_.push_back(sid); // No attributes + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddRestrictingSidLogonSession() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD error; + std::unique_ptr<BYTE[]> buffer = + GetTokenInfo(effective_token_, TokenGroups, &error); + + if (!buffer) + return error; + + TOKEN_GROUPS* token_groups = reinterpret_cast<TOKEN_GROUPS*>(buffer.get()); + + SID* logon_sid = nullptr; + for (unsigned int i = 0; i < token_groups->GroupCount; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) != 0) { + logon_sid = static_cast<SID*>(token_groups->Groups[i].Sid); + break; + } + } + + if (logon_sid) + sids_to_restrict_.push_back(logon_sid); + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddRestrictingSidCurrentUser() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; + std::unique_ptr<BYTE[]> buffer(new BYTE[size]); + TOKEN_USER* token_user = reinterpret_cast<TOKEN_USER*>(buffer.get()); + + bool result = ::GetTokenInformation(effective_token_.Get(), TokenUser, + token_user, size, &size); + + if (!result) + return ::GetLastError(); + + Sid user = reinterpret_cast<SID*>(token_user->User.Sid); + sids_to_restrict_.push_back(user); + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::AddRestrictingSidAllSids() { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + // Add the current user to the list. + DWORD error = AddRestrictingSidCurrentUser(); + if (ERROR_SUCCESS != error) + return error; + + std::unique_ptr<BYTE[]> buffer = + GetTokenInfo(effective_token_, TokenGroups, &error); + + if (!buffer) + return error; + + TOKEN_GROUPS* token_groups = reinterpret_cast<TOKEN_GROUPS*>(buffer.get()); + + // Build the list of restricting sids from all groups. + for (unsigned int i = 0; i < token_groups->GroupCount; ++i) { + if ((token_groups->Groups[i].Attributes & SE_GROUP_INTEGRITY) == 0) + AddRestrictingSid(reinterpret_cast<SID*>(token_groups->Groups[i].Sid)); + } + + return ERROR_SUCCESS; +} + +DWORD RestrictedToken::SetIntegrityLevel(IntegrityLevel integrity_level) { + integrity_level_ = integrity_level; + return ERROR_SUCCESS; +} + +void RestrictedToken::SetLockdownDefaultDacl() { + lockdown_default_dacl_ = true; +} + +DWORD RestrictedToken::AddDefaultDaclSid(const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access) { + DCHECK(init_); + if (!init_) + return ERROR_NO_TOKEN; + + sids_for_default_dacl_.push_back(std::make_tuple(sid, access_mode, access)); + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token.h b/security/sandbox/chromium/sandbox/win/src/restricted_token.h new file mode 100644 index 0000000000..c89427af14 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token.h @@ -0,0 +1,207 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_RESTRICTED_TOKEN_H_ +#define SANDBOX_SRC_RESTRICTED_TOKEN_H_ + +#include <windows.h> + +#include <tuple> +#include <vector> + +#include <string> + +#include "base/macros.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/security_level.h" +#include "sandbox/win/src/sid.h" + +// Flags present in the Group SID list. These 2 flags are new in Windows Vista +#ifndef SE_GROUP_INTEGRITY +#define SE_GROUP_INTEGRITY (0x00000020L) +#endif +#ifndef SE_GROUP_INTEGRITY_ENABLED +#define SE_GROUP_INTEGRITY_ENABLED (0x00000040L) +#endif + +namespace sandbox { + +// Handles the creation of a restricted token using the effective token or +// any token handle. +// Sample usage: +// RestrictedToken restricted_token; +// DWORD err_code = restricted_token.Init(nullptr); // Use the current +// // effective token +// if (ERROR_SUCCESS != err_code) { +// // handle error. +// } +// +// restricted_token.AddRestrictingSid(ATL::Sids::Users().GetPSID()); +// base::win::ScopedHandle token_handle; +// err_code = restricted_token.GetRestrictedToken(&token_handle); +// if (ERROR_SUCCESS != err_code) { +// // handle error. +// } +// [...] +class RestrictedToken { + public: + // Init() has to be called before calling any other method in the class. + RestrictedToken(); + ~RestrictedToken(); + + // Initializes the RestrictedToken object with effective_token. + // If effective_token is nullptr, it initializes the RestrictedToken object + // with the effective token of the current process. + DWORD Init(HANDLE effective_token); + + // Creates a restricted token. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD GetRestrictedToken(base::win::ScopedHandle* token) const; + + // Creates a restricted token and uses this new token to create a new token + // for impersonation. Returns this impersonation token. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // The sample usage is the same as the GetRestrictedToken function. + DWORD GetRestrictedTokenForImpersonation( + base::win::ScopedHandle* token) const; + + // Lists all sids in the token and mark them as Deny Only except for those + // present in the exceptions parameter. If there is no exception needed, + // the caller can pass an empty list or nullptr for the exceptions + // parameter. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // std::vector<Sid> sid_exceptions; + // sid_exceptions.push_back(ATL::Sids::Users().GetPSID()); + // sid_exceptions.push_back(ATL::Sids::World().GetPSID()); + // restricted_token.AddAllSidsForDenyOnly(&sid_exceptions); + // Note: A Sid marked for Deny Only in a token cannot be used to grant + // access to any resource. It can only be used to deny access. + DWORD AddAllSidsForDenyOnly(std::vector<Sid>* exceptions); + + // Adds a user or group SID for Deny Only in the restricted token. + // Parameter: sid is the SID to add in the Deny Only list. + // The return value is always ERROR_SUCCESS. + // + // Sample Usage: + // restricted_token.AddSidForDenyOnly(ATL::Sids::Admins().GetPSID()); + DWORD AddSidForDenyOnly(const Sid& sid); + + // Adds the user sid of the token for Deny Only in the restricted token. + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AddUserSidForDenyOnly(); + + // Lists all privileges in the token and add them to the list of privileges + // to remove except for those present in the exceptions parameter. If + // there is no exception needed, the caller can pass an empty list or nullptr + // for the exceptions parameter. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // std::vector<std::wstring> privilege_exceptions; + // privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + // restricted_token.DeleteAllPrivileges(&privilege_exceptions); + DWORD DeleteAllPrivileges(const std::vector<std::wstring>* exceptions); + + // Adds a privilege to the list of privileges to remove in the restricted + // token. + // Parameter: privilege is the privilege name to remove. This is the string + // representing the privilege. (e.g. "SeChangeNotifyPrivilege"). + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + // + // Sample usage: + // restricted_token.DeletePrivilege(SE_LOAD_DRIVER_NAME); + DWORD DeletePrivilege(const wchar_t* privilege); + + // Adds a SID to the list of restricting sids in the restricted token. + // Parameter: sid is the sid to add to the list restricting sids. + // The return value is always ERROR_SUCCESS. + // + // Sample usage: + // restricted_token.AddRestrictingSid(ATL::Sids::Users().GetPSID()); + // Note: The list of restricting is used to force Windows to perform all + // access checks twice. The first time using your user SID and your groups, + // and the second time using your list of restricting sids. The access has + // to be granted in both places to get access to the resource requested. + DWORD AddRestrictingSid(const Sid& sid); + + // Adds the logon sid of the token in the list of restricting sids for the + // restricted token. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AddRestrictingSidLogonSession(); + + // Adds the owner sid of the token in the list of restricting sids for the + // restricted token. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AddRestrictingSidCurrentUser(); + + // Adds all group sids and the user sid to the restricting sids list. + // + // If the function succeeds, the return value is ERROR_SUCCESS. If the + // function fails, the return value is the win32 error code corresponding to + // the error. + DWORD AddRestrictingSidAllSids(); + + // Sets the token integrity level. This is only valid on Vista. The integrity + // level cannot be higher than your current integrity level. + DWORD SetIntegrityLevel(IntegrityLevel integrity_level); + + // Set a flag which indicates the created token should have a locked down + // default DACL when created. + void SetLockdownDefaultDacl(); + + // Add a SID to the default DACL. These SIDs are added regardless of the + // SetLockdownDefaultDacl state. + DWORD AddDefaultDaclSid(const Sid& sid, + ACCESS_MODE access_mode, + ACCESS_MASK access); + + private: + // The list of restricting sids in the restricted token. + std::vector<Sid> sids_to_restrict_; + // The list of privileges to remove in the restricted token. + std::vector<LUID> privileges_to_disable_; + // The list of sids to mark as Deny Only in the restricted token. + std::vector<Sid> sids_for_deny_only_; + // The list of sids to add to the default DACL of the restricted token. + std::vector<std::tuple<Sid, ACCESS_MODE, ACCESS_MASK>> sids_for_default_dacl_; + // The token to restrict. Can only be set in a constructor. + base::win::ScopedHandle effective_token_; + // The token integrity level. Only valid on Vista. + IntegrityLevel integrity_level_; + // Tells if the object is initialized or not (if Init() has been called) + bool init_; + // Lockdown the default DACL when creating new tokens. + bool lockdown_default_dacl_; + + DISALLOW_COPY_AND_ASSIGN(RestrictedToken); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_RESTRICTED_TOKEN_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token_unittest.cc b/security/sandbox/chromium/sandbox/win/src/restricted_token_unittest.cc new file mode 100644 index 0000000000..1855054a7c --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token_unittest.cc @@ -0,0 +1,829 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for the RestrictedToken. + +#include "sandbox/win/src/restricted_token.h" + +#include <vector> + +#include "base/win/atl.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/sid.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +void TestDefaultDalc(bool restricted_required) { + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + if (!restricted_required) + token.SetLockdownDefaultDacl(); + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + + base::win::ScopedHandle handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(handle.Take()); + + ATL::CDacl dacl; + ASSERT_TRUE(restricted_token.GetDefaultDacl(&dacl)); + + ATL::CSid logon_sid; + ASSERT_TRUE(restricted_token.GetLogonSid(&logon_sid)); + + bool restricted_found = false; + bool logon_sid_found = false; + + unsigned int ace_count = dacl.GetAceCount(); + for (unsigned int i = 0; i < ace_count; ++i) { + ATL::CSid sid; + ACCESS_MASK mask = 0; + dacl.GetAclEntry(i, &sid, &mask); + if (sid == ATL::Sids::RestrictedCode() && mask == GENERIC_ALL) { + restricted_found = true; + } else if (sid == logon_sid) { + logon_sid_found = true; + } + } + + ASSERT_EQ(restricted_required, restricted_found); + if (!restricted_required) + ASSERT_FALSE(logon_sid_found); +} + +bool GetVariableTokenInformation(HANDLE token, + TOKEN_INFORMATION_CLASS information_class, + std::vector<char>* information) { + DWORD return_length; + if (!::GetTokenInformation(token, information_class, nullptr, 0, + &return_length)) { + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return false; + } + } + + information->resize(return_length); + return !!::GetTokenInformation(token, information_class, information->data(), + return_length, &return_length); +} + +bool GetVariableTokenInformation(const base::win::ScopedHandle& token, + TOKEN_INFORMATION_CLASS information_class, + std::vector<char>* information) { + return GetVariableTokenInformation(token.Get(), information_class, + information); +} + +void CheckDaclForPackageSid(const base::win::ScopedHandle& token, + PSECURITY_CAPABILITIES security_capabilities, + bool package_sid_required) { + DWORD length_needed = 0; + ::GetKernelObjectSecurity(token.Get(), DACL_SECURITY_INFORMATION, nullptr, 0, + &length_needed); + ASSERT_EQ(::GetLastError(), DWORD{ERROR_INSUFFICIENT_BUFFER}); + + std::vector<char> security_desc_buffer(length_needed); + SECURITY_DESCRIPTOR* security_desc = + reinterpret_cast<SECURITY_DESCRIPTOR*>(security_desc_buffer.data()); + + ASSERT_TRUE(::GetKernelObjectSecurity(token.Get(), DACL_SECURITY_INFORMATION, + security_desc, length_needed, + &length_needed)); + + ATL::CSecurityDesc token_sd(*security_desc); + ATL::CDacl dacl; + ASSERT_TRUE(token_sd.GetDacl(&dacl)); + + ATL::CSid package_sid( + static_cast<SID*>(security_capabilities->AppContainerSid)); + ATL::CSid all_package_sid( + static_cast<SID*>(sandbox::Sid(::WinBuiltinAnyPackageSid).GetPSID())); + + unsigned int ace_count = dacl.GetAceCount(); + for (unsigned int i = 0; i < ace_count; ++i) { + ATL::CSid sid; + ACCESS_MASK mask = 0; + BYTE type = 0; + dacl.GetAclEntry(i, &sid, &mask, &type); + if (mask != TOKEN_ALL_ACCESS || type != ACCESS_ALLOWED_ACE_TYPE) + continue; + if (sid == package_sid) + EXPECT_TRUE(package_sid_required); + else if (sid == all_package_sid) + EXPECT_FALSE(package_sid_required); + } +} + +void CheckLowBoxToken(const base::win::ScopedHandle& token, + TOKEN_TYPE token_type, + PSECURITY_CAPABILITIES security_capabilities) { + DWORD appcontainer; + DWORD return_length; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenIsAppContainer, + &appcontainer, sizeof(appcontainer), + &return_length)); + ASSERT_TRUE(appcontainer); + TOKEN_TYPE token_type_real; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenType, &token_type_real, + sizeof(token_type_real), &return_length)); + ASSERT_EQ(token_type_real, token_type); + if (token_type == ::TokenImpersonation) { + SECURITY_IMPERSONATION_LEVEL imp_level; + ASSERT_TRUE(::GetTokenInformation(token.Get(), ::TokenImpersonationLevel, + &imp_level, sizeof(imp_level), + &return_length)); + ASSERT_EQ(imp_level, ::SecurityImpersonation); + } + + std::vector<char> package_sid_buf; + ASSERT_TRUE(GetVariableTokenInformation(token, ::TokenAppContainerSid, + &package_sid_buf)); + PTOKEN_APPCONTAINER_INFORMATION package_sid = + reinterpret_cast<PTOKEN_APPCONTAINER_INFORMATION>(package_sid_buf.data()); + EXPECT_TRUE(::EqualSid(security_capabilities->AppContainerSid, + package_sid->TokenAppContainer)); + + std::vector<char> capabilities_buf; + ASSERT_TRUE(GetVariableTokenInformation(token, ::TokenCapabilities, + &capabilities_buf)); + PTOKEN_GROUPS capabilities = + reinterpret_cast<PTOKEN_GROUPS>(capabilities_buf.data()); + ASSERT_EQ(capabilities->GroupCount, security_capabilities->CapabilityCount); + for (DWORD index = 0; index < capabilities->GroupCount; ++index) { + EXPECT_EQ(capabilities->Groups[index].Attributes, + security_capabilities->Capabilities[index].Attributes); + EXPECT_TRUE(::EqualSid(capabilities->Groups[index].Sid, + security_capabilities->Capabilities[index].Sid)); + } + + CheckDaclForPackageSid(token, security_capabilities, true); +} + +// Checks if a sid is in the restricting list of the restricted token. +// Asserts if it's not the case. If count is a positive number, the number of +// elements in the restricting sids list has to be equal. +void CheckRestrictingSid(HANDLE restricted_token, ATL::CSid sid, int count) { + std::vector<char> memory; + ASSERT_TRUE(GetVariableTokenInformation(restricted_token, + ::TokenRestrictedSids, &memory)); + PTOKEN_GROUPS groups = reinterpret_cast<PTOKEN_GROUPS>(memory.data()); + ATL::CTokenGroups atl_groups(*groups); + + if (count >= 0) + ASSERT_EQ(static_cast<unsigned>(count), atl_groups.GetCount()); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + atl_groups.GetSidsAndAttributes(&sids, &attributes); + + bool present = false; + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (sids[i] == sid) { + present = true; + break; + } + } + + ASSERT_TRUE(present); +} + +void CheckRestrictingSid(const ATL::CAccessToken& restricted_token, + ATL::CSid sid, + int count) { + CheckRestrictingSid(restricted_token.GetHandle(), sid, count); +} + +} // namespace + +// Tests the initializatioin with an invalid token handle. +TEST(RestrictedTokenTest, InvalidHandle) { + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_INVALID_HANDLE), + token.Init(reinterpret_cast<HANDLE>(0x5555))); +} + +// Tests the initialization with nullptr as parameter. +TEST(RestrictedTokenTest, DefaultInit) { + // Get the current process token. + HANDLE token_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &token_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, token_handle); + + ATL::CAccessToken access_token; + access_token.Attach(token_handle); + + // Create the token using the current token. + RestrictedToken token_default; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token_default.Init(nullptr)); + + // Get the handle to the restricted token. + + base::win::ScopedHandle restricted_token_handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token_default.GetRestrictedToken(&restricted_token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(restricted_token_handle.Take()); + + ATL::CSid sid_user_restricted; + ATL::CSid sid_user_default; + ATL::CSid sid_owner_restricted; + ATL::CSid sid_owner_default; + ASSERT_TRUE(restricted_token.GetUser(&sid_user_restricted)); + ASSERT_TRUE(access_token.GetUser(&sid_user_default)); + ASSERT_TRUE(restricted_token.GetOwner(&sid_owner_restricted)); + ASSERT_TRUE(access_token.GetOwner(&sid_owner_default)); + + // Check if both token have the same owner and user. + ASSERT_EQ(sid_user_restricted, sid_user_default); + ASSERT_EQ(sid_owner_restricted, sid_owner_default); +} + +// Tests the initialization with a custom token as parameter. +TEST(RestrictedTokenTest, CustomInit) { + // Get the current process token. + HANDLE token_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &token_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, token_handle); + + ATL::CAccessToken access_token; + access_token.Attach(token_handle); + + // Change the primary group. + access_token.SetPrimaryGroup(ATL::Sids::World()); + + // Create the token using the current token. + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.Init(access_token.GetHandle())); + + // Get the handle to the restricted token. + + base::win::ScopedHandle restricted_token_handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&restricted_token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(restricted_token_handle.Take()); + + ATL::CSid sid_restricted; + ATL::CSid sid_default; + ASSERT_TRUE(restricted_token.GetPrimaryGroup(&sid_restricted)); + ASSERT_TRUE(access_token.GetPrimaryGroup(&sid_default)); + + // Check if both token have the same owner. + ASSERT_EQ(sid_restricted, sid_default); +} + +// Verifies that the token created by the object are valid. +TEST(RestrictedTokenTest, ResultToken) { + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + + base::win::ScopedHandle restricted_token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&restricted_token)); + + ASSERT_TRUE(::IsTokenRestricted(restricted_token.Get())); + + DWORD length = 0; + TOKEN_TYPE type; + ASSERT_TRUE(::GetTokenInformation(restricted_token.Get(), ::TokenType, &type, + sizeof(type), &length)); + + ASSERT_EQ(type, TokenPrimary); + + base::win::ScopedHandle impersonation_token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedTokenForImpersonation(&impersonation_token)); + + ASSERT_TRUE(::IsTokenRestricted(impersonation_token.Get())); + + ASSERT_TRUE(::GetTokenInformation(impersonation_token.Get(), ::TokenType, + &type, sizeof(type), &length)); + + ASSERT_EQ(type, TokenImpersonation); +} + +// Verifies that the token created has "Restricted" in its default dacl. +TEST(RestrictedTokenTest, DefaultDacl) { + TestDefaultDalc(true); +} + +// Verifies that the token created does not have "Restricted" in its default +// dacl. +TEST(RestrictedTokenTest, DefaultDaclLockdown) { + TestDefaultDalc(false); +} + +// Tests the method "AddSidForDenyOnly". +TEST(RestrictedTokenTest, DenySid) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddSidForDenyOnly(Sid(WinWorldSid))); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if (ATL::Sids::World() == sids[i]) { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method "AddAllSidsForDenyOnly". +TEST(RestrictedTokenTest, DenySids) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddAllSidsForDenyOnly(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all sids are really gone. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_LOGON_ID) == 0 && + (attributes[i] & SE_GROUP_INTEGRITY) == 0) { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method "AddAllSidsForDenyOnly" using an exception list. +TEST(RestrictedTokenTest, DenySidsException) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + std::vector<Sid> sids_exception; + sids_exception.push_back(Sid(WinWorldSid)); + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddAllSidsForDenyOnly(&sids_exception)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all sids are really gone. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_LOGON_ID) == 0 && + (attributes[i] & SE_GROUP_INTEGRITY) == 0) { + if (ATL::Sids::World() == sids[i]) { + ASSERT_EQ(0u, attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } else { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } + } +} + +// Tests test method AddOwnerSidForDenyOnly. +TEST(RestrictedTokenTest, DenyOwnerSid) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.AddUserSidForDenyOnly()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + ATL::CSid user_sid; + ASSERT_TRUE(restricted_token.GetUser(&user_sid)); + + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (user_sid == sids[i]) { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests test method AddOwnerSidForDenyOnly with a custom effective token. +TEST(RestrictedTokenTest, DenyOwnerSidCustom) { + // Get the current process token. + HANDLE access_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &access_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, access_handle); + + ATL::CAccessToken access_token; + access_token.Attach(access_handle); + + RestrictedToken token; + base::win::ScopedHandle token_handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.Init(access_token.GetHandle())); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.AddUserSidForDenyOnly()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + ATL::CSid user_sid; + ASSERT_TRUE(restricted_token.GetUser(&user_sid)); + + for (unsigned int i = 0; i < sids.GetCount(); ++i) { + if (user_sid == sids[i]) { + ASSERT_EQ(static_cast<DWORD>(SE_GROUP_USE_FOR_DENY_ONLY), + attributes[i] & SE_GROUP_USE_FOR_DENY_ONLY); + } + } +} + +// Tests the method DeleteAllPrivileges. +TEST(RestrictedTokenTest, DeleteAllPrivileges) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.DeleteAllPrivileges(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ASSERT_EQ(0u, privileges.GetCount()); +} + +// Tests the method DeleteAllPrivileges with an exception list. +TEST(RestrictedTokenTest, DeleteAllPrivilegesException) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + std::vector<std::wstring> exceptions; + exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.DeleteAllPrivileges(&exceptions)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ATL::CTokenPrivileges::CNames privilege_names; + ATL::CTokenPrivileges::CAttributes privilege_name_attributes; + privileges.GetNamesAndAttributes(&privilege_names, + &privilege_name_attributes); + + ASSERT_EQ(1u, privileges.GetCount()); + + for (unsigned int i = 0; i < privileges.GetCount(); ++i) { + ASSERT_EQ(privilege_names[i], SE_CHANGE_NOTIFY_NAME); + } +} + +// Tests the method DeletePrivilege. +TEST(RestrictedTokenTest, DeletePrivilege) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.DeletePrivilege(SE_CHANGE_NOTIFY_NAME)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenPrivileges privileges; + ASSERT_TRUE(restricted_token.GetPrivileges(&privileges)); + + ATL::CTokenPrivileges::CNames privilege_names; + ATL::CTokenPrivileges::CAttributes privilege_name_attributes; + privileges.GetNamesAndAttributes(&privilege_names, + &privilege_name_attributes); + + for (unsigned int i = 0; i < privileges.GetCount(); ++i) { + ASSERT_NE(privilege_names[i], SE_CHANGE_NOTIFY_NAME); + } +} + +// Tests the method AddRestrictingSid. +TEST(RestrictedTokenTest, AddRestrictingSid) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + CheckRestrictingSid(restricted_token, ATL::Sids::World(), 1); +} + +// Tests the method AddRestrictingSidCurrentUser. +TEST(RestrictedTokenTest, AddRestrictingSidCurrentUser) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + ATL::CSid user; + restricted_token.GetUser(&user); + + CheckRestrictingSid(restricted_token, user, 1); +} + +// Tests the method AddRestrictingSidCurrentUser with a custom effective token. +TEST(RestrictedTokenTest, AddRestrictingSidCurrentUserCustom) { + // Get the current process token. + HANDLE access_handle = INVALID_HANDLE_VALUE; + ASSERT_TRUE(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &access_handle)); + + ASSERT_NE(INVALID_HANDLE_VALUE, access_handle); + + ATL::CAccessToken access_token; + access_token.Attach(access_handle); + + RestrictedToken token; + base::win::ScopedHandle token_handle; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.Init(access_token.GetHandle())); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + ATL::CSid user; + restricted_token.GetUser(&user); + + CheckRestrictingSid(restricted_token, user, 1); +} + +// Tests the method AddRestrictingSidLogonSession. +TEST(RestrictedTokenTest, AddRestrictingSidLogonSession) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidLogonSession()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + ATL::CSid session; + restricted_token.GetLogonSid(&session); + + CheckRestrictingSid(restricted_token, session, 1); +} + +// Tests adding a lot of restricting sids. +TEST(RestrictedTokenTest, AddMultipleRestrictingSids) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidCurrentUser()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidLogonSession()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + ATL::CSid session; + restricted_token.GetLogonSid(&session); + + std::vector<char> memory; + ASSERT_TRUE(GetVariableTokenInformation(restricted_token.GetHandle(), + ::TokenRestrictedSids, &memory)); + PTOKEN_GROUPS groups = reinterpret_cast<PTOKEN_GROUPS>(memory.data()); + ATL::CTokenGroups atl_groups(*groups); + ASSERT_EQ(3u, atl_groups.GetCount()); +} + +// Tests the method "AddRestrictingSidAllSids". +TEST(RestrictedTokenTest, AddAllSidToRestrictingSids) { + RestrictedToken token; + base::win::ScopedHandle token_handle; + + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.AddRestrictingSidAllSids()); + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + token.GetRestrictedToken(&token_handle)); + + ATL::CAccessToken restricted_token; + restricted_token.Attach(token_handle.Take()); + + ATL::CTokenGroups groups; + ASSERT_TRUE(restricted_token.GetGroups(&groups)); + + ATL::CSid::CSidArray sids; + ATL::CAtlArray<DWORD> attributes; + groups.GetSidsAndAttributes(&sids, &attributes); + + // Verify that all group sids are in the restricting sid list. + for (unsigned int i = 0; i < sids.GetCount(); i++) { + if ((attributes[i] & SE_GROUP_INTEGRITY) == 0) { + CheckRestrictingSid(restricted_token, sids[i], -1); + } + } + + // Verify that the user is in the restricting sid list. + ATL::CSid user; + restricted_token.GetUser(&user); + CheckRestrictingSid(restricted_token, user, -1); +} + +// Checks the error code when the object is initialized twice. +TEST(RestrictedTokenTest, DoubleInit) { + RestrictedToken token; + ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS), token.Init(nullptr)); + + ASSERT_EQ(static_cast<DWORD>(ERROR_ALREADY_INITIALIZED), token.Init(nullptr)); +} + +TEST(RestrictedTokenTest, LockdownDefaultDaclNoLogonSid) { + ATL::CAccessToken anonymous_token; + ASSERT_TRUE(::ImpersonateAnonymousToken(::GetCurrentThread())); + ASSERT_TRUE(anonymous_token.GetThreadToken(TOKEN_ALL_ACCESS)); + ::RevertToSelf(); + ATL::CSid logon_sid; + // Verify that the anonymous token doesn't have the logon sid. + ASSERT_FALSE(anonymous_token.GetLogonSid(&logon_sid)); + + RestrictedToken token; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, token.Init(anonymous_token.GetHandle())); + token.SetLockdownDefaultDacl(); + + base::win::ScopedHandle handle; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, token.GetRestrictedToken(&handle)); +} + +TEST(RestrictedTokenTest, LowBoxToken) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return; + base::win::ScopedHandle token; + + Sid package_sid = Sid::FromSddlString(L"S-1-15-2-1-2-3-4-5-6-7"); + SecurityCapabilities caps_no_capabilities(package_sid); + + ASSERT_EQ(DWORD{ERROR_INVALID_PARAMETER}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_no_capabilities, nullptr, + 0, nullptr)); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_no_capabilities, nullptr, + 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_no_capabilities); + + ASSERT_TRUE(ReplacePackageSidInDacl(token.Get(), SE_KERNEL_OBJECT, + Sid(caps_no_capabilities.AppContainerSid), + TOKEN_ALL_ACCESS)); + CheckDaclForPackageSid(token, &caps_no_capabilities, false); + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, IMPERSONATION, &caps_no_capabilities, + nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenImpersonation, &caps_no_capabilities); + + std::vector<Sid> capabilities; + capabilities.push_back(Sid::FromKnownCapability(kInternetClient)); + capabilities.push_back(Sid::FromKnownCapability(kPrivateNetworkClientServer)); + SecurityCapabilities caps_with_capabilities(package_sid, capabilities); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(nullptr, PRIMARY, &caps_with_capabilities, + nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_with_capabilities); + + RestrictedToken restricted_token; + base::win::ScopedHandle token_handle; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, restricted_token.Init(nullptr)); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + restricted_token.AddRestrictingSid(ATL::Sids::World().GetPSID())); + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + restricted_token.GetRestrictedToken(&token_handle)); + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(token_handle.Get(), PRIMARY, + &caps_with_capabilities, nullptr, 0, &token)); + ASSERT_TRUE(token.IsValid()); + CheckLowBoxToken(token, ::TokenPrimary, &caps_with_capabilities); + CheckRestrictingSid(token.Get(), ATL::Sids::World(), 1); + + SecurityCapabilities caps_for_handles( + Sid::FromSddlString(L"S-1-15-2-1-2-3-4-5-6-8")); + base::win::ScopedHandle object_handle; + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxObjectDirectory(caps_for_handles.AppContainerSid, true, + &object_handle)); + HANDLE saved_handles[] = {object_handle.Get()}; + + ASSERT_EQ(DWORD{ERROR_SUCCESS}, + CreateLowBoxToken(token_handle.Get(), PRIMARY, &caps_for_handles, + saved_handles, 1, &token)); + ASSERT_TRUE(token.IsValid()); + object_handle.Close(); + ASSERT_FALSE(object_handle.IsValid()); + ASSERT_EQ(DWORD{ERROR_ALREADY_EXISTS}, + CreateLowBoxObjectDirectory(caps_for_handles.AppContainerSid, false, + &object_handle)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.cc b/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.cc new file mode 100644 index 0000000000..863dee8d69 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.cc @@ -0,0 +1,480 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/restricted_token_utils.h" + +#include <aclapi.h> +#include <sddl.h> + +#include <memory> +#include <vector> + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/security_level.h" +#include "sandbox/win/src/sid.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +DWORD GetObjectSecurityDescriptor(HANDLE handle, + SECURITY_INFORMATION security_info, + std::vector<char>* security_desc_buffer, + PSECURITY_DESCRIPTOR* security_desc) { + DWORD last_error = 0; + DWORD length_needed = 0; + + ::GetKernelObjectSecurity(handle, security_info, nullptr, 0, &length_needed); + last_error = ::GetLastError(); + if (last_error != ERROR_INSUFFICIENT_BUFFER) + return last_error; + + security_desc_buffer->resize(length_needed); + *security_desc = + reinterpret_cast<PSECURITY_DESCRIPTOR>(security_desc_buffer->data()); + + if (!::GetKernelObjectSecurity(handle, security_info, *security_desc, + length_needed, &length_needed)) { + return ::GetLastError(); + } + + return ERROR_SUCCESS; +} + +} // namespace + +DWORD CreateRestrictedToken(HANDLE effective_token, + TokenLevel security_level, + IntegrityLevel integrity_level, + TokenType token_type, + bool lockdown_default_dacl, + PSID unique_restricted_sid, + bool use_restricting_sids, + base::win::ScopedHandle* token) { + RestrictedToken restricted_token; + restricted_token.Init(effective_token); + if (lockdown_default_dacl) + restricted_token.SetLockdownDefaultDacl(); + if (unique_restricted_sid) { + restricted_token.AddDefaultDaclSid(Sid(unique_restricted_sid), GRANT_ACCESS, + GENERIC_ALL); + restricted_token.AddDefaultDaclSid(Sid(WinCreatorOwnerRightsSid), + GRANT_ACCESS, READ_CONTROL); + } + + std::vector<std::wstring> privilege_exceptions; + std::vector<Sid> sid_exceptions; + + bool deny_sids = true; + bool remove_privileges = true; + + switch (security_level) { + case USER_UNPROTECTED: { + deny_sids = false; + remove_privileges = false; + break; + } + case USER_RESTRICTED_SAME_ACCESS: { + deny_sids = false; + remove_privileges = false; + + if (use_restricting_sids) { + unsigned err_code = restricted_token.AddRestrictingSidAllSids(); + if (ERROR_SUCCESS != err_code) { + return err_code; + } + } + + break; + } + case USER_NON_ADMIN: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + sid_exceptions.push_back(WinAuthenticatedUserSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + break; + } + case USER_RESTRICTED_NON_ADMIN: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + sid_exceptions.push_back(WinAuthenticatedUserSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + restricted_token.AddRestrictingSid(WinBuiltinUsersSid); + restricted_token.AddRestrictingSid(WinWorldSid); + restricted_token.AddRestrictingSid(WinInteractiveSid); + restricted_token.AddRestrictingSid(WinAuthenticatedUserSid); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + restricted_token.AddRestrictingSidCurrentUser(); + restricted_token.AddRestrictingSidLogonSession(); + break; + } + case USER_INTERACTIVE: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + sid_exceptions.push_back(WinAuthenticatedUserSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + if (use_restricting_sids) { + restricted_token.AddRestrictingSid(WinBuiltinUsersSid); + restricted_token.AddRestrictingSid(WinWorldSid); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + restricted_token.AddRestrictingSidCurrentUser(); + restricted_token.AddRestrictingSidLogonSession(); + if (unique_restricted_sid) + restricted_token.AddRestrictingSid(Sid(unique_restricted_sid)); + } + break; + } + case USER_LIMITED: { + sid_exceptions.push_back(WinBuiltinUsersSid); + sid_exceptions.push_back(WinWorldSid); + sid_exceptions.push_back(WinInteractiveSid); + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + if (use_restricting_sids) { + restricted_token.AddRestrictingSid(WinBuiltinUsersSid); + restricted_token.AddRestrictingSid(WinWorldSid); + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + if (unique_restricted_sid) + restricted_token.AddRestrictingSid(Sid(unique_restricted_sid)); + + // This token has to be able to create objects in BNO. + // Unfortunately, on Vista+, it needs the current logon sid + // in the token to achieve this. You should also set the process to be + // low integrity level so it can't access object created by other + // processes. + restricted_token.AddRestrictingSidLogonSession(); + } else { + restricted_token.AddUserSidForDenyOnly(); + } + break; + } + case USER_RESTRICTED: { + privilege_exceptions.push_back(SE_CHANGE_NOTIFY_NAME); + restricted_token.AddUserSidForDenyOnly(); + if (use_restricting_sids) { + restricted_token.AddRestrictingSid(WinRestrictedCodeSid); + if (unique_restricted_sid) + restricted_token.AddRestrictingSid(Sid(unique_restricted_sid)); + } + break; + } + case USER_LOCKDOWN: { + restricted_token.AddUserSidForDenyOnly(); + if (use_restricting_sids) { + restricted_token.AddRestrictingSid(WinNullSid); + if (unique_restricted_sid) + restricted_token.AddRestrictingSid(Sid(unique_restricted_sid)); + } + break; + } + default: { return ERROR_BAD_ARGUMENTS; } + } + + DWORD err_code = ERROR_SUCCESS; + if (deny_sids) { + err_code = restricted_token.AddAllSidsForDenyOnly(&sid_exceptions); + if (ERROR_SUCCESS != err_code) + return err_code; + } + + if (remove_privileges) { + err_code = restricted_token.DeleteAllPrivileges(&privilege_exceptions); + if (ERROR_SUCCESS != err_code) + return err_code; + } + + restricted_token.SetIntegrityLevel(integrity_level); + + switch (token_type) { + case PRIMARY: { + err_code = restricted_token.GetRestrictedToken(token); + break; + } + case IMPERSONATION: { + err_code = restricted_token.GetRestrictedTokenForImpersonation(token); + break; + } + default: { + err_code = ERROR_BAD_ARGUMENTS; + break; + } + } + + return err_code; +} + +DWORD SetObjectIntegrityLabel(HANDLE handle, + SE_OBJECT_TYPE type, + const wchar_t* ace_access, + const wchar_t* integrity_level_sid) { + // Build the SDDL string for the label. + std::wstring sddl = L"S:("; // SDDL for a SACL. + sddl += SDDL_MANDATORY_LABEL; // Ace Type is "Mandatory Label". + sddl += L";;"; // No Ace Flags. + sddl += ace_access; // Add the ACE access. + sddl += L";;;"; // No ObjectType and Inherited Object Type. + sddl += integrity_level_sid; // Trustee Sid. + sddl += L")"; + + DWORD error = ERROR_SUCCESS; + PSECURITY_DESCRIPTOR sec_desc = nullptr; + + PACL sacl = nullptr; + BOOL sacl_present = false; + BOOL sacl_defaulted = false; + + if (::ConvertStringSecurityDescriptorToSecurityDescriptorW( + sddl.c_str(), SDDL_REVISION, &sec_desc, nullptr)) { + if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl, + &sacl_defaulted)) { + error = ::SetSecurityInfo(handle, type, LABEL_SECURITY_INFORMATION, + nullptr, nullptr, nullptr, sacl); + } else { + error = ::GetLastError(); + } + + ::LocalFree(sec_desc); + } else { + return ::GetLastError(); + } + + return error; +} + +const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level) { + switch (integrity_level) { + case INTEGRITY_LEVEL_SYSTEM: + return L"S-1-16-16384"; + case INTEGRITY_LEVEL_HIGH: + return L"S-1-16-12288"; + case INTEGRITY_LEVEL_MEDIUM: + return L"S-1-16-8192"; + case INTEGRITY_LEVEL_MEDIUM_LOW: + return L"S-1-16-6144"; + case INTEGRITY_LEVEL_LOW: + return L"S-1-16-4096"; + case INTEGRITY_LEVEL_BELOW_LOW: + return L"S-1-16-2048"; + case INTEGRITY_LEVEL_UNTRUSTED: + return L"S-1-16-0"; + case INTEGRITY_LEVEL_LAST: + return nullptr; + } + + NOTREACHED(); + return nullptr; +} +DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level) { + const wchar_t* integrity_level_str = GetIntegrityLevelString(integrity_level); + if (!integrity_level_str) { + // No mandatory level specified, we don't change it. + return ERROR_SUCCESS; + } + + PSID integrity_sid = nullptr; + if (!::ConvertStringSidToSid(integrity_level_str, &integrity_sid)) + return ::GetLastError(); + + TOKEN_MANDATORY_LABEL label = {}; + label.Label.Attributes = SE_GROUP_INTEGRITY; + label.Label.Sid = integrity_sid; + + DWORD size = sizeof(TOKEN_MANDATORY_LABEL) + ::GetLengthSid(integrity_sid); + bool result = ::SetTokenInformation(token, TokenIntegrityLevel, &label, size); + auto last_error = ::GetLastError(); + ::LocalFree(integrity_sid); + + return result ? ERROR_SUCCESS : last_error; +} + +DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level) { + // We don't check for an invalid level here because we'll just let it + // fail on the SetTokenIntegrityLevel call later on. + if (integrity_level == INTEGRITY_LEVEL_LAST) { + // No mandatory level specified, we don't change it. + return ERROR_SUCCESS; + } + + HANDLE token_handle; + if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, + &token_handle)) + return ::GetLastError(); + + base::win::ScopedHandle token(token_handle); + + return SetTokenIntegrityLevel(token.Get(), integrity_level); +} + +DWORD HardenTokenIntegrityLevelPolicy(HANDLE token) { + std::vector<char> security_desc_buffer; + PSECURITY_DESCRIPTOR security_desc = nullptr; + DWORD last_error = GetObjectSecurityDescriptor( + token, LABEL_SECURITY_INFORMATION, &security_desc_buffer, &security_desc); + if (last_error != ERROR_SUCCESS) + return last_error; + + PACL sacl = nullptr; + BOOL sacl_present = false; + BOOL sacl_defaulted = false; + + if (!::GetSecurityDescriptorSacl(security_desc, &sacl_present, &sacl, + &sacl_defaulted)) { + return ::GetLastError(); + } + + for (DWORD ace_index = 0; ace_index < sacl->AceCount; ++ace_index) { + PSYSTEM_MANDATORY_LABEL_ACE ace; + + if (::GetAce(sacl, ace_index, reinterpret_cast<LPVOID*>(&ace)) && + ace->Header.AceType == SYSTEM_MANDATORY_LABEL_ACE_TYPE) { + ace->Mask |= SYSTEM_MANDATORY_LABEL_NO_READ_UP | + SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP; + break; + } + } + + if (!::SetKernelObjectSecurity(token, LABEL_SECURITY_INFORMATION, + security_desc)) + return ::GetLastError(); + + return ERROR_SUCCESS; +} + +DWORD HardenProcessIntegrityLevelPolicy() { + HANDLE token_handle; + if (!::OpenProcessToken(GetCurrentProcess(), READ_CONTROL | WRITE_OWNER, + &token_handle)) + return ::GetLastError(); + + base::win::ScopedHandle token(token_handle); + + return HardenTokenIntegrityLevelPolicy(token.Get()); +} + +DWORD CreateLowBoxToken(HANDLE base_token, + TokenType token_type, + PSECURITY_CAPABILITIES security_capabilities, + PHANDLE saved_handles, + DWORD saved_handles_count, + base::win::ScopedHandle* token) { + NtCreateLowBoxToken CreateLowBoxToken = nullptr; + ResolveNTFunctionPtr("NtCreateLowBoxToken", &CreateLowBoxToken); + + if (base::win::GetVersion() < base::win::Version::WIN8) + return ERROR_CALL_NOT_IMPLEMENTED; + + if (token_type != PRIMARY && token_type != IMPERSONATION) + return ERROR_INVALID_PARAMETER; + + if (!token) + return ERROR_INVALID_PARAMETER; + + base::win::ScopedHandle base_token_handle; + if (!base_token) { + HANDLE process_token = nullptr; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, + &process_token)) { + return ::GetLastError(); + } + base_token_handle.Set(process_token); + base_token = process_token; + } + OBJECT_ATTRIBUTES obj_attr; + InitializeObjectAttributes(&obj_attr, nullptr, 0, nullptr, nullptr); + HANDLE token_lowbox = nullptr; + + NTSTATUS status = CreateLowBoxToken( + &token_lowbox, base_token, TOKEN_ALL_ACCESS, &obj_attr, + security_capabilities->AppContainerSid, + security_capabilities->CapabilityCount, + security_capabilities->Capabilities, saved_handles_count, + saved_handles_count > 0 ? saved_handles : nullptr); + if (!NT_SUCCESS(status)) + return GetLastErrorFromNtStatus(status); + + base::win::ScopedHandle token_lowbox_handle(token_lowbox); + DCHECK(token_lowbox_handle.IsValid()); + + // Default from NtCreateLowBoxToken is a Primary token. + if (token_type == PRIMARY) { + *token = std::move(token_lowbox_handle); + return ERROR_SUCCESS; + } + + HANDLE dup_handle = nullptr; + if (!::DuplicateTokenEx(token_lowbox_handle.Get(), TOKEN_ALL_ACCESS, nullptr, + ::SecurityImpersonation, ::TokenImpersonation, + &dup_handle)) { + return ::GetLastError(); + } + + // Copy security descriptor from primary token as the new object will have + // the DACL from the current token's default DACL. + base::win::ScopedHandle token_for_sd(dup_handle); + std::vector<char> security_desc_buffer; + PSECURITY_DESCRIPTOR security_desc = nullptr; + DWORD last_error = GetObjectSecurityDescriptor( + token_lowbox_handle.Get(), DACL_SECURITY_INFORMATION, + &security_desc_buffer, &security_desc); + if (last_error != ERROR_SUCCESS) + return last_error; + + if (!::SetKernelObjectSecurity(token_for_sd.Get(), DACL_SECURITY_INFORMATION, + security_desc)) { + return ::GetLastError(); + } + + *token = std::move(token_for_sd); + + return ERROR_SUCCESS; +} + +DWORD CreateLowBoxObjectDirectory(PSID lowbox_sid, + bool open_directory, + base::win::ScopedHandle* directory) { + DWORD session_id = 0; + if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &session_id)) + return ::GetLastError(); + + LPWSTR sid_string = nullptr; + if (!::ConvertSidToStringSid(lowbox_sid, &sid_string)) + return ::GetLastError(); + + std::unique_ptr<wchar_t, LocalFreeDeleter> sid_string_ptr(sid_string); + std::wstring directory_path = base::StringPrintf( + L"\\Sessions\\%d\\AppContainerNamedObjects\\%ls", session_id, sid_string); + + NtCreateDirectoryObjectFunction CreateObjectDirectory = nullptr; + ResolveNTFunctionPtr("NtCreateDirectoryObject", &CreateObjectDirectory); + + OBJECT_ATTRIBUTES obj_attr; + UNICODE_STRING obj_name; + DWORD attributes = OBJ_CASE_INSENSITIVE; + if (open_directory) + attributes |= OBJ_OPENIF; + + sandbox::InitObjectAttribs(directory_path, attributes, nullptr, &obj_attr, + &obj_name, nullptr); + + HANDLE handle = nullptr; + NTSTATUS status = + CreateObjectDirectory(&handle, DIRECTORY_ALL_ACCESS, &obj_attr); + + if (!NT_SUCCESS(status)) + return GetLastErrorFromNtStatus(status); + directory->Set(handle); + + return ERROR_SUCCESS; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.h b/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.h new file mode 100644 index 0000000000..e1f75a89c9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/restricted_token_utils.h @@ -0,0 +1,105 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ +#define SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ + +#include <accctrl.h> +#include <windows.h> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/restricted_token.h" +#include "sandbox/win/src/security_level.h" + +// Contains the utility functions to be able to create restricted tokens based +// on a security profiles. + +namespace sandbox { + +// The type of the token returned by the CreateNakedToken. +enum TokenType { IMPERSONATION = 0, PRIMARY }; + +// Creates a restricted token from effective token. If it's nullptr then +// effective token of process is used instead. The parameter security_level +// determines how much the token isrestricted. The token_type determines if +// the token will be used as a primarytoken or impersonation token. The +// integrity level of the token is set to |integrity level| on Vista only. +// |token| is the output value containing the handle of the newly created +// restricted token. +// |lockdown_default_dacl| indicates the token's default DACL should be locked +// down to restrict what other process can open kernel resources created while +// running under the token. +// If the function succeeds, the return value is ERROR_SUCCESS. If the +// function fails, the return value is the win32 error code corresponding to +// the error. +DWORD CreateRestrictedToken(HANDLE effective_token, + TokenLevel security_level, + IntegrityLevel integrity_level, + TokenType token_type, + bool lockdown_default_dacl, + PSID unique_restricted_sid, + bool use_restricting_sids, + base::win::ScopedHandle* token); + +// Sets the integrity label on a object handle. +DWORD SetObjectIntegrityLabel(HANDLE handle, + SE_OBJECT_TYPE type, + const wchar_t* ace_access, + const wchar_t* integrity_level_sid); + +// Sets the integrity level on a token. This is only valid on Vista. It returns +// without failing on XP. If the integrity level that you specify is greater +// than the current integrity level, the function will fail. +DWORD SetTokenIntegrityLevel(HANDLE token, IntegrityLevel integrity_level); + +// Returns the integrity level SDDL string associated with a given +// IntegrityLevel value. +const wchar_t* GetIntegrityLevelString(IntegrityLevel integrity_level); + +// Sets the integrity level on the current process on Vista. It returns without +// failing on XP. If the integrity level that you specify is greater than the +// current integrity level, the function will fail. +DWORD SetProcessIntegrityLevel(IntegrityLevel integrity_level); + +// Hardens the integrity level policy on a token. This is only valid on Win 7 +// and above. Specifically it sets the policy to block read and execute so +// that a lower privileged process cannot open the token for impersonate or +// duplicate permissions. This should limit potential security holes. +DWORD HardenTokenIntegrityLevelPolicy(HANDLE token); + +// Hardens the integrity level policy on the current process. This is only +// valid on Win 7 and above. Specifically it sets the policy to block read +// and execute so that a lower privileged process cannot open the token for +// impersonate or duplicate permissions. This should limit potential security +// holes. +DWORD HardenProcessIntegrityLevelPolicy(); + +// Create a lowbox token. This is not valid prior to Windows 8. +// |base_token| a base token to derive the lowbox token from. Can be nullptr. +// |security_capabilities| list of LowBox capabilities to use when creating the +// token. +// |token| is the output value containing the handle of the newly created +// restricted token. +// |lockdown_default_dacl| indicates the token's default DACL should be locked +// down to restrict what other process can open kernel resources created while +// running under the token. +DWORD CreateLowBoxToken(HANDLE base_token, + TokenType token_type, + PSECURITY_CAPABILITIES security_capabilities, + PHANDLE saved_handles, + DWORD saved_handles_count, + base::win::ScopedHandle* token); + +// Create a lowbox object directory token. This is not valid prior to Windows 8. +// This returns the Win32 error code from the operation. +// |lowbox_sid| the SID for the LowBox. +// |open_directory| open the directory if it already exists. +// |directory| is the output value for the directory object. +DWORD CreateLowBoxObjectDirectory(PSID lowbox_sid, + bool open_directory, + base::win::ScopedHandle* directory); + +} // namespace sandbox + +#endif // SANDBOX_SRC_RESTRICTED_TOKEN_UTILS_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox.cc b/security/sandbox/chromium/sandbox/win/src/sandbox.cc new file mode 100644 index 0000000000..f65e379683 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox.h" + +#include <windows.h> + +#include "sandbox/win/src/broker_services.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { +// The section for IPC and policy. +SANDBOX_INTERCEPT HANDLE g_shared_section; +static bool s_is_broker = false; + +// GetBrokerServices: the current implementation relies on a shared section +// that is created by the broker and opened by the target. +BrokerServices* SandboxFactory::GetBrokerServices() { + // Can't be the broker if the shared section is open. + if (g_shared_section) + return nullptr; + // If the shared section does not exist we are the broker, then create + // the broker object. + s_is_broker = true; + return BrokerServicesBase::GetInstance(); +} + +// GetTargetServices implementation must follow the same technique as the +// GetBrokerServices, but in this case the logic is the opposite. +TargetServices* SandboxFactory::GetTargetServices() { + // Can't be the target if the section handle is not valid. + if (!g_shared_section) + return nullptr; + // We are the target + s_is_broker = false; + // Creates and returns the target services implementation. + return TargetServicesBase::GetInstance(); +} + +} // namespace sandbox + +// Allows querying for whether the current process has been sandboxed. +extern "C" bool __declspec(dllexport) IsSandboxedProcess() { + return !!sandbox::g_shared_section; +} diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox.h b/security/sandbox/chromium/sandbox/win/src/sandbox.h new file mode 100644 index 0000000000..858c350558 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox.h @@ -0,0 +1,228 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Sandbox is a sandbox library for windows processes. Use when you want a +// 'privileged' process and a 'locked down process' to interact with. +// The privileged process is called the broker and it is started by external +// means (such as the user starting it). The 'sandboxed' process is called the +// target and it is started by the broker. There can be many target processes +// started by a single broker process. This library provides facilities +// for both the broker and the target. +// +// The design rationale and relevant documents can be found at http://go/sbox. +// +// Note: this header does not include the SandboxFactory definitions because +// there are cases where the Sandbox library is linked against the main .exe +// while its API needs to be used in a DLL. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_H_ +#define SANDBOX_WIN_SRC_SANDBOX_H_ + +#if !defined(SANDBOX_FUZZ_TARGET) +#include <windows.h> +#else +#include "sandbox/win/fuzzer/fuzzer_types.h" +#endif + +#include <stddef.h> +#include <memory> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_types.h" + +// sandbox: Google User-Land Application Sandbox +namespace sandbox { + +class BrokerServices; +class PolicyDiagnosticsReceiver; +class ProcessState; +class TargetPolicy; +class TargetServices; + +// BrokerServices exposes all the broker API. +// The basic use is to start the target(s) and wait for them to end. +// +// This API is intended to be called in the following order +// (error checking omitted): +// BrokerServices* broker = SandboxFactory::GetBrokerServices(); +// broker->Init(); +// PROCESS_INFORMATION target; +// broker->SpawnTarget(target_exe_path, target_args, &target); +// ::ResumeThread(target->hThread); +// // -- later you can call: +// broker->WaitForAllTargets(option); +// +class BrokerServices { + public: + // Initializes the broker. Must be called before any other on this class. + // returns ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode Init() = 0; + + // Returns the interface pointer to a new, empty policy object. Use this + // interface to specify the sandbox policy for new processes created by + // SpawnTarget() + virtual scoped_refptr<TargetPolicy> CreatePolicy() = 0; + + // Creates a new target (child process) in a suspended state. + // Parameters: + // exe_path: This is the full path to the target binary. This parameter + // can be null and in this case the exe path must be the first argument + // of the command_line. + // command_line: The arguments to be passed as command line to the new + // process. This can be null if the exe_path parameter is not null. + // policy: This is the pointer to the policy object for the sandbox to + // be created. + // last_warning: The argument will contain an indication on whether + // the process security was initialized completely, Only set if the + // process can be used without a serious compromise in security. + // last_error: If an error or warning is returned from this method this + // parameter will hold the last Win32 error value. + // target: returns the resulting target process information such as process + // handle and PID just as if CreateProcess() had been called. The caller is + // responsible for closing the handles returned in this structure. + // Returns: + // ALL_OK if successful. All other return values imply failure. + virtual ResultCode SpawnTarget(const wchar_t* exe_path, + const wchar_t* command_line, + base::EnvironmentMap& env_map, + scoped_refptr<TargetPolicy> policy, + ResultCode* last_warning, + DWORD* last_error, + PROCESS_INFORMATION* target) = 0; + + // This call blocks (waits) for all the targets to terminate. + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode WaitForAllTargets() = 0; + + // Adds an unsandboxed process as a peer for policy decisions (e.g. + // HANDLES_DUP_ANY policy). + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode AddTargetPeer(HANDLE peer_process) = 0; + + // This call creates a snapshot of policies managed by the sandbox and + // returns them via a helper class. + // Parameters: + // receiver: The |PolicyDiagnosticsReceiver| implementation will be + // called to accept the results of the call. + // Returns: + // ALL_OK if the request was dispatched. All other return values + // imply failure, and the responder will not receive its completion + // callback. + virtual ResultCode GetPolicyDiagnostics( + std::unique_ptr<PolicyDiagnosticsReceiver> receiver) = 0; + + // Derive a capability PSID from the given string. + virtual bool DeriveCapabilitySidFromName(const wchar_t* name, + PSID derived_sid, + DWORD sid_buffer_length) = 0; + + protected: + ~BrokerServices() {} +}; + +// TargetServices models the current process from the perspective +// of a target process. To obtain a pointer to it use +// Sandbox::GetTargetServices(). Note that this call returns a non-null +// pointer only if this process is in fact a target. A process is a target +// only if the process was spawned by a call to BrokerServices::SpawnTarget(). +// +// This API allows the target to gain access to resources with a high +// privilege token and then when it is ready to perform dangerous activities +// (such as download content from the web) it can lower its token and +// enter into locked-down (sandbox) mode. +// The typical usage is as follows: +// +// TargetServices* target_services = Sandbox::GetTargetServices(); +// if (target_services) { +// // We are the target. +// target_services->Init(); +// // Do work that requires high privileges here. +// // .... +// // When ready to enter lock-down mode call LowerToken: +// target_services->LowerToken(); +// } +// +// For more information see the BrokerServices API documentation. +class TargetServices { + public: + // Initializes the target. Must call this function before any other. + // returns ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode Init() = 0; + + // Discards the impersonation token and uses the lower token, call before + // processing any untrusted data or running third-party code. If this call + // fails the current process could be terminated immediately. + virtual void LowerToken() = 0; + + // Returns the ProcessState object. Through that object it's possible to have + // information about the current state of the process, such as whether + // LowerToken has been called or not. + virtual ProcessState* GetState() = 0; + + // Requests the broker to duplicate the supplied handle into the target + // process. The target process must be an active sandbox child process + // and the source process must have a corresponding policy allowing + // handle duplication for this object type. + // Returns: + // ALL_OK if successful. All other return values imply failure. + // If the return is ERROR_GENERIC, you can call ::GetLastError() to get + // more information. + virtual ResultCode DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) = 0; + + virtual ResultCode GetComplexLineBreaks(const WCHAR* text, uint32_t length, + uint8_t* break_before) = 0; + + protected: + ~TargetServices() {} +}; + +class PolicyInfo { + public: + // Returns a JSON representation of the policy snapshot. + // This pointer has the same lifetime as this PolicyInfo object. + virtual const char* JsonString() = 0; + virtual ~PolicyInfo() {} +}; + +// This is returned by BrokerServices::GetPolicyDiagnostics(). +// PolicyInfo entries need not be ordered. +class PolicyList { + public: + virtual std::vector<std::unique_ptr<PolicyInfo>>::iterator begin() = 0; + virtual std::vector<std::unique_ptr<PolicyInfo>>::iterator end() = 0; + virtual size_t size() const = 0; + virtual ~PolicyList() {} +}; + +// This class mediates calls to BrokerServices::GetPolicyDiagnostics(). +class PolicyDiagnosticsReceiver { + public: + // ReceiveDiagnostics() should return quickly and should not block the + // thread on which it is called. + virtual void ReceiveDiagnostics(std::unique_ptr<PolicyList> policies) = 0; + // OnError() is passed any errors encountered and |ReceiveDiagnostics| + // will not be called. + virtual void OnError(ResultCode code) = 0; + virtual ~PolicyDiagnosticsReceiver() {} +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox.vcproj b/security/sandbox/chromium/sandbox/win/src/sandbox.vcproj new file mode 100644 index 0000000000..229441cbd5 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox.vcproj @@ -0,0 +1,648 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="sandbox" + ProjectGUID="{881F6A97-D539-4C48-B401-DF04385B2343}" + RootNamespace="sandbox" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + ForcedIncludeFiles="stdafx.h" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + ConfigurationType="4" + InheritedPropertySheets="$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\testing\using_gtest.vsprops" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="security" + > + <File + RelativePath=".\acl.cc" + > + </File> + <File + RelativePath=".\acl.h" + > + </File> + <File + RelativePath=".\dep.cc" + > + </File> + <File + RelativePath=".\dep.h" + > + </File> + <File + RelativePath=".\job.cc" + > + </File> + <File + RelativePath=".\job.h" + > + </File> + <File + RelativePath=".\restricted_token.cc" + > + </File> + <File + RelativePath=".\restricted_token.h" + > + </File> + <File + RelativePath=".\restricted_token_utils.cc" + > + </File> + <File + RelativePath=".\restricted_token_utils.h" + > + </File> + <File + RelativePath=".\security_level.h" + > + </File> + <File + RelativePath=".\sid.cc" + > + </File> + <File + RelativePath=".\sid.h" + > + </File> + <File + RelativePath=".\window.cc" + > + </File> + <File + RelativePath=".\window.h" + > + </File> + </Filter> + <Filter + Name="Interception" + > + <File + RelativePath=".\eat_resolver.cc" + > + </File> + <File + RelativePath=".\eat_resolver.h" + > + </File> + <File + RelativePath=".\interception.cc" + > + </File> + <File + RelativePath=".\interception.h" + > + </File> + <File + RelativePath=".\interception_agent.cc" + > + </File> + <File + RelativePath=".\interception_agent.h" + > + </File> + <File + RelativePath=".\interception_internal.h" + > + </File> + <File + RelativePath=".\pe_image.cc" + > + </File> + <File + RelativePath=".\pe_image.h" + > + </File> + <File + RelativePath=".\resolver.cc" + > + </File> + <File + RelativePath=".\resolver.h" + > + </File> + <File + RelativePath=".\service_resolver.cc" + > + </File> + <File + RelativePath=".\service_resolver.h" + > + </File> + <File + RelativePath=".\sidestep_resolver.cc" + > + </File> + <File + RelativePath=".\sidestep_resolver.h" + > + </File> + <File + RelativePath=".\target_interceptions.cc" + > + </File> + <File + RelativePath=".\target_interceptions.h" + > + </File> + <File + RelativePath=".\Wow64.cc" + > + </File> + <File + RelativePath=".\Wow64.h" + > + </File> + <Filter + Name="sidestep" + > + <File + RelativePath=".\sidestep\ia32_modrm_map.cpp" + > + </File> + <File + RelativePath=".\sidestep\ia32_opcode_map.cpp" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler.cpp" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler.h" + > + </File> + <File + RelativePath=".\sidestep\mini_disassembler_types.h" + > + </File> + <File + RelativePath=".\sidestep\preamble_patcher.h" + > + </File> + <File + RelativePath=".\sidestep\preamble_patcher_with_stub.cpp" + > + </File> + </Filter> + </Filter> + <Filter + Name="nt_level" + > + <File + RelativePath=".\nt_internals.h" + > + </File> + <File + RelativePath=".\policy_target.cc" + > + </File> + <File + RelativePath=".\policy_target.h" + > + </File> + <File + RelativePath=".\sandbox_nt_types.h" + > + </File> + <File + RelativePath=".\sandbox_nt_util.cc" + > + </File> + <File + RelativePath=".\sandbox_nt_util.h" + > + </File> + </Filter> + <Filter + Name="Policy_handlers" + > + <File + RelativePath=".\filesystem_dispatcher.cc" + > + </File> + <File + RelativePath=".\filesystem_dispatcher.h" + > + </File> + <File + RelativePath=".\filesystem_interception.cc" + > + </File> + <File + RelativePath=".\filesystem_interception.h" + > + </File> + <File + RelativePath=".\filesystem_policy.cc" + > + </File> + <File + RelativePath=".\filesystem_policy.h" + > + </File> + <File + RelativePath=".\named_pipe_dispatcher.cc" + > + </File> + <File + RelativePath=".\named_pipe_dispatcher.h" + > + </File> + <File + RelativePath=".\named_pipe_interception.cc" + > + </File> + <File + RelativePath=".\named_pipe_interception.h" + > + </File> + <File + RelativePath=".\named_pipe_policy.cc" + > + </File> + <File + RelativePath=".\named_pipe_policy.h" + > + </File> + <File + RelativePath=".\policy_params.h" + > + </File> + <File + RelativePath=".\process_thread_dispatcher.cc" + > + </File> + <File + RelativePath=".\process_thread_dispatcher.h" + > + </File> + <File + RelativePath=".\process_thread_interception.cc" + > + </File> + <File + RelativePath=".\process_thread_interception.h" + > + </File> + <File + RelativePath=".\process_thread_policy.cc" + > + </File> + <File + RelativePath=".\process_thread_policy.h" + > + </File> + <File + RelativePath=".\registry_dispatcher.cc" + > + </File> + <File + RelativePath=".\registry_dispatcher.h" + > + </File> + <File + RelativePath=".\registry_interception.cc" + > + </File> + <File + RelativePath=".\registry_interception.h" + > + </File> + <File + RelativePath=".\registry_policy.cc" + > + </File> + <File + RelativePath=".\registry_policy.h" + > + </File> + <File + RelativePath=".\sync_dispatcher.cc" + > + </File> + <File + RelativePath=".\sync_dispatcher.h" + > + </File> + <File + RelativePath=".\sync_interception.cc" + > + </File> + <File + RelativePath=".\sync_interception.h" + > + </File> + <File + RelativePath=".\sync_policy.cc" + > + </File> + <File + RelativePath=".\sync_policy.h" + > + </File> + </Filter> + <Filter + Name="IPC" + > + <File + RelativePath=".\crosscall_client.h" + > + </File> + <File + RelativePath=".\crosscall_params.h" + > + </File> + <File + RelativePath=".\crosscall_server.cc" + > + </File> + <File + RelativePath=".\crosscall_server.h" + > + </File> + <File + RelativePath=".\ipc_tags.h" + > + </File> + <File + RelativePath=".\sharedmem_ipc_client.cc" + > + </File> + <File + RelativePath=".\sharedmem_ipc_client.h" + > + </File> + <File + RelativePath=".\sharedmem_ipc_server.cc" + > + </File> + <File + RelativePath=".\sharedmem_ipc_server.h" + > + </File> + </Filter> + <Filter + Name="Policy_base" + > + <File + RelativePath=".\policy_engine_opcodes.cc" + > + </File> + <File + RelativePath=".\policy_engine_opcodes.h" + > + </File> + <File + RelativePath=".\policy_engine_params.h" + > + </File> + <File + RelativePath=".\policy_engine_processor.cc" + > + </File> + <File + RelativePath=".\policy_engine_processor.h" + > + </File> + <File + RelativePath=".\policy_low_level.cc" + > + </File> + <File + RelativePath=".\policy_low_level.h" + > + </File> + <File + RelativePath=".\sandbox_policy_base.cc" + > + </File> + <File + RelativePath=".\sandbox_policy_base.h" + > + </File> + </Filter> + <File + RelativePath=".\broker_services.cc" + > + </File> + <File + RelativePath=".\broker_services.h" + > + </File> + <File + RelativePath=".\internal_types.h" + > + </File> + <File + RelativePath=".\policy_broker.cc" + > + </File> + <File + RelativePath=".\policy_broker.h" + > + </File> + <File + RelativePath=".\sandbox.cc" + > + </File> + <File + RelativePath=".\sandbox.h" + > + </File> + <File + RelativePath=".\sandbox_factory.h" + > + </File> + <File + RelativePath=".\sandbox_policy.h" + > + </File> + <File + RelativePath=".\sandbox_types.h" + > + </File> + <File + RelativePath=".\sandbox_utils.cc" + > + </File> + <File + RelativePath=".\sandbox_utils.h" + > + </File> + <File + RelativePath=".\shared_handles.cc" + > + </File> + <File + RelativePath=".\shared_handles.h" + > + </File> + <File + RelativePath=".\stdafx.cc" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + <FileConfiguration + Name="Release|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="0" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + <File + RelativePath=".\target_process.cc" + > + </File> + <File + RelativePath=".\target_process.h" + > + </File> + <File + RelativePath=".\target_services.cc" + > + </File> + <File + RelativePath=".\target_services.h" + > + </File> + <File + RelativePath=".\win2k_threadpool.cc" + > + </File> + <File + RelativePath=".\win2k_threadpool.h" + > + </File> + <File + RelativePath=".\win_utils.cc" + > + </File> + <File + RelativePath=".\win_utils.h" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_factory.h b/security/sandbox/chromium/sandbox/win/src/sandbox_factory.h new file mode 100644 index 0000000000..07402521dc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_factory.h @@ -0,0 +1,52 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_FACTORY_H__ +#define SANDBOX_SRC_SANDBOX_FACTORY_H__ + +#include "base/macros.h" +#include "sandbox/win/src/sandbox.h" + +// SandboxFactory is a set of static methods to get access to the broker +// or target services object. Only one of the two methods (GetBrokerServices, +// GetTargetServices) will return a non-null pointer and that should be used +// as the indication that the process is the broker or the target: +// +// BrokerServices* broker_services = SandboxFactory::GetBrokerServices(); +// if (broker_services) { +// //we are the broker, call broker api here +// broker_services->Init(); +// } else { +// TargetServices* target_services = SandboxFactory::GetTargetServices(); +// if (target_services) { +// //we are the target, call target api here +// target_services->Init(); +// } +// +// The methods in this class are expected to be called from a single thread +// +// The Sandbox library needs to be linked against the main executable, but +// sometimes the API calls are issued from a DLL that loads into the exe +// process. These factory methods then need to be called from the main +// exe and the interface pointers then can be safely passed to the DLL where +// the Sandbox API calls are made. +namespace sandbox { + +class SandboxFactory { + public: + // Returns the Broker API interface, returns nullptr if this process is the + // target. + static BrokerServices* GetBrokerServices(); + + // Returns the Target API interface, returns nullptr if this process is the + // broker. + static TargetServices* GetTargetServices(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(SandboxFactory); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_FACTORY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_globals.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_globals.cc new file mode 100644 index 0000000000..3387e3f604 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_globals.cc @@ -0,0 +1,18 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> + +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// The section for IPC and policy. +SANDBOX_INTERCEPT HANDLE g_shared_section = nullptr; + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt = {}; + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_nt_types.h b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_types.h new file mode 100644 index 0000000000..eff0f019d3 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_types.h @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_NT_TYPES_H__ +#define SANDBOX_SRC_SANDBOX_NT_TYPES_H__ + +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +struct NtExports { + NtAllocateVirtualMemoryFunction AllocateVirtualMemory; + NtCloseFunction Close; + NtDuplicateObjectFunction DuplicateObject; + NtFreeVirtualMemoryFunction FreeVirtualMemory; + NtMapViewOfSectionFunction MapViewOfSection; + NtProtectVirtualMemoryFunction ProtectVirtualMemory; + NtQueryInformationProcessFunction QueryInformationProcess; + NtQueryObjectFunction QueryObject; + NtQuerySectionFunction QuerySection; + NtQueryVirtualMemoryFunction QueryVirtualMemory; + NtUnmapViewOfSectionFunction UnmapViewOfSection; + NtSignalAndWaitForSingleObjectFunction SignalAndWaitForSingleObject; + NtWaitForSingleObjectFunction WaitForSingleObject; + RtlAllocateHeapFunction RtlAllocateHeap; + RtlAnsiStringToUnicodeStringFunction RtlAnsiStringToUnicodeString; + RtlCompareUnicodeStringFunction RtlCompareUnicodeString; + RtlCreateHeapFunction RtlCreateHeap; + RtlCreateUserThreadFunction RtlCreateUserThread; + RtlDestroyHeapFunction RtlDestroyHeap; + RtlFreeHeapFunction RtlFreeHeap; + _strnicmpFunction _strnicmp; + strlenFunction strlen; + wcslenFunction wcslen; + memcpyFunction memcpy; +}; + +// This is the value used for the ntdll level allocator. +enum AllocationType { + NT_ALLOC, + NT_PAGE +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_NT_TYPES_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.cc new file mode 100644 index 0000000000..2641a13d04 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.cc @@ -0,0 +1,755 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox_nt_util.h" + +#include <stddef.h> +#include <stdint.h> + +#include <string> + +#include "base/compiler_specific.h" +#include "base/win/pe_image.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" + +namespace sandbox { + +// This is the list of all imported symbols from ntdll.dll. +SANDBOX_INTERCEPT NtExports g_nt; + +} // namespace sandbox + +namespace { + +#if defined(_WIN64) +// Align a pointer to the next allocation granularity boundary. +inline char* AlignToBoundary(void* ptr, size_t increment) { + const size_t kAllocationGranularity = (64 * 1024) - 1; + uintptr_t ptr_int = reinterpret_cast<uintptr_t>(ptr); + uintptr_t ret_ptr = + (ptr_int + increment + kAllocationGranularity) & ~kAllocationGranularity; + // Check for overflow. + if (ret_ptr < ptr_int) + return nullptr; + return reinterpret_cast<char*>(ret_ptr); +} + +// Allocate a memory block somewhere within 2GiB of a specified base address. +// This is used for the DLL hooking code to get a valid trampoline location +// which must be within +/- 2GiB of the base. We only consider +2GiB for now. +void* AllocateNearTo(void* source, size_t size) { + using sandbox::g_nt; + // 2GiB, maximum upper bound the allocation address must be within. + const size_t kMaxSize = 0x80000000ULL; + // We don't support null as a base as this would just pick an arbitrary + // address when passed to NtAllocateVirtualMemory. + if (!source) + return nullptr; + // Ignore an allocation which is larger than the maximum. + if (size > kMaxSize) + return nullptr; + + // Ensure base address is aligned to the allocation granularity boundary. + char* base = AlignToBoundary(source, 0); + if (!base) + return nullptr; + // Set top address to be base + 2GiB. + const char* top_address = base + kMaxSize; + + while (base < top_address) { + // Avoid memset inserted by -ftrivial-auto-var-init=pattern. + STACK_UNINITIALIZED MEMORY_BASIC_INFORMATION mem_info; + NTSTATUS status = + g_nt.QueryVirtualMemory(NtCurrentProcess, base, MemoryBasicInformation, + &mem_info, sizeof(mem_info), nullptr); + if (!NT_SUCCESS(status)) + break; + + if ((mem_info.State == MEM_FREE) && (mem_info.RegionSize >= size)) { + // We've found a valid free block, try and allocate it for use. + // Note that we need to both commit and reserve the block for the + // allocation to succeed as per Windows virtual memory requirements. + void* ret_base = mem_info.BaseAddress; + status = + g_nt.AllocateVirtualMemory(NtCurrentProcess, &ret_base, 0, &size, + MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + // Shouldn't fail, but if it does we'll just continue and try next block. + if (NT_SUCCESS(status)) + return ret_base; + } + + // Update base past current allocation region. + base = AlignToBoundary(mem_info.BaseAddress, mem_info.RegionSize); + if (!base) + break; + } + return nullptr; +} +#else // defined(_WIN64). +void* AllocateNearTo(void* source, size_t size) { + using sandbox::g_nt; + + // In 32-bit processes allocations below 512k are predictable, so mark + // anything in that range as reserved and retry until we get a good address. + const void* const kMinAddress = reinterpret_cast<void*>(512 * 1024); + NTSTATUS ret; + SIZE_T actual_size; + void* base; + do { + base = nullptr; + actual_size = 64 * 1024; + ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, + MEM_RESERVE, PAGE_NOACCESS); + if (!NT_SUCCESS(ret)) + return nullptr; + } while (base < kMinAddress); + + actual_size = size; + ret = g_nt.AllocateVirtualMemory(NtCurrentProcess, &base, 0, &actual_size, + MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(ret)) + return nullptr; + return base; +} +#endif // defined(_WIN64). + +} // namespace. + +namespace sandbox { + +// Handle for our private heap. +void* g_heap = nullptr; + +SANDBOX_INTERCEPT HANDLE g_shared_section; +SANDBOX_INTERCEPT size_t g_shared_IPC_size = 0; +SANDBOX_INTERCEPT size_t g_shared_policy_size = 0; + +void* volatile g_shared_policy_memory = nullptr; +void* volatile g_shared_IPC_memory = nullptr; + +// Both the IPC and the policy share a single region of memory in which the IPC +// memory is first and the policy memory is last. +bool MapGlobalMemory() { + if (!g_shared_IPC_memory) { + void* memory = nullptr; + SIZE_T size = 0; + // Map the entire shared section from the start. + NTSTATUS ret = + g_nt.MapViewOfSection(g_shared_section, NtCurrentProcess, &memory, 0, 0, + nullptr, &size, ViewUnmap, 0, PAGE_READWRITE); + + if (!NT_SUCCESS(ret) || !memory) { + NOTREACHED_NT(); + return false; + } + + if (_InterlockedCompareExchangePointer(&g_shared_IPC_memory, memory, + nullptr)) { + // Somebody beat us to the memory setup. + VERIFY_SUCCESS(g_nt.UnmapViewOfSection(NtCurrentProcess, memory)); + } + DCHECK_NT(g_shared_IPC_size > 0); + g_shared_policy_memory = + reinterpret_cast<char*>(g_shared_IPC_memory) + g_shared_IPC_size; + } + DCHECK_NT(g_shared_policy_memory); + DCHECK_NT(g_shared_policy_size > 0); + return true; +} + +void* GetGlobalIPCMemory() { + if (!MapGlobalMemory()) + return nullptr; + return g_shared_IPC_memory; +} + +void* GetGlobalPolicyMemory() { + if (!MapGlobalMemory()) + return nullptr; + return g_shared_policy_memory; +} + +bool InitHeap() { + if (!g_heap) { + // Create a new heap using default values for everything. + void* heap = + g_nt.RtlCreateHeap(HEAP_GROWABLE, nullptr, 0, 0, nullptr, nullptr); + if (!heap) + return false; + + if (_InterlockedCompareExchangePointer(&g_heap, heap, nullptr)) { + // Somebody beat us to the memory setup. + g_nt.RtlDestroyHeap(heap); + } + } + return !!g_heap; +} + +// Physically reads or writes from memory to verify that (at this time), it is +// valid. Returns a dummy value. +int TouchMemory(void* buffer, size_t size_bytes, RequiredAccess intent) { + const int kPageSize = 4096; + int dummy = 0; + volatile char* start = reinterpret_cast<char*>(buffer); + volatile char* end = start + size_bytes - 1; + + if (WRITE == intent) { + for (; start < end; start += kPageSize) { + *start = *start; + } + *end = *end; + } else { + for (; start < end; start += kPageSize) { + dummy += *start; + } + dummy += *end; + } + + return dummy; +} + +bool ValidParameter(void* buffer, size_t size, RequiredAccess intent) { + DCHECK_NT(size); + __try { + TouchMemory(buffer, size, intent); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } + return true; +} + +NTSTATUS CopyData(void* destination, const void* source, size_t bytes) { + NTSTATUS ret = STATUS_SUCCESS; + __try { + g_nt.memcpy(destination, source, bytes); + } __except (EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + return ret; +} + +NTSTATUS AllocAndGetFullPath( + HANDLE root, + const wchar_t* path, + std::unique_ptr<wchar_t, NtAllocDeleter>* full_path) { + if (!InitHeap()) + return STATUS_NO_MEMORY; + + DCHECK_NT(full_path); + DCHECK_NT(path); + NTSTATUS ret = STATUS_UNSUCCESSFUL; + __try { + do { + static NtQueryObjectFunction NtQueryObject = nullptr; + if (!NtQueryObject) + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + ULONG size = 0; + // Query the name information a first time to get the size of the name. + ret = NtQueryObject(root, ObjectNameInformation, nullptr, 0, &size); + + std::unique_ptr<OBJECT_NAME_INFORMATION, NtAllocDeleter> handle_name; + if (size) { + handle_name.reset(reinterpret_cast<OBJECT_NAME_INFORMATION*>( + new (NT_ALLOC) BYTE[size])); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + ret = NtQueryObject(root, ObjectNameInformation, handle_name.get(), + size, &size); + } + + if (STATUS_SUCCESS != ret) + break; + + // Space for path + '\' + name + '\0'. + size_t name_length = + handle_name->ObjectName.Length + (wcslen(path) + 2) * sizeof(wchar_t); + full_path->reset(new (NT_ALLOC) wchar_t[name_length / sizeof(wchar_t)]); + if (!*full_path) + break; + wchar_t* off = full_path->get(); + ret = CopyData(off, handle_name->ObjectName.Buffer, + handle_name->ObjectName.Length); + if (!NT_SUCCESS(ret)) + break; + off += handle_name->ObjectName.Length / sizeof(wchar_t); + *off = L'\\'; + off += 1; + ret = CopyData(off, path, wcslen(path) * sizeof(wchar_t)); + if (!NT_SUCCESS(ret)) + break; + off += wcslen(path); + *off = L'\0'; + } while (false); + } __except (EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + + if (!NT_SUCCESS(ret) && *full_path) + full_path->reset(nullptr); + + return ret; +} + +// Hacky code... replace with AllocAndCopyObjectAttributes. +NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object, + std::unique_ptr<wchar_t, NtAllocDeleter>* out_name, + uint32_t* attributes, + HANDLE* root) { + if (!InitHeap()) + return STATUS_NO_MEMORY; + + DCHECK_NT(out_name); + NTSTATUS ret = STATUS_UNSUCCESSFUL; + __try { + do { + if (in_object->RootDirectory != static_cast<HANDLE>(0) && !root) + break; + if (!in_object->ObjectName) + break; + if (!in_object->ObjectName->Buffer) + break; + + size_t size = in_object->ObjectName->Length + sizeof(wchar_t); + out_name->reset(new (NT_ALLOC) wchar_t[size / sizeof(wchar_t)]); + if (!*out_name) + break; + + ret = CopyData(out_name->get(), in_object->ObjectName->Buffer, + size - sizeof(wchar_t)); + if (!NT_SUCCESS(ret)) + break; + + out_name->get()[size / sizeof(wchar_t) - 1] = L'\0'; + + if (attributes) + *attributes = in_object->Attributes; + + if (root) + *root = in_object->RootDirectory; + ret = STATUS_SUCCESS; + } while (false); + } __except (EXCEPTION_EXECUTE_HANDLER) { + ret = GetExceptionCode(); + } + + if (!NT_SUCCESS(ret) && *out_name) + out_name->reset(nullptr); + + return ret; +} + +NTSTATUS GetProcessId(HANDLE process, DWORD* process_id) { + PROCESS_BASIC_INFORMATION proc_info; + ULONG bytes_returned; + + NTSTATUS ret = + g_nt.QueryInformationProcess(process, ProcessBasicInformation, &proc_info, + sizeof(proc_info), &bytes_returned); + if (!NT_SUCCESS(ret) || sizeof(proc_info) != bytes_returned) + return ret; + + *process_id = proc_info.UniqueProcessId; + return STATUS_SUCCESS; +} + +bool IsSameProcess(HANDLE process) { + if (NtCurrentProcess == process) + return true; + + static DWORD s_process_id = 0; + + if (!s_process_id) { + NTSTATUS ret = GetProcessId(NtCurrentProcess, &s_process_id); + if (!NT_SUCCESS(ret)) + return false; + } + + DWORD process_id; + NTSTATUS ret = GetProcessId(process, &process_id); + if (!NT_SUCCESS(ret)) + return false; + + return (process_id == s_process_id); +} + +bool IsValidImageSection(HANDLE section, + PVOID* base, + PLARGE_INTEGER offset, + PSIZE_T view_size) { + if (!section || !base || !view_size || offset) + return false; + + HANDLE query_section; + + NTSTATUS ret = + g_nt.DuplicateObject(NtCurrentProcess, section, NtCurrentProcess, + &query_section, SECTION_QUERY, 0, 0); + if (!NT_SUCCESS(ret)) + return false; + + SECTION_BASIC_INFORMATION basic_info; + SIZE_T bytes_returned; + ret = g_nt.QuerySection(query_section, SectionBasicInformation, &basic_info, + sizeof(basic_info), &bytes_returned); + + VERIFY_SUCCESS(g_nt.Close(query_section)); + + if (!NT_SUCCESS(ret) || sizeof(basic_info) != bytes_returned) + return false; + + if (!(basic_info.Attributes & SEC_IMAGE)) + return false; + + // Windows 10 2009+ may open PEs as SEC_IMAGE_NO_EXECUTE in non-dll-loading + // paths which looks identical to dll-loading unless we check if the section + // handle has execute rights. + // Avoid memset inserted by -ftrivial-auto-var-init=pattern. + STACK_UNINITIALIZED OBJECT_BASIC_INFORMATION obj_info; + ULONG obj_size_returned; + ret = g_nt.QueryObject(section, ObjectBasicInformation, &obj_info, + sizeof(obj_info), &obj_size_returned); + + if (!NT_SUCCESS(ret) || sizeof(obj_info) != obj_size_returned) + return false; + + if (!(obj_info.GrantedAccess & SECTION_MAP_EXECUTE)) + return false; + + return true; +} + +UNICODE_STRING* AnsiToUnicode(const char* string) { + ANSI_STRING ansi_string; + ansi_string.Length = static_cast<USHORT>(g_nt.strlen(string)); + ansi_string.MaximumLength = ansi_string.Length + 1; + ansi_string.Buffer = const_cast<char*>(string); + + if (ansi_string.Length > ansi_string.MaximumLength) + return nullptr; + + size_t name_bytes = + ansi_string.MaximumLength * sizeof(wchar_t) + sizeof(UNICODE_STRING); + + UNICODE_STRING* out_string = + reinterpret_cast<UNICODE_STRING*>(new (NT_ALLOC) char[name_bytes]); + if (!out_string) + return nullptr; + + out_string->MaximumLength = ansi_string.MaximumLength * sizeof(wchar_t); + out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]); + + BOOLEAN alloc_destination = false; + NTSTATUS ret = g_nt.RtlAnsiStringToUnicodeString(out_string, &ansi_string, + alloc_destination); + DCHECK_NT(STATUS_BUFFER_OVERFLOW != ret); + if (!NT_SUCCESS(ret)) { + operator delete(out_string, NT_ALLOC); + return nullptr; + } + + return out_string; +} + +UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32_t* flags) { +// PEImage's dtor won't be run during SEH unwinding, but that's OK. +#pragma warning(push) +#pragma warning(disable : 4509) + UNICODE_STRING* out_name = nullptr; + __try { + do { + *flags = 0; + base::win::PEImage pe(module); + + if (!pe.VerifyMagic()) + break; + *flags |= MODULE_IS_PE_IMAGE; + + PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory(); + if (exports) { + char* name = reinterpret_cast<char*>(pe.RVAToAddr(exports->Name)); + out_name = AnsiToUnicode(name); + } + + PIMAGE_NT_HEADERS headers = pe.GetNTHeaders(); + if (headers) { + if (headers->OptionalHeader.AddressOfEntryPoint) + *flags |= MODULE_HAS_ENTRY_POINT; + if (headers->OptionalHeader.SizeOfCode) + *flags |= MODULE_HAS_CODE; + } + } while (false); + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + + return out_name; +#pragma warning(pop) +} + +const char* GetAnsiImageInfoFromModule(HMODULE module) { +// PEImage's dtor won't be run during SEH unwinding, but that's OK. +#pragma warning(push) +#pragma warning(disable : 4509) + const char* out_name = nullptr; + __try { + do { + base::win::PEImage pe(module); + + if (!pe.VerifyMagic()) + break; + + PIMAGE_EXPORT_DIRECTORY exports = pe.GetExportDirectory(); + if (exports) + out_name = static_cast<const char*>(pe.RVAToAddr(exports->Name)); + } while (false); + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + + return out_name; +#pragma warning(pop) +} + +UNICODE_STRING* GetBackingFilePath(PVOID address) { + // We'll start with something close to max_path charactes for the name. + SIZE_T buffer_bytes = MAX_PATH * 2; + + for (;;) { + MEMORY_SECTION_NAME* section_name = reinterpret_cast<MEMORY_SECTION_NAME*>( + new (NT_ALLOC) char[buffer_bytes]); + + if (!section_name) + return nullptr; + + SIZE_T returned_bytes; + NTSTATUS ret = + g_nt.QueryVirtualMemory(NtCurrentProcess, address, MemorySectionName, + section_name, buffer_bytes, &returned_bytes); + + if (STATUS_BUFFER_OVERFLOW == ret) { + // Retry the call with the given buffer size. + operator delete(section_name, NT_ALLOC); + section_name = nullptr; + buffer_bytes = returned_bytes; + continue; + } + if (!NT_SUCCESS(ret)) { + operator delete(section_name, NT_ALLOC); + return nullptr; + } + + return reinterpret_cast<UNICODE_STRING*>(section_name); + } +} + +UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path) { + if ((!module_path) || (!module_path->Buffer)) + return nullptr; + + wchar_t* sep = nullptr; + int start_pos = module_path->Length / sizeof(wchar_t) - 1; + int ix = start_pos; + + for (; ix >= 0; --ix) { + if (module_path->Buffer[ix] == L'\\') { + sep = &module_path->Buffer[ix]; + break; + } + } + + // Ends with path separator. Not a valid module name. + if ((ix == start_pos) && sep) + return nullptr; + + // No path separator found. Use the entire name. + if (!sep) { + sep = &module_path->Buffer[-1]; + } + + // Add one to the size so we can null terminate the string. + size_t size_bytes = (start_pos - ix + 1) * sizeof(wchar_t); + + // Based on the code above, size_bytes should always be small enough + // to make the static_cast below safe. + DCHECK_NT(UINT16_MAX > size_bytes); + char* str_buffer = new (NT_ALLOC) char[size_bytes + sizeof(UNICODE_STRING)]; + if (!str_buffer) + return nullptr; + + UNICODE_STRING* out_string = reinterpret_cast<UNICODE_STRING*>(str_buffer); + out_string->Buffer = reinterpret_cast<wchar_t*>(&out_string[1]); + out_string->Length = static_cast<USHORT>(size_bytes - sizeof(wchar_t)); + out_string->MaximumLength = static_cast<USHORT>(size_bytes); + + NTSTATUS ret = CopyData(out_string->Buffer, &sep[1], out_string->Length); + if (!NT_SUCCESS(ret)) { + operator delete(out_string, NT_ALLOC); + return nullptr; + } + + out_string->Buffer[out_string->Length / sizeof(wchar_t)] = L'\0'; + return out_string; +} + +NTSTATUS AutoProtectMemory::ChangeProtection(void* address, + size_t bytes, + ULONG protect) { + DCHECK_NT(!changed_); + SIZE_T new_bytes = bytes; + NTSTATUS ret = g_nt.ProtectVirtualMemory(NtCurrentProcess, &address, + &new_bytes, protect, &old_protect_); + if (NT_SUCCESS(ret)) { + changed_ = true; + address_ = address; + bytes_ = new_bytes; + } + + return ret; +} + +NTSTATUS AutoProtectMemory::RevertProtection() { + if (!changed_) + return STATUS_SUCCESS; + + DCHECK_NT(address_); + DCHECK_NT(bytes_); + + SIZE_T new_bytes = bytes_; + NTSTATUS ret = g_nt.ProtectVirtualMemory( + NtCurrentProcess, &address_, &new_bytes, old_protect_, &old_protect_); + DCHECK_NT(NT_SUCCESS(ret)); + + changed_ = false; + address_ = nullptr; + bytes_ = 0; + old_protect_ = 0; + + return ret; +} + +bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, + DWORD length, + uint32_t file_info_class) { + if (FileRenameInformation != file_info_class) + return false; + + if (length < sizeof(FILE_RENAME_INFORMATION)) + return false; + + // Make sure file name length doesn't exceed the message length + if (length - offsetof(FILE_RENAME_INFORMATION, FileName) < + file_info->FileNameLength) + return false; + + // We don't support a root directory. + if (file_info->RootDirectory) + return false; + + static const wchar_t kPathPrefix[] = {L'\\', L'?', L'?', L'\\'}; + + // Check if it starts with \\??\\. We don't support relative paths. + if (file_info->FileNameLength < sizeof(kPathPrefix) || + file_info->FileNameLength > UINT16_MAX) + return false; + + if (file_info->FileName[0] != kPathPrefix[0] || + file_info->FileName[1] != kPathPrefix[1] || + file_info->FileName[2] != kPathPrefix[2] || + file_info->FileName[3] != kPathPrefix[3]) + return false; + + return true; +} + +bool NtGetPathFromHandle(HANDLE handle, + std::unique_ptr<wchar_t, NtAllocDeleter>* path) { + OBJECT_NAME_INFORMATION initial_buffer; + OBJECT_NAME_INFORMATION* name; + ULONG size = 0; + // Query the name information a first time to get the size of the name. + NTSTATUS status = g_nt.QueryObject(handle, ObjectNameInformation, + &initial_buffer, size, &size); + + if (!NT_SUCCESS(status) && status != STATUS_INFO_LENGTH_MISMATCH) + return false; + + std::unique_ptr<BYTE[], NtAllocDeleter> name_ptr; + if (!size) + return false; + name_ptr.reset(new (NT_ALLOC) BYTE[size]); + name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(name_ptr.get()); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + status = g_nt.QueryObject(handle, ObjectNameInformation, name, size, &size); + + if (STATUS_SUCCESS != status) + return false; + size_t num_path_wchars = (name->ObjectName.Length / sizeof(wchar_t)) + 1; + path->reset(new (NT_ALLOC) wchar_t[num_path_wchars]); + status = + CopyData(path->get(), name->ObjectName.Buffer, name->ObjectName.Length); + path->get()[num_path_wchars - 1] = L'\0'; + if (STATUS_SUCCESS != status) + return false; + return true; +} + +} // namespace sandbox + +void* operator new(size_t size, sandbox::AllocationType type, void* near_to) { + void* result = nullptr; + if (type == sandbox::NT_ALLOC) { + if (sandbox::InitHeap()) { + // Use default flags for the allocation. + result = sandbox::g_nt.RtlAllocateHeap(sandbox::g_heap, 0, size); + } + } else if (type == sandbox::NT_PAGE) { + result = AllocateNearTo(near_to, size); + } else { + NOTREACHED_NT(); + } + + // TODO: Returning nullptr from operator new has undefined behavior, but + // the Allocate() functions called above can return nullptr. Consider checking + // for nullptr here and crashing or throwing. + + return result; +} + +void* operator new [](size_t size, sandbox::AllocationType type, + void* near_to) { + return operator new(size, type, near_to); +} + +void operator delete(void* memory, sandbox::AllocationType type) { + if (type == sandbox::NT_ALLOC) { + // Use default flags. + VERIFY(sandbox::g_nt.RtlFreeHeap(sandbox::g_heap, 0, memory)); + } else if (type == sandbox::NT_PAGE) { + void* base = memory; + SIZE_T size = 0; + VERIFY_SUCCESS(sandbox::g_nt.FreeVirtualMemory(NtCurrentProcess, &base, + &size, MEM_RELEASE)); + } else { + NOTREACHED_NT(); + } +} + +void operator delete(void* memory, + sandbox::AllocationType type, + void* near_to) { + operator delete(memory, type); +} + +void* __cdecl operator new(size_t size, + void* buffer, + sandbox::AllocationType type) { + return buffer; +} + +void __cdecl operator delete(void* memory, + void* buffer, + sandbox::AllocationType type) {} diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.h b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.h new file mode 100644 index 0000000000..70d011dfb4 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_nt_util.h @@ -0,0 +1,220 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_NT_UTIL_H_ +#define SANDBOX_SRC_SANDBOX_NT_UTIL_H_ + +#include <intrin.h> +#include <stddef.h> +#include <stdint.h> +#include <memory> + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_types.h" + +// Placement new and delete to be used from ntdll interception code. +void* __cdecl operator new(size_t size, + sandbox::AllocationType type, + void* near_to = nullptr); +void* __cdecl operator new[](size_t size, + sandbox::AllocationType type, + void* near_to = nullptr); +void __cdecl operator delete(void* memory, sandbox::AllocationType type); +// Add operator delete that matches the placement form of the operator new +// above. This is required by compiler to generate code to call operator delete +// in case the object's constructor throws an exception. +// See http://msdn.microsoft.com/en-us/library/cxdxz3x6.aspx +void __cdecl operator delete(void* memory, + sandbox::AllocationType type, + void* near_to); + +// Regular placement new and delete +void* __cdecl operator new(size_t size, + void* buffer, + sandbox::AllocationType type); +void __cdecl operator delete(void* memory, + void* buffer, + sandbox::AllocationType type); + +// DCHECK_NT is defined to be pretty much an assert at this time because we +// don't have logging from the ntdll layer on the child. +// +// VERIFY_NT and VERIFY_SUCCESS are the standard asserts on debug, but +// execute the actual argument on release builds. VERIFY_NT expects an action +// returning a bool, while VERIFY_SUCCESS expects an action returning +// NTSTATUS. +#ifndef NDEBUG +#define DCHECK_NT(condition) \ + { (condition) ? (void)0 : __debugbreak(); } +#define VERIFY(action) DCHECK_NT(action) +#define VERIFY_SUCCESS(action) DCHECK_NT(NT_SUCCESS(action)) +#else +#define DCHECK_NT(condition) +#define VERIFY(action) (action) +#define VERIFY_SUCCESS(action) (action) +#endif + +#define CHECK_NT(condition) \ + { (condition) ? (void)0 : __debugbreak(); } + +#define NOTREACHED_NT() DCHECK_NT(false) + +namespace sandbox { + +#if defined(_M_X64) || defined(_M_ARM64) +#pragma intrinsic(_InterlockedCompareExchange) +#pragma intrinsic(_InterlockedCompareExchangePointer) + +#elif defined(_M_IX86) +extern "C" long _InterlockedCompareExchange(long volatile* destination, + long exchange, + long comperand); + +#pragma intrinsic(_InterlockedCompareExchange) + +// We want to make sure that we use an intrinsic version of the function, not +// the one provided by kernel32. +__forceinline void* _InterlockedCompareExchangePointer( + void* volatile* destination, + void* exchange, + void* comperand) { + long ret = _InterlockedCompareExchange( + reinterpret_cast<long volatile*>(destination), + static_cast<long>(reinterpret_cast<size_t>(exchange)), + static_cast<long>(reinterpret_cast<size_t>(comperand))); + + return reinterpret_cast<void*>(static_cast<size_t>(ret)); +} + +#else +#error Architecture not supported. + +#endif + +struct NtAllocDeleter { + inline void operator()(void* ptr) const { + operator delete(ptr, AllocationType::NT_ALLOC); + } +}; + +// Returns a pointer to the IPC shared memory. +void* GetGlobalIPCMemory(); + +// Returns a pointer to the Policy shared memory. +void* GetGlobalPolicyMemory(); + +enum RequiredAccess { READ, WRITE }; + +// Performs basic user mode buffer validation. In any case, buffers access must +// be protected by SEH. intent specifies if the buffer should be tested for read +// or write. +bool ValidParameter(void* buffer, size_t size, RequiredAccess intent); + +// Copies data from a user buffer to our buffer. Returns the operation status. +NTSTATUS CopyData(void* destination, const void* source, size_t bytes); + +// Copies the name from an object attributes. +NTSTATUS AllocAndCopyName(const OBJECT_ATTRIBUTES* in_object, + std::unique_ptr<wchar_t, NtAllocDeleter>* out_name, + uint32_t* attributes, + HANDLE* root); + +// Determine full path name from object root and path. +NTSTATUS AllocAndGetFullPath( + HANDLE root, + const wchar_t* path, + std::unique_ptr<wchar_t, NtAllocDeleter>* full_path); + +// Initializes our ntdll level heap +bool InitHeap(); + +// Returns true if the provided handle refers to the current process. +bool IsSameProcess(HANDLE process); + +enum MappedModuleFlags { + MODULE_IS_PE_IMAGE = 1, // Module is an executable. + MODULE_HAS_ENTRY_POINT = 2, // Execution entry point found. + MODULE_HAS_CODE = 4 // Non zero size of executable sections. +}; + +// Returns the name and characteristics for a given PE module. The return +// value is the name as defined by the export table and the flags is any +// combination of the MappedModuleFlags enumeration. +// +// The returned buffer must be freed with a placement delete from the ntdll +// level allocator: +// +// UNICODE_STRING* name = GetPEImageInfoFromModule(HMODULE module, &flags); +// if (!name) { +// // probably not a valid dll +// return; +// } +// InsertYourLogicHere(name); +// operator delete(name, NT_ALLOC); +UNICODE_STRING* GetImageInfoFromModule(HMODULE module, uint32_t* flags); + +// Returns the name and characteristics for a given PE module. The return +// value is the name as defined by the export table. +// +// The returned buffer is within the PE module and must not be freed. +const char* GetAnsiImageInfoFromModule(HMODULE module); + +// Returns the full path and filename for a given dll. +// May return nullptr if the provided address is not backed by a named section, +// or if the current OS version doesn't support the call. The returned buffer +// must be freed with a placement delete (see GetImageNameFromModule example). +UNICODE_STRING* GetBackingFilePath(PVOID address); + +// Returns the last component of a path that contains the module name. +// It will return nullptr if the path ends with the path separator. The returned +// buffer must be freed with a placement delete (see GetImageNameFromModule +// example). +UNICODE_STRING* ExtractModuleName(const UNICODE_STRING* module_path); + +// Returns true if the parameters correspond to a dll mapped as code. +bool IsValidImageSection(HANDLE section, + PVOID* base, + PLARGE_INTEGER offset, + PSIZE_T view_size); + +// Converts an ansi string to an UNICODE_STRING. +UNICODE_STRING* AnsiToUnicode(const char* string); + +// Resolves a handle to an nt path. Returns true if the handle can be resolved. +bool NtGetPathFromHandle(HANDLE handle, + std::unique_ptr<wchar_t, NtAllocDeleter>* path); + +// Provides a simple way to temporarily change the protection of a memory page. +class AutoProtectMemory { + public: + AutoProtectMemory() + : changed_(false), address_(nullptr), bytes_(0), old_protect_(0) {} + + ~AutoProtectMemory() { RevertProtection(); } + + // Sets the desired protection of a given memory range. + NTSTATUS ChangeProtection(void* address, size_t bytes, ULONG protect); + + // Restores the original page protection. + NTSTATUS RevertProtection(); + + private: + bool changed_; + void* address_; + size_t bytes_; + ULONG old_protect_; + + DISALLOW_COPY_AND_ASSIGN(AutoProtectMemory); +}; + +// Returns true if the file_rename_information structure is supported by our +// rename handler. +bool IsSupportedRenameCall(FILE_RENAME_INFORMATION* file_info, + DWORD length, + uint32_t file_info_class); + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_NT_UTIL_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_policy.h b/security/sandbox/chromium/sandbox/win/src/sandbox_policy.h new file mode 100644 index 0000000000..75514ef595 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_policy.h @@ -0,0 +1,296 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ +#define SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <string> + +#include "base/memory/scoped_refptr.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/security_level.h" + +namespace sandbox { + +class AppContainerProfile; + +class TargetPolicy { + public: + // Windows subsystems that can have specific rules. + // Note: The process subsystem(SUBSYS_PROCESS) does not evaluate the request + // exactly like the CreateProcess API does. See the comment at the top of + // process_thread_dispatcher.cc for more details. + enum SubSystem { + SUBSYS_FILES, // Creation and opening of files and pipes. + SUBSYS_NAMED_PIPES, // Creation of named pipes. + SUBSYS_PROCESS, // Creation of child processes. + SUBSYS_REGISTRY, // Creation and opening of registry keys. + SUBSYS_SYNC, // Creation of named sync objects. + SUBSYS_HANDLES, // Duplication of handles to other processes. + SUBSYS_WIN32K_LOCKDOWN, // Win32K Lockdown related policy. + SUBSYS_SIGNED_BINARY, // Signed binary policy. + SUBSYS_LINE_BREAK // Complex line break policy. + }; + + // Allowable semantics when a rule is matched. + enum Semantics { + FILES_ALLOW_ANY, // Allows open or create for any kind of access that + // the file system supports. + FILES_ALLOW_READONLY, // Allows open or create with read access only. + FILES_ALLOW_QUERY, // Allows access to query the attributes of a file. + FILES_ALLOW_DIR_ANY, // Allows open or create with directory semantics + // only. + HANDLES_DUP_ANY, // Allows duplicating handles opened with any + // access permissions. + HANDLES_DUP_BROKER, // Allows duplicating handles to the broker process. + NAMEDPIPES_ALLOW_ANY, // Allows creation of a named pipe. + PROCESS_MIN_EXEC, // Allows to create a process with minimal rights + // over the resulting process and thread handles. + // No other parameters besides the command line are + // passed to the child process. + PROCESS_ALL_EXEC, // Allows the creation of a process and return full + // access on the returned handles. + // This flag can be used only when the main token of + // the sandboxed application is at least INTERACTIVE. + EVENTS_ALLOW_ANY, // Allows the creation of an event with full access. + EVENTS_ALLOW_READONLY, // Allows opening an even with synchronize access. + REG_ALLOW_READONLY, // Allows readonly access to a registry key. + REG_ALLOW_ANY, // Allows read and write access to a registry key. + FAKE_USER_GDI_INIT, // Fakes user32 and gdi32 initialization. This can + // be used to allow the DLLs to load and initialize + // even if the process cannot access that subsystem. + IMPLEMENT_OPM_APIS, // Implements FAKE_USER_GDI_INIT and also exposes + // IPC calls to handle Output Protection Manager + // APIs. + SIGNED_ALLOW_LOAD, // Allows loading the module when CIG is enabled. + LINE_BREAK_ALLOW // Allow complex line break brokering. + }; + + // Increments the reference count of this object. The reference count must + // be incremented if this interface is given to another component. + virtual void AddRef() = 0; + + // Decrements the reference count of this object. When the reference count + // is zero the object is automatically destroyed. + // Indicates that the caller is done with this interface. After calling + // release no other method should be called. + virtual void Release() = 0; + + // Sets the security level for the target process' two tokens. + // This setting is permanent and cannot be changed once the target process is + // spawned. + // initial: the security level for the initial token. This is the token that + // is used by the process from the creation of the process until the moment + // the process calls TargetServices::LowerToken() or the process calls + // win32's RevertToSelf(). Once this happens the initial token is no longer + // available and the lockdown token is in effect. Using an initial token is + // not compatible with AppContainer, see SetAppContainer. + // lockdown: the security level for the token that comes into force after the + // process calls TargetServices::LowerToken() or the process calls + // RevertToSelf(). See the explanation of each level in the TokenLevel + // definition. + // Return value: SBOX_ALL_OK if the setting succeeds and false otherwise. + // Returns false if the lockdown value is more permissive than the initial + // value. + // + // Important: most of the sandbox-provided security relies on this single + // setting. The caller should strive to set the lockdown level as restricted + // as possible. + virtual ResultCode SetTokenLevel(TokenLevel initial, TokenLevel lockdown) = 0; + + // Returns the initial token level. + virtual TokenLevel GetInitialTokenLevel() const = 0; + + // Returns the lockdown token level. + virtual TokenLevel GetLockdownTokenLevel() const = 0; + + // Sets that we should not use restricting SIDs in the access tokens. We need + // to do this in some circumstances even though it weakens the sandbox. + // The default is to use them. + virtual void SetDoNotUseRestrictingSIDs() = 0; + + // Sets the security level of the Job Object to which the target process will + // belong. This setting is permanent and cannot be changed once the target + // process is spawned. The job controls the global security settings which + // can not be specified in the token security profile. + // job_level: the security level for the job. See the explanation of each + // level in the JobLevel definition. + // ui_exceptions: specify what specific rights that are disabled in the + // chosen job_level that need to be granted. Use this parameter to avoid + // selecting the next permissive job level unless you need all the rights + // that are granted in such level. + // The exceptions can be specified as a combination of the following + // constants: + // JOB_OBJECT_UILIMIT_HANDLES : grant access to all user-mode handles. These + // include windows, icons, menus and various GDI objects. In addition the + // target process can set hooks, and broadcast messages to other processes + // that belong to the same desktop. + // JOB_OBJECT_UILIMIT_READCLIPBOARD : grant read-only access to the clipboard. + // JOB_OBJECT_UILIMIT_WRITECLIPBOARD : grant write access to the clipboard. + // JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS : allow changes to the system-wide + // parameters as defined by the Win32 call SystemParametersInfo(). + // JOB_OBJECT_UILIMIT_DISPLAYSETTINGS : allow programmatic changes to the + // display settings. + // JOB_OBJECT_UILIMIT_GLOBALATOMS : allow access to the global atoms table. + // JOB_OBJECT_UILIMIT_DESKTOP : allow the creation of new desktops. + // JOB_OBJECT_UILIMIT_EXITWINDOWS : allow the call to ExitWindows(). + // + // Return value: SBOX_ALL_OK if the setting succeeds and false otherwise. + // + // Note: JOB_OBJECT_XXXX constants are defined in winnt.h and documented at + // length in: + // http://msdn2.microsoft.com/en-us/library/ms684152.aspx + // + // Note: the recommended level is JOB_RESTRICTED or JOB_LOCKDOWN. + virtual ResultCode SetJobLevel(JobLevel job_level, + uint32_t ui_exceptions) = 0; + + // Returns the job level. + virtual JobLevel GetJobLevel() const = 0; + + // Sets a hard limit on the size of the commit set for the sandboxed process. + // If the limit is reached, the process will be terminated with + // SBOX_FATAL_MEMORY_EXCEEDED (7012). + virtual ResultCode SetJobMemoryLimit(size_t memory_limit) = 0; + + // Specifies the desktop on which the application is going to run. If the + // desktop does not exist, it will be created. If alternate_winstation is + // set to true, the desktop will be created on an alternate window station. + virtual ResultCode SetAlternateDesktop(bool alternate_winstation) = 0; + + // Returns the name of the alternate desktop used. If an alternate window + // station is specified, the name is prepended by the window station name, + // followed by a backslash. + virtual std::wstring GetAlternateDesktop() const = 0; + + // Precreates the desktop and window station, if any. + virtual ResultCode CreateAlternateDesktop(bool alternate_winstation) = 0; + + // Destroys the desktop and windows station. + virtual void DestroyAlternateDesktop() = 0; + + // Sets the integrity level of the process in the sandbox. Both the initial + // token and the main token will be affected by this. If the integrity level + // is set to a level higher than the current level, the sandbox will fail + // to start. + virtual ResultCode SetIntegrityLevel(IntegrityLevel level) = 0; + + // Returns the initial integrity level used. + virtual IntegrityLevel GetIntegrityLevel() const = 0; + + // Sets the integrity level of the process in the sandbox. The integrity level + // will not take effect before you call LowerToken. User Interface Privilege + // Isolation is not affected by this setting and will remain off for the + // process in the sandbox. If the integrity level is set to a level higher + // than the current level, the sandbox will fail to start. + virtual ResultCode SetDelayedIntegrityLevel(IntegrityLevel level) = 0; + + // Sets the LowBox token for sandboxed process. This is mutually exclusive + // with SetAppContainer method. + virtual ResultCode SetLowBox(const wchar_t* sid) = 0; + + // Sets the mitigations enabled when the process is created. Most of these + // are implemented as attributes passed via STARTUPINFOEX. So they take + // effect before any thread in the target executes. The declaration of + // MitigationFlags is followed by a detailed description of each flag. + virtual ResultCode SetProcessMitigations(MitigationFlags flags) = 0; + + // Returns the currently set mitigation flags. + virtual MitigationFlags GetProcessMitigations() = 0; + + // Sets process mitigation flags that don't take effect before the call to + // LowerToken(). + virtual ResultCode SetDelayedProcessMitigations(MitigationFlags flags) = 0; + + // Returns the currently set delayed mitigation flags. + virtual MitigationFlags GetDelayedProcessMitigations() const = 0; + + // Disconnect the target from CSRSS when TargetServices::LowerToken() is + // called inside the target. + virtual ResultCode SetDisconnectCsrss() = 0; + + // Sets the interceptions to operate in strict mode. By default, interceptions + // are performed in "relaxed" mode, where if something inside NTDLL.DLL is + // already patched we attempt to intercept it anyway. Setting interceptions + // to strict mode means that when we detect that the function is patched we'll + // refuse to perform the interception. + virtual void SetStrictInterceptions() = 0; + + // Set the handles the target process should inherit for stdout and + // stderr. The handles the caller passes must remain valid for the + // lifetime of the policy object. This only has an effect on + // Windows Vista and later versions. These methods accept pipe and + // file handles, but not console handles. + virtual ResultCode SetStdoutHandle(HANDLE handle) = 0; + virtual ResultCode SetStderrHandle(HANDLE handle) = 0; + + // Adds a policy rule effective for processes spawned using this policy. + // subsystem: One of the above enumerated windows subsystems. + // semantics: One of the above enumerated FileSemantics. + // pattern: A specific full path or a full path with wildcard patterns. + // The valid wildcards are: + // '*' : Matches zero or more character. Only one in series allowed. + // '?' : Matches a single character. One or more in series are allowed. + // Examples: + // "c:\\documents and settings\\vince\\*.dmp" + // "c:\\documents and settings\\*\\crashdumps\\*.dmp" + // "c:\\temp\\app_log_?????_chrome.txt" + virtual ResultCode AddRule(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) = 0; + + // Adds a dll that will be unloaded in the target process before it gets + // a chance to initialize itself. Typically, dlls that cause the target + // to crash go here. + virtual ResultCode AddDllToUnload(const wchar_t* dll_name) = 0; + + // Adds a handle that will be closed in the target process after lockdown. + // A nullptr value for handle_name indicates all handles of the specified + // type. An empty string for handle_name indicates the handle is unnamed. + virtual ResultCode AddKernelObjectToClose(const wchar_t* handle_type, + const wchar_t* handle_name) = 0; + + // Adds a handle that will be shared with the target process. Does not take + // ownership of the handle. + virtual void AddHandleToShare(HANDLE handle) = 0; + + // Locks down the default DACL of the created lockdown and initial tokens + // to restrict what other processes are allowed to access a process' kernel + // resources. + virtual void SetLockdownDefaultDacl() = 0; + + // Adds a restricting random SID to the restricted SIDs list as well as + // the default DACL. + virtual void AddRestrictingRandomSid() = 0; + + // Enable OPM API redirection when in Win32k lockdown. + virtual void SetEnableOPMRedirection() = 0; + // Enable OPM API emulation when in Win32k lockdown. + virtual bool GetEnableOPMRedirection() = 0; + + // Configure policy to use an AppContainer profile. |package_name| is the + // name of the profile to use. Specifying True for |create_profile| ensures + // the profile exists, if set to False process creation will fail if the + // profile has not already been created. + virtual ResultCode AddAppContainerProfile(const wchar_t* package_name, + bool create_profile) = 0; + + // Get the configured AppContainerProfile. + virtual scoped_refptr<AppContainerProfile> GetAppContainerProfile() = 0; + + // Set effective token that will be used for creating the initial and + // lockdown tokens. The token the caller passes must remain valid for the + // lifetime of the policy object. + virtual void SetEffectiveToken(HANDLE token) = 0; + + protected: + ~TargetPolicy() {} +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.cc new file mode 100644 index 0000000000..f228dbbc31 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.cc @@ -0,0 +1,832 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox_policy_base.h" + +#include <sddl.h> +#include <stddef.h> +#include <stdint.h> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/filesystem_policy.h" +#include "sandbox/win/src/handle_policy.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/job.h" +#include "sandbox/win/src/line_break_policy.h" +#include "sandbox/win/src/named_pipe_policy.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_engine_processor.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/process_mitigations_win32k_policy.h" +#include "sandbox/win/src/process_thread_policy.h" +#include "sandbox/win/src/registry_policy.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/signed_policy.h" +#include "sandbox/win/src/sync_policy.h" +#include "sandbox/win/src/target_process.h" +#include "sandbox/win/src/top_level_dispatcher.h" +#include "sandbox/win/src/window.h" + +namespace { + +// The standard windows size for one memory page. +constexpr size_t kOneMemPage = 4096; +// The IPC and Policy shared memory sizes. +constexpr size_t kIPCMemSize = kOneMemPage * 2; +constexpr size_t kPolMemSize = kOneMemPage * 14; + +// Helper function to allocate space (on the heap) for policy. +sandbox::PolicyGlobal* MakeBrokerPolicyMemory() { + const size_t kTotalPolicySz = kPolMemSize; + sandbox::PolicyGlobal* policy = + static_cast<sandbox::PolicyGlobal*>(::operator new(kTotalPolicySz)); + DCHECK(policy); + memset(policy, 0, kTotalPolicySz); + policy->data_size = kTotalPolicySz - sizeof(sandbox::PolicyGlobal); + return policy; +} + +bool IsInheritableHandle(HANDLE handle) { + if (!handle) + return false; + if (handle == INVALID_HANDLE_VALUE) + return false; + // File handles (FILE_TYPE_DISK) and pipe handles are known to be + // inheritable. Console handles (FILE_TYPE_CHAR) are not + // inheritable via PROC_THREAD_ATTRIBUTE_HANDLE_LIST. + DWORD handle_type = GetFileType(handle); + return handle_type == FILE_TYPE_DISK || handle_type == FILE_TYPE_PIPE; +} + +} // namespace + +namespace sandbox { + +SANDBOX_INTERCEPT IntegrityLevel g_shared_delayed_integrity_level; +SANDBOX_INTERCEPT MitigationFlags g_shared_delayed_mitigations; + +// Initializes static members. alternate_desktop_handle_ is a desktop on +// alternate_winstation_handle_, alternate_desktop_local_winstation_handle_ is a +// desktop on the same winstation as the parent process. +HWINSTA PolicyBase::alternate_winstation_handle_ = nullptr; +HDESK PolicyBase::alternate_desktop_handle_ = nullptr; +HDESK PolicyBase::alternate_desktop_local_winstation_handle_ = nullptr; +IntegrityLevel PolicyBase::alternate_desktop_integrity_level_label_ = + INTEGRITY_LEVEL_SYSTEM; +IntegrityLevel + PolicyBase::alternate_desktop_local_winstation_integrity_level_label_ = + INTEGRITY_LEVEL_SYSTEM; + +PolicyBase::PolicyBase() + : ref_count(1), + lockdown_level_(USER_LOCKDOWN), + initial_level_(USER_LOCKDOWN), + job_level_(JOB_LOCKDOWN), + ui_exceptions_(0), + memory_limit_(0), + use_alternate_desktop_(false), + use_alternate_winstation_(false), + file_system_init_(false), + relaxed_interceptions_(true), + stdout_handle_(INVALID_HANDLE_VALUE), + stderr_handle_(INVALID_HANDLE_VALUE), + integrity_level_(INTEGRITY_LEVEL_LAST), + delayed_integrity_level_(INTEGRITY_LEVEL_LAST), + mitigations_(0), + delayed_mitigations_(0), + is_csrss_connected_(true), + policy_maker_(nullptr), + policy_(nullptr), + lowbox_sid_(nullptr), + lockdown_default_dacl_(false), + add_restricting_random_sid_(false), + enable_opm_redirection_(false), + effective_token_(nullptr) { + ::InitializeCriticalSection(&lock_); + dispatcher_.reset(new TopLevelDispatcher(this)); +} + +PolicyBase::~PolicyBase() { + TargetSet::iterator it; + for (it = targets_.begin(); it != targets_.end(); ++it) { + TargetProcess* target = (*it); + delete target; + } + delete policy_maker_; + delete policy_; + + if (lowbox_sid_) + ::LocalFree(lowbox_sid_); + + ::DeleteCriticalSection(&lock_); +} + +void PolicyBase::AddRef() { + ::InterlockedIncrement(&ref_count); +} + +void PolicyBase::Release() { + if (0 == ::InterlockedDecrement(&ref_count)) + delete this; +} + +ResultCode PolicyBase::SetTokenLevel(TokenLevel initial, TokenLevel lockdown) { + if (initial < lockdown) { + return SBOX_ERROR_BAD_PARAMS; + } + initial_level_ = initial; + lockdown_level_ = lockdown; + return SBOX_ALL_OK; +} + +TokenLevel PolicyBase::GetInitialTokenLevel() const { + return initial_level_; +} + +TokenLevel PolicyBase::GetLockdownTokenLevel() const { + return lockdown_level_; +} + +void PolicyBase::SetDoNotUseRestrictingSIDs() { + use_restricting_sids_ = false; +} + +ResultCode PolicyBase::SetJobLevel(JobLevel job_level, uint32_t ui_exceptions) { + if (memory_limit_ && job_level == JOB_NONE) { + return SBOX_ERROR_BAD_PARAMS; + } + job_level_ = job_level; + ui_exceptions_ = ui_exceptions; + return SBOX_ALL_OK; +} + +JobLevel PolicyBase::GetJobLevel() const { + return job_level_; +} + +ResultCode PolicyBase::SetJobMemoryLimit(size_t memory_limit) { + memory_limit_ = memory_limit; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetAlternateDesktop(bool alternate_winstation) { + use_alternate_desktop_ = true; + use_alternate_winstation_ = alternate_winstation; + return CreateAlternateDesktop(alternate_winstation); +} + +std::wstring PolicyBase::GetAlternateDesktop() const { + // No alternate desktop or winstation. Return an empty string. + if (!use_alternate_desktop_ && !use_alternate_winstation_) { + return std::wstring(); + } + + if (use_alternate_winstation_) { + // The desktop and winstation should have been created by now. + // If we hit this scenario, it means that the user ignored the failure + // during SetAlternateDesktop, so we ignore it here too. + if (!alternate_desktop_handle_ || !alternate_winstation_handle_) + return std::wstring(); + + return GetFullDesktopName(alternate_winstation_handle_, + alternate_desktop_handle_); + } + + if (!alternate_desktop_local_winstation_handle_) + return std::wstring(); + + return GetFullDesktopName(nullptr, + alternate_desktop_local_winstation_handle_); +} + +ResultCode PolicyBase::CreateAlternateDesktop(bool alternate_winstation) { + if (alternate_winstation) { + // Check if it's already created. + if (alternate_winstation_handle_ && alternate_desktop_handle_) + return SBOX_ALL_OK; + + DCHECK(!alternate_winstation_handle_); + // Create the window station. + ResultCode result = CreateAltWindowStation(&alternate_winstation_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_winstation_handle_ || + base::win::GetWindowObjectName(alternate_winstation_handle_).empty()) + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + + // Create the destkop. + result = CreateAltDesktop(alternate_winstation_handle_, + &alternate_desktop_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_desktop_handle_ || + base::win::GetWindowObjectName(alternate_desktop_handle_).empty()) { + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + } else { + // Check if it already exists. + if (alternate_desktop_local_winstation_handle_) + return SBOX_ALL_OK; + + // Create the destkop. + ResultCode result = + CreateAltDesktop(nullptr, &alternate_desktop_local_winstation_handle_); + if (SBOX_ALL_OK != result) + return result; + + // Verify that everything is fine. + if (!alternate_desktop_local_winstation_handle_ || + base::win::GetWindowObjectName( + alternate_desktop_local_winstation_handle_) + .empty()) { + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + } + + return SBOX_ALL_OK; +} + +void PolicyBase::DestroyAlternateDesktop() { + if (use_alternate_winstation_) { + if (alternate_desktop_handle_) { + ::CloseDesktop(alternate_desktop_handle_); + alternate_desktop_handle_ = nullptr; + } + + if (alternate_winstation_handle_) { + ::CloseWindowStation(alternate_winstation_handle_); + alternate_winstation_handle_ = nullptr; + } + } else { + if (alternate_desktop_local_winstation_handle_) { + ::CloseDesktop(alternate_desktop_local_winstation_handle_); + alternate_desktop_local_winstation_handle_ = nullptr; + } + } +} + +ResultCode PolicyBase::SetIntegrityLevel(IntegrityLevel integrity_level) { + if (app_container_profile_) + return SBOX_ERROR_BAD_PARAMS; + integrity_level_ = integrity_level; + return SBOX_ALL_OK; +} + +IntegrityLevel PolicyBase::GetIntegrityLevel() const { + return integrity_level_; +} + +ResultCode PolicyBase::SetDelayedIntegrityLevel( + IntegrityLevel integrity_level) { + delayed_integrity_level_ = integrity_level; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetLowBox(const wchar_t* sid) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return SBOX_ERROR_UNSUPPORTED; + + DCHECK(sid); + if (lowbox_sid_ || app_container_profile_) + return SBOX_ERROR_BAD_PARAMS; + + if (!ConvertStringSidToSid(sid, &lowbox_sid_)) + return SBOX_ERROR_INVALID_LOWBOX_SID; + + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetProcessMitigations(MitigationFlags flags) { + // Prior to Win10 RS5 CreateProcess fails when AppContainer and mitigation + // flags are enabled. Return an error on downlevel platforms if trying to + // set new mitigations. + if (app_container_profile_ && + base::win::GetVersion() < base::win::Version::WIN10_RS5) { + return SBOX_ERROR_BAD_PARAMS; + } + if (!CanSetProcessMitigationsPreStartup(flags)) + return SBOX_ERROR_BAD_PARAMS; + mitigations_ = flags; + return SBOX_ALL_OK; +} + +MitigationFlags PolicyBase::GetProcessMitigations() { + return mitigations_; +} + +ResultCode PolicyBase::SetDelayedProcessMitigations(MitigationFlags flags) { + if (!CanSetProcessMitigationsPostStartup(flags)) + return SBOX_ERROR_BAD_PARAMS; + delayed_mitigations_ = flags; + return SBOX_ALL_OK; +} + +MitigationFlags PolicyBase::GetDelayedProcessMitigations() const { + return delayed_mitigations_; +} + +void PolicyBase::SetStrictInterceptions() { + relaxed_interceptions_ = false; +} + +ResultCode PolicyBase::SetStdoutHandle(HANDLE handle) { + if (!IsInheritableHandle(handle)) + return SBOX_ERROR_BAD_PARAMS; + stdout_handle_ = handle; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::SetStderrHandle(HANDLE handle) { + if (!IsInheritableHandle(handle)) + return SBOX_ERROR_BAD_PARAMS; + stderr_handle_ = handle; + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::AddRule(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) { + ResultCode result = AddRuleInternal(subsystem, semantics, pattern); + LOG_IF(ERROR, result != SBOX_ALL_OK) + << "Failed to add sandbox rule." + << " error = " << result << ", subsystem = " << subsystem + << ", semantics = " << semantics << ", pattern = '" << pattern << "'"; + return result; +} + +ResultCode PolicyBase::AddDllToUnload(const wchar_t* dll_name) { + blocklisted_dlls_.push_back(dll_name); + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::AddKernelObjectToClose(const wchar_t* handle_type, + const wchar_t* handle_name) { + return handle_closer_.AddHandle(handle_type, handle_name); +} + +void PolicyBase::AddHandleToShare(HANDLE handle) { + CHECK(handle); + CHECK_NE(handle, INVALID_HANDLE_VALUE); + + // Ensure the handle can be inherited. + bool result = + SetHandleInformation(handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + PCHECK(result); + + handles_to_share_.push_back(handle); +} + +void PolicyBase::SetLockdownDefaultDacl() { + lockdown_default_dacl_ = true; +} + +void PolicyBase::AddRestrictingRandomSid() { + add_restricting_random_sid_ = true; +} + +const base::HandlesToInheritVector& PolicyBase::GetHandlesBeingShared() { + return handles_to_share_; +} + +ResultCode PolicyBase::MakeJobObject(base::win::ScopedHandle* job) { + if (job_level_ == JOB_NONE) { + job->Close(); + return SBOX_ALL_OK; + } + + // Create the windows job object. + Job job_obj; + DWORD result = + job_obj.Init(job_level_, nullptr, ui_exceptions_, memory_limit_); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_CANNOT_INIT_JOB; + + *job = job_obj.Take(); + return SBOX_ALL_OK; +} + +ResultCode PolicyBase::MakeTokens(base::win::ScopedHandle* initial, + base::win::ScopedHandle* lockdown, + base::win::ScopedHandle* lowbox) { + Sid random_sid = Sid::GenerateRandomSid(); + PSID random_sid_ptr = nullptr; + if (add_restricting_random_sid_) + random_sid_ptr = random_sid.GetPSID(); + + // Create the 'naked' token. This will be the permanent token associated + // with the process and therefore with any thread that is not impersonating. + DWORD result = CreateRestrictedToken( + effective_token_, lockdown_level_, integrity_level_, PRIMARY, + lockdown_default_dacl_, random_sid_ptr, use_restricting_sids_, lockdown); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_CANNOT_CREATE_RESTRICTED_TOKEN; + + // If we're launching on the alternate desktop we need to make sure the + // integrity label on the object is no higher than the sandboxed process's + // integrity level. So, we lower the label on the desktop process if it's + // not already low enough for our process. + if (use_alternate_desktop_ && integrity_level_ != INTEGRITY_LEVEL_LAST) { + // Integrity label enum is reversed (higher level is a lower value). + static_assert(INTEGRITY_LEVEL_SYSTEM < INTEGRITY_LEVEL_UNTRUSTED, + "Integrity level ordering reversed."); + HDESK desktop_handle = nullptr; + IntegrityLevel desktop_integrity_level_label; + if (use_alternate_winstation_) { + desktop_handle = alternate_desktop_handle_; + desktop_integrity_level_label = alternate_desktop_integrity_level_label_; + } else { + desktop_handle = alternate_desktop_local_winstation_handle_; + desktop_integrity_level_label = + alternate_desktop_local_winstation_integrity_level_label_; + } + // If the desktop_handle hasn't been created for any reason, skip this. + if (desktop_handle && desktop_integrity_level_label < integrity_level_) { + result = + SetObjectIntegrityLabel(desktop_handle, SE_WINDOW_OBJECT, L"", + GetIntegrityLevelString(integrity_level_)); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_CANNOT_SET_DESKTOP_INTEGRITY; + + if (use_alternate_winstation_) { + alternate_desktop_integrity_level_label_ = integrity_level_; + } else { + alternate_desktop_local_winstation_integrity_level_label_ = + integrity_level_; + } + } + } + + if (lowbox_sid_) { + if (!lowbox_directory_.IsValid()) { + result = + CreateLowBoxObjectDirectory(lowbox_sid_, true, &lowbox_directory_); + DCHECK(result == ERROR_SUCCESS); + } + + // The order of handles isn't important in the CreateLowBoxToken call. + // The kernel will maintain a reference to the object directory handle. + HANDLE saved_handles[1] = {lowbox_directory_.Get()}; + DWORD saved_handles_count = lowbox_directory_.IsValid() ? 1 : 0; + + Sid package_sid(lowbox_sid_); + SecurityCapabilities caps(package_sid); + if (CreateLowBoxToken(lockdown->Get(), PRIMARY, &caps, saved_handles, + saved_handles_count, lowbox) != ERROR_SUCCESS) { + return SBOX_ERROR_CANNOT_CREATE_LOWBOX_TOKEN; + } + + if (!ReplacePackageSidInDacl(lowbox->Get(), SE_KERNEL_OBJECT, package_sid, + TOKEN_ALL_ACCESS)) { + return SBOX_ERROR_CANNOT_MODIFY_LOWBOX_TOKEN_DACL; + } + } + + // Create the 'better' token. We use this token as the one that the main + // thread uses when booting up the process. It should contain most of + // what we need (before reaching main( )) + result = CreateRestrictedToken( + effective_token_, initial_level_, integrity_level_, IMPERSONATION, + lockdown_default_dacl_, random_sid_ptr, use_restricting_sids_, initial); + if (ERROR_SUCCESS != result) + return SBOX_ERROR_CANNOT_CREATE_RESTRICTED_IMP_TOKEN; + + return SBOX_ALL_OK; +} + +PSID PolicyBase::GetLowBoxSid() const { + return lowbox_sid_; +} + +ResultCode PolicyBase::AddTarget(TargetProcess* target) { + if (policy_) + policy_maker_->Done(); + + if (!ApplyProcessMitigationsToSuspendedProcess(target->Process(), + mitigations_)) { + return SBOX_ERROR_APPLY_ASLR_MITIGATIONS; + } + + ResultCode ret = SetupAllInterceptions(target); + + if (ret != SBOX_ALL_OK) + return ret; + + if (!SetupHandleCloser(target)) + return SBOX_ERROR_SETUP_HANDLE_CLOSER; + + DWORD win_error = ERROR_SUCCESS; + // Initialize the sandbox infrastructure for the target. + // TODO(wfh) do something with win_error code here. + ret = target->Init(dispatcher_.get(), policy_, kIPCMemSize, kPolMemSize, + &win_error); + + if (ret != SBOX_ALL_OK) + return ret; + + g_shared_delayed_integrity_level = delayed_integrity_level_; + ret = target->TransferVariable("g_shared_delayed_integrity_level", + &g_shared_delayed_integrity_level, + sizeof(g_shared_delayed_integrity_level)); + g_shared_delayed_integrity_level = INTEGRITY_LEVEL_LAST; + if (SBOX_ALL_OK != ret) + return ret; + + // Add in delayed mitigations and pseudo-mitigations enforced at startup. + g_shared_delayed_mitigations = + delayed_mitigations_ | FilterPostStartupProcessMitigations(mitigations_); + if (!CanSetProcessMitigationsPostStartup(g_shared_delayed_mitigations)) + return SBOX_ERROR_BAD_PARAMS; + + ret = target->TransferVariable("g_shared_delayed_mitigations", + &g_shared_delayed_mitigations, + sizeof(g_shared_delayed_mitigations)); + g_shared_delayed_mitigations = 0; + if (SBOX_ALL_OK != ret) + return ret; + + AutoLock lock(&lock_); + targets_.push_back(target); + return SBOX_ALL_OK; +} + +bool PolicyBase::OnJobEmpty(HANDLE job) { + AutoLock lock(&lock_); + TargetSet::iterator it; + for (it = targets_.begin(); it != targets_.end(); ++it) { + if ((*it)->Job() == job) + break; + } + if (it == targets_.end()) { + return false; + } + TargetProcess* target = *it; + targets_.erase(it); + delete target; + return true; +} + +ResultCode PolicyBase::SetDisconnectCsrss() { +// Does not work on 32-bit, and the ASAN runtime falls over with the +// CreateThread EAT patch used when this is enabled. +// See https://crbug.com/783296#c27. +#if defined(_WIN64) && !defined(ADDRESS_SANITIZER) + if (base::win::GetVersion() >= base::win::Version::WIN10) { + is_csrss_connected_ = false; + return AddKernelObjectToClose(L"ALPC Port", nullptr); + } +#endif // !defined(_WIN64) + return SBOX_ALL_OK; +} + +EvalResult PolicyBase::EvalPolicy(IpcTag service, + CountedParameterSetBase* params) { + if (policy_) { + if (!policy_->entry[static_cast<size_t>(service)]) { + // There is no policy for this particular service. This is not a big + // deal. + return DENY_ACCESS; + } + for (size_t i = 0; i < params->count; i++) { + if (!params->parameters[i].IsValid()) { + NOTREACHED(); + return SIGNAL_ALARM; + } + } + PolicyProcessor pol_evaluator(policy_->entry[static_cast<size_t>(service)]); + PolicyResult result = + pol_evaluator.Evaluate(kShortEval, params->parameters, params->count); + if (POLICY_MATCH == result) + return pol_evaluator.GetAction(); + + DCHECK(POLICY_ERROR != result); + } + + return DENY_ACCESS; +} + +HANDLE PolicyBase::GetStdoutHandle() { + return stdout_handle_; +} + +HANDLE PolicyBase::GetStderrHandle() { + return stderr_handle_; +} + +void PolicyBase::SetEnableOPMRedirection() { + enable_opm_redirection_ = true; +} + +bool PolicyBase::GetEnableOPMRedirection() { + return enable_opm_redirection_; +} + +ResultCode PolicyBase::AddAppContainerProfile(const wchar_t* package_name, + bool create_profile) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return SBOX_ERROR_UNSUPPORTED; + + DCHECK(package_name); + if (lowbox_sid_ || app_container_profile_ || + integrity_level_ != INTEGRITY_LEVEL_LAST) { + return SBOX_ERROR_BAD_PARAMS; + } + + if (create_profile) { + app_container_profile_ = AppContainerProfileBase::Create( + package_name, L"Chrome Sandbox", L"Profile for Chrome Sandbox"); + } else { + app_container_profile_ = AppContainerProfileBase::Open(package_name); + } + if (!app_container_profile_) + return SBOX_ERROR_CREATE_APPCONTAINER_PROFILE; + + // A bug exists in CreateProcess where enabling an AppContainer profile and + // passing a set of mitigation flags will generate ERROR_INVALID_PARAMETER. + // Apply best efforts here and convert set mitigations to delayed mitigations. + // This bug looks to have been fixed in Win10 RS5, so exit early if possible. + if (base::win::GetVersion() >= base::win::Version::WIN10_RS5) + return SBOX_ALL_OK; + + delayed_mitigations_ = + mitigations_ & GetAllowedPostStartupProcessMitigations(); + DCHECK(delayed_mitigations_ == + (mitigations_ & ~(MITIGATION_SEHOP | + MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION))); + mitigations_ = 0; + return SBOX_ALL_OK; +} + +scoped_refptr<AppContainerProfile> PolicyBase::GetAppContainerProfile() { + return GetAppContainerProfileBase(); +} + +void PolicyBase::SetEffectiveToken(HANDLE token) { + CHECK(token); + effective_token_ = token; +} + +scoped_refptr<AppContainerProfileBase> +PolicyBase::GetAppContainerProfileBase() { + return app_container_profile_; +} + +ResultCode PolicyBase::SetupAllInterceptions(TargetProcess* target) { + InterceptionManager manager(target, relaxed_interceptions_); + + if (policy_) { + for (size_t i = 0; i < kMaxIpcTag; i++) { + if (policy_->entry[i] && + !dispatcher_->SetupService(&manager, static_cast<IpcTag>(i))) + return SBOX_ERROR_SETUP_INTERCEPTION_SERVICE; + } + } + + for (const std::wstring& dll : blocklisted_dlls_) + manager.AddToUnloadModules(dll.c_str()); + + if (!SetupBasicInterceptions(&manager, is_csrss_connected_)) + return SBOX_ERROR_SETUP_BASIC_INTERCEPTIONS; + + ResultCode rc = manager.InitializeInterceptions(); + if (rc != SBOX_ALL_OK) + return rc; + + // Finally, setup imports on the target so the interceptions can work. + if (!SetupNtdllImports(target)) + return SBOX_ERROR_SETUP_NTDLL_IMPORTS; + + return SBOX_ALL_OK; +} + +bool PolicyBase::SetupHandleCloser(TargetProcess* target) { + return handle_closer_.InitializeTargetHandles(target); +} + +ResultCode PolicyBase::AddRuleInternal(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) { + if (!policy_) { + policy_ = MakeBrokerPolicyMemory(); + DCHECK(policy_); + policy_maker_ = new LowLevelPolicy(policy_); + DCHECK(policy_maker_); + } + + switch (subsystem) { + case SUBSYS_FILES: { + if (!file_system_init_) { + if (!FileSystemPolicy::SetInitialRules(policy_maker_)) + return SBOX_ERROR_BAD_PARAMS; + file_system_init_ = true; + } + if (!FileSystemPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_SYNC: { + if (!SyncPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_PROCESS: { + if (lockdown_level_ < USER_INTERACTIVE && + TargetPolicy::PROCESS_ALL_EXEC == semantics) { + // This is unsupported. This is a huge security risk to give full access + // to a process handle. + return SBOX_ERROR_UNSUPPORTED; + } + if (!ProcessPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_NAMED_PIPES: { + if (!NamedPipePolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_REGISTRY: { + if (!RegistryPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + case SUBSYS_HANDLES: { + if (!HandlePolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + + case SUBSYS_WIN32K_LOCKDOWN: { + // Win32k intercept rules only supported on Windows 8 and above. This must + // match the version checks in process_mitigations.cc for consistency. + if (base::win::GetVersion() >= base::win::Version::WIN8) { + DCHECK_EQ(MITIGATION_WIN32K_DISABLE, + mitigations_ & MITIGATION_WIN32K_DISABLE) + << "Enable MITIGATION_WIN32K_DISABLE before adding win32k policy " + "rules."; + if (!ProcessMitigationsWin32KLockdownPolicy::GenerateRules( + pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + } + break; + } + case SUBSYS_SIGNED_BINARY: { + // Signed intercept rules only supported on Windows 10 TH2 and above. This + // must match the version checks in process_mitigations.cc for + // consistency. + if (base::win::GetVersion() >= base::win::Version::WIN10_TH2) { + DCHECK_EQ(MITIGATION_FORCE_MS_SIGNED_BINS, + (mitigations_ & MITIGATION_FORCE_MS_SIGNED_BINS) | (delayed_mitigations_ & MITIGATION_FORCE_MS_SIGNED_BINS)) + << "Enable MITIGATION_FORCE_MS_SIGNED_BINS before adding signed " + "policy rules."; + if (!SignedPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + } + break; + } + case SUBSYS_LINE_BREAK: { + if (!LineBreakPolicy::GenerateRules(pattern, semantics, policy_maker_)) { + NOTREACHED(); + return SBOX_ERROR_BAD_PARAMS; + } + break; + } + + default: { return SBOX_ERROR_UNSUPPORTED; } + } + + return SBOX_ALL_OK; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.h b/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.h new file mode 100644 index 0000000000..18268e230d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_policy_base.h @@ -0,0 +1,198 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_POLICY_BASE_H_ +#define SANDBOX_WIN_SRC_SANDBOX_POLICY_BASE_H_ + +#include <windows.h> + +#include <stddef.h> +#include <stdint.h> + +#include <list> +#include <memory> +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "base/process/launch.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/app_container_profile_base.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/handle_closer.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_engine_params.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +class LowLevelPolicy; +class PolicyDiagnostic; +class TargetProcess; +struct PolicyGlobal; + +class PolicyBase final : public TargetPolicy { + public: + PolicyBase(); + + // TargetPolicy: + void AddRef() override; + void Release() override; + ResultCode SetTokenLevel(TokenLevel initial, TokenLevel lockdown) override; + TokenLevel GetInitialTokenLevel() const override; + TokenLevel GetLockdownTokenLevel() const override; + void SetDoNotUseRestrictingSIDs() final; + ResultCode SetJobLevel(JobLevel job_level, uint32_t ui_exceptions) override; + JobLevel GetJobLevel() const override; + ResultCode SetJobMemoryLimit(size_t memory_limit) override; + ResultCode SetAlternateDesktop(bool alternate_winstation) override; + std::wstring GetAlternateDesktop() const override; + ResultCode CreateAlternateDesktop(bool alternate_winstation) override; + void DestroyAlternateDesktop() override; + ResultCode SetIntegrityLevel(IntegrityLevel integrity_level) override; + IntegrityLevel GetIntegrityLevel() const override; + ResultCode SetDelayedIntegrityLevel(IntegrityLevel integrity_level) override; + ResultCode SetLowBox(const wchar_t* sid) override; + ResultCode SetProcessMitigations(MitigationFlags flags) override; + MitigationFlags GetProcessMitigations() override; + ResultCode SetDelayedProcessMitigations(MitigationFlags flags) override; + MitigationFlags GetDelayedProcessMitigations() const override; + ResultCode SetDisconnectCsrss() override; + void SetStrictInterceptions() override; + ResultCode SetStdoutHandle(HANDLE handle) override; + ResultCode SetStderrHandle(HANDLE handle) override; + ResultCode AddRule(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern) override; + ResultCode AddDllToUnload(const wchar_t* dll_name) override; + ResultCode AddKernelObjectToClose(const wchar_t* handle_type, + const wchar_t* handle_name) override; + void AddHandleToShare(HANDLE handle) override; + void SetLockdownDefaultDacl() override; + void AddRestrictingRandomSid() override; + void SetEnableOPMRedirection() override; + bool GetEnableOPMRedirection() override; + ResultCode AddAppContainerProfile(const wchar_t* package_name, + bool create_profile) override; + scoped_refptr<AppContainerProfile> GetAppContainerProfile() override; + void SetEffectiveToken(HANDLE token) override; + + // Get the AppContainer profile as its internal type. + scoped_refptr<AppContainerProfileBase> GetAppContainerProfileBase(); + + // Creates a Job object with the level specified in a previous call to + // SetJobLevel(). + ResultCode MakeJobObject(base::win::ScopedHandle* job); + + // Creates the two tokens with the levels specified in a previous call to + // SetTokenLevel(). Also creates a lowbox token if specified based on the + // lowbox SID. + ResultCode MakeTokens(base::win::ScopedHandle* initial, + base::win::ScopedHandle* lockdown, + base::win::ScopedHandle* lowbox); + + PSID GetLowBoxSid() const; + + // Adds a target process to the internal list of targets. Internally a + // call to TargetProcess::Init() is issued. + ResultCode AddTarget(TargetProcess* target); + + // Called when there are no more active processes in a Job. + // Removes a Job object associated with this policy and the target associated + // with the job. + bool OnJobEmpty(HANDLE job); + + EvalResult EvalPolicy(IpcTag service, CountedParameterSetBase* params); + + HANDLE GetStdoutHandle(); + HANDLE GetStderrHandle(); + + // Returns the list of handles being shared with the target process. + const base::HandlesToInheritVector& GetHandlesBeingShared(); + + private: + // Allow PolicyInfo to snapshot PolicyBase for diagnostics. + friend class PolicyDiagnostic; + ~PolicyBase(); + + // Sets up interceptions for a new target. + ResultCode SetupAllInterceptions(TargetProcess* target); + + // Sets up the handle closer for a new target. + bool SetupHandleCloser(TargetProcess* target); + + ResultCode AddRuleInternal(SubSystem subsystem, + Semantics semantics, + const wchar_t* pattern); + + // This lock synchronizes operations on the targets_ collection. + CRITICAL_SECTION lock_; + // Maintains the list of target process associated with this policy. + // The policy takes ownership of them. + typedef std::list<TargetProcess*> TargetSet; + TargetSet targets_; + // Standard object-lifetime reference counter. + volatile LONG ref_count; + // The user-defined global policy settings. + TokenLevel lockdown_level_; + TokenLevel initial_level_; + bool use_restricting_sids_ = true; + JobLevel job_level_; + uint32_t ui_exceptions_; + size_t memory_limit_; + bool use_alternate_desktop_; + bool use_alternate_winstation_; + // Helps the file system policy initialization. + bool file_system_init_; + bool relaxed_interceptions_; + HANDLE stdout_handle_; + HANDLE stderr_handle_; + IntegrityLevel integrity_level_; + IntegrityLevel delayed_integrity_level_; + MitigationFlags mitigations_; + MitigationFlags delayed_mitigations_; + bool is_csrss_connected_; + // Object in charge of generating the low level policy. + LowLevelPolicy* policy_maker_; + // Memory structure that stores the low level policy. + PolicyGlobal* policy_; + // The list of dlls to unload in the target process. + std::vector<std::wstring> blocklisted_dlls_; + // This is a map of handle-types to names that we need to close in the + // target process. A null set means we need to close all handles of the + // given type. + HandleCloser handle_closer_; + PSID lowbox_sid_; + base::win::ScopedHandle lowbox_directory_; + std::unique_ptr<Dispatcher> dispatcher_; + bool lockdown_default_dacl_; + bool add_restricting_random_sid_; + + static HDESK alternate_desktop_handle_; + static HWINSTA alternate_winstation_handle_; + static HDESK alternate_desktop_local_winstation_handle_; + static IntegrityLevel alternate_desktop_integrity_level_label_; + static IntegrityLevel + alternate_desktop_local_winstation_integrity_level_label_; + + // Contains the list of handles being shared with the target process. + // This list contains handles other than the stderr/stdout handles which are + // shared with the target at times. + base::HandlesToInheritVector handles_to_share_; + bool enable_opm_redirection_; + + scoped_refptr<AppContainerProfileBase> app_container_profile_; + + HANDLE effective_token_; + + DISALLOW_COPY_AND_ASSIGN(PolicyBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_POLICY_BASE_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_rand.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_rand.cc new file mode 100644 index 0000000000..9bc8634ec6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_rand.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox_rand.h" + +#include <windows.h> + +// #define needed to link in RtlGenRandom(), a.k.a. SystemFunction036. See the +// "Community Additions" comment on MSDN here: +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx +#define SystemFunction036 NTAPI SystemFunction036 +#include <ntsecapi.h> +#undef SystemFunction036 + +namespace sandbox { + +bool GetRandom(unsigned int* random_value) { + return RtlGenRandom(random_value, sizeof(unsigned int)) != false; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_rand.h b/security/sandbox/chromium/sandbox/win/src/sandbox_rand.h new file mode 100644 index 0000000000..f4662f9a84 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_rand.h @@ -0,0 +1,15 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_RAND_H_ +#define SANDBOX_SRC_SANDBOX_RAND_H_ + +namespace sandbox { + +// Generate a random value in |random_value|. Returns true on success. +bool GetRandom(unsigned int* random_value); + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_RAND_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_types.h b/security/sandbox/chromium/sandbox/win/src/sandbox_types.h new file mode 100644 index 0000000000..782541863d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_types.h @@ -0,0 +1,199 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ +#define SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ + +#include "base/process/kill.h" +#include "base/process/launch.h" + +namespace sandbox { + +#ifdef __MINGW32__ +// Map Microsoft's proprietary more-safe version of copy() back to +// the std::basic_string method +#define _Copy_s copy +#endif + +// Operation result codes returned by the sandbox API. +// +// Note: These codes are listed in a histogram and any new codes should be added +// at the end. If the underlying type is changed then the forward declaration in +// sandbox_init.h must be updated. +// +enum ResultCode : int { + SBOX_ALL_OK = 0, + // Error is originating on the win32 layer. Call GetlastError() for more + // information. + SBOX_ERROR_GENERIC = 1, + // An invalid combination of parameters was given to the API. + SBOX_ERROR_BAD_PARAMS = 2, + // The desired operation is not supported at this time. + SBOX_ERROR_UNSUPPORTED = 3, + // The request requires more memory that allocated or available. + SBOX_ERROR_NO_SPACE = 4, + // The ipc service requested does not exist. + SBOX_ERROR_INVALID_IPC = 5, + // The ipc service did not complete. + SBOX_ERROR_FAILED_IPC = 6, + // The requested handle was not found. + SBOX_ERROR_NO_HANDLE = 7, + // This function was not expected to be called at this time. + SBOX_ERROR_UNEXPECTED_CALL = 8, + // WaitForAllTargets is already called. + SBOX_ERROR_WAIT_ALREADY_CALLED = 9, + // A channel error prevented DoCall from executing. + SBOX_ERROR_CHANNEL_ERROR = 10, + // Failed to create the alternate desktop. + SBOX_ERROR_CANNOT_CREATE_DESKTOP = 11, + // Failed to create the alternate window station. + SBOX_ERROR_CANNOT_CREATE_WINSTATION = 12, + // Failed to switch back to the interactive window station. + SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION = 13, + // The supplied AppContainer is not valid. + SBOX_ERROR_INVALID_APP_CONTAINER = 14, + // The supplied capability is not valid. + SBOX_ERROR_INVALID_CAPABILITY = 15, + // There is a failure initializing the AppContainer. + SBOX_ERROR_CANNOT_INIT_APPCONTAINER = 16, + // Initializing or updating ProcThreadAttributes failed. + SBOX_ERROR_PROC_THREAD_ATTRIBUTES = 17, + // Error in creating process. + SBOX_ERROR_CREATE_PROCESS = 18, + // Failure calling delegate PreSpawnTarget. + SBOX_ERROR_DELEGATE_PRE_SPAWN = 19, + // Could not assign process to job object. + SBOX_ERROR_ASSIGN_PROCESS_TO_JOB_OBJECT = 20, + // Could not assign process to job object. + SBOX_ERROR_SET_THREAD_TOKEN = 21, + // Could not get thread context of new process. + SBOX_ERROR_GET_THREAD_CONTEXT = 22, + // Could not duplicate target info of new process. + SBOX_ERROR_DUPLICATE_TARGET_INFO = 23, + // Could not set low box token. + SBOX_ERROR_SET_LOW_BOX_TOKEN = 24, + // Could not create file mapping for IPC dispatcher. + SBOX_ERROR_CREATE_FILE_MAPPING = 25, + // Could not duplicate shared section into target process for IPC dispatcher. + SBOX_ERROR_DUPLICATE_SHARED_SECTION = 26, + // Could not map view of shared memory in broker. + SBOX_ERROR_MAP_VIEW_OF_SHARED_SECTION = 27, + // Could not apply ASLR mitigations to target process. + SBOX_ERROR_APPLY_ASLR_MITIGATIONS = 28, + // Could not setup one of the required interception services. + SBOX_ERROR_SETUP_BASIC_INTERCEPTIONS = 29, + // Could not setup basic interceptions. + SBOX_ERROR_SETUP_INTERCEPTION_SERVICE = 30, + // Could not initialize interceptions. This usually means 3rd party software + // is stomping on our hooks, or can sometimes mean the syscall format has + // changed. + SBOX_ERROR_INITIALIZE_INTERCEPTIONS = 31, + // Could not setup the imports for ntdll in target process. + SBOX_ERROR_SETUP_NTDLL_IMPORTS = 32, + // Could not setup the handle closer in target process. + SBOX_ERROR_SETUP_HANDLE_CLOSER = 33, + // Cannot get the current Window Station. + SBOX_ERROR_CANNOT_GET_WINSTATION = 34, + // Cannot query the security attributes of the current Window Station. + SBOX_ERROR_CANNOT_QUERY_WINSTATION_SECURITY = 35, + // Cannot get the current Desktop. + SBOX_ERROR_CANNOT_GET_DESKTOP = 36, + // Cannot query the security attributes of the current Desktop. + SBOX_ERROR_CANNOT_QUERY_DESKTOP_SECURITY = 37, + // Cannot setup the interception manager config buffer. + SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_CONFIG_BUFFER = 38, + // Cannot copy data to the child process. + SBOX_ERROR_CANNOT_COPY_DATA_TO_CHILD = 39, + // Cannot setup the interception thunk. + SBOX_ERROR_CANNOT_SETUP_INTERCEPTION_THUNK = 40, + // Cannot resolve the interception thunk. + SBOX_ERROR_CANNOT_RESOLVE_INTERCEPTION_THUNK = 41, + // Cannot write interception thunk to child process. + SBOX_ERROR_CANNOT_WRITE_INTERCEPTION_THUNK = 42, + // Cannot find the base address of the new process. + SBOX_ERROR_CANNOT_FIND_BASE_ADDRESS = 43, + // Cannot create the AppContainer profile. + SBOX_ERROR_CREATE_APPCONTAINER_PROFILE = 44, + // Cannot create the AppContainer as the main executable can't be accessed. + SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_ACCESS_CHECK = 45, + // Cannot create the AppContainer as adding a capability failed. + SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_CAPABILITY = 46, + // Cannot initialize a job object. + SBOX_ERROR_CANNOT_INIT_JOB = 47, + // Invalid LowBox SID string. + SBOX_ERROR_INVALID_LOWBOX_SID = 48, + // Cannot create restricted token. + SBOX_ERROR_CANNOT_CREATE_RESTRICTED_TOKEN = 49, + // Cannot set the integrity level on a desktop object. + SBOX_ERROR_CANNOT_SET_DESKTOP_INTEGRITY = 50, + // Cannot create a LowBox token. + SBOX_ERROR_CANNOT_CREATE_LOWBOX_TOKEN = 51, + // Cannot modify LowBox token's DACL. + SBOX_ERROR_CANNOT_MODIFY_LOWBOX_TOKEN_DACL = 52, + // Cannot create restricted impersonation token. + SBOX_ERROR_CANNOT_CREATE_RESTRICTED_IMP_TOKEN = 53, + // Cannot duplicate target process handle. + SBOX_ERROR_CANNOT_DUPLICATE_PROCESS_HANDLE = 54, + // Cannot load executable for variable transfer. + SBOX_ERROR_CANNOT_LOADLIBRARY_EXECUTABLE = 55, + // Cannot find variable address for transfer. + SBOX_ERROR_CANNOT_FIND_VARIABLE_ADDRESS = 56, + // Cannot write variable value. + SBOX_ERROR_CANNOT_WRITE_VARIABLE_VALUE = 57, + // Short write to variable. + SBOX_ERROR_INVALID_WRITE_VARIABLE_SIZE = 58, + // Cannot initialize BrokerServices. + SBOX_ERROR_CANNOT_INIT_BROKERSERVICES = 59, + // Placeholder for last item of the enum. + SBOX_ERROR_LAST +}; + +// If the sandbox cannot create a secure environment for the target, the +// target will be forcibly terminated. These are the process exit codes. +enum TerminationCodes { + SBOX_FATAL_INTEGRITY = 7006, // Could not set the integrity level. + SBOX_FATAL_DROPTOKEN = 7007, // Could not lower the token. + SBOX_FATAL_FLUSHANDLES = 7008, // Failed to flush registry handles. + SBOX_FATAL_CACHEDISABLE = 7009, // Failed to forbid HCKU caching. + SBOX_FATAL_CLOSEHANDLES = 7010, // Failed to close pending handles. + SBOX_FATAL_MITIGATION = 7011, // Could not set the mitigation policy. + SBOX_FATAL_MEMORY_EXCEEDED = 7012, // Exceeded the job memory limit. + SBOX_FATAL_WARMUP = 7013, // Failed to warmup. + SBOX_FATAL_LAST +}; + +#if !defined(SANDBOX_FUZZ_TARGET) +static_assert(SBOX_FATAL_MEMORY_EXCEEDED == + base::win::kSandboxFatalMemoryExceeded, + "Value for SBOX_FATAL_MEMORY_EXCEEDED must match base."); +#endif // !defined(SANDBOX_FUZZ_TARGET) + +class BrokerServices; +class TargetServices; + +// Contains the pointer to a target or broker service. +struct SandboxInterfaceInfo { + BrokerServices* broker_services; + TargetServices* target_services; +}; + +#if SANDBOX_EXPORTS +#define SANDBOX_INTERCEPT extern "C" __declspec(dllexport) +#else +#define SANDBOX_INTERCEPT extern "C" +#endif + +enum InterceptionType { + INTERCEPTION_INVALID = 0, + INTERCEPTION_SERVICE_CALL, // Trampoline of an NT native call + INTERCEPTION_EAT, + INTERCEPTION_SIDESTEP, // Preamble patch + INTERCEPTION_SMART_SIDESTEP, // Preamble patch but bypass internal calls + INTERCEPTION_UNLOAD_MODULE, // Unload the module (don't patch) + INTERCEPTION_LAST // Placeholder for last item in the enumeration +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SANDBOX_TYPES_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_utils.cc b/security/sandbox/chromium/sandbox/win/src/sandbox_utils.cc new file mode 100644 index 0000000000..f34daa7d3e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_utils.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sandbox_utils.h" + +#include <windows.h> + +#include "base/logging.h" +#include "sandbox/win/src/internal_types.h" + +namespace sandbox { + +void InitObjectAttribs(const std::wstring& name, + ULONG attributes, + HANDLE root, + OBJECT_ATTRIBUTES* obj_attr, + UNICODE_STRING* uni_name, + SECURITY_QUALITY_OF_SERVICE* security_qos) { + static RtlInitUnicodeStringFunction RtlInitUnicodeString; + if (!RtlInitUnicodeString) { + HMODULE ntdll = ::GetModuleHandle(kNtdllName); + RtlInitUnicodeString = reinterpret_cast<RtlInitUnicodeStringFunction>( + GetProcAddress(ntdll, "RtlInitUnicodeString")); + DCHECK(RtlInitUnicodeString); + } + RtlInitUnicodeString(uni_name, name.c_str()); + InitializeObjectAttributes(obj_attr, uni_name, attributes, root, nullptr); + obj_attr->SecurityQualityOfService = security_qos; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sandbox_utils.h b/security/sandbox/chromium/sandbox/win/src/sandbox_utils.h new file mode 100644 index 0000000000..580d1298f6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sandbox_utils.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SANDBOX_UTILS_H_ +#define SANDBOX_SRC_SANDBOX_UTILS_H_ + +#include <windows.h> +#include <string> + +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +void InitObjectAttribs(const std::wstring& name, + ULONG attributes, + HANDLE root, + OBJECT_ATTRIBUTES* obj_attr, + UNICODE_STRING* uni_name, + SECURITY_QUALITY_OF_SERVICE* security_qos); + +} // namespace sandbox + +#endif // SANDBOX_SRC_SANDBOX_UTILS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/security_capabilities.cc b/security/sandbox/chromium/sandbox/win/src/security_capabilities.cc new file mode 100644 index 0000000000..0df446b5be --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/security_capabilities.cc @@ -0,0 +1,33 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/security_capabilities.h" +#include "base/numerics/safe_conversions.h" + +namespace sandbox { + +SecurityCapabilities::SecurityCapabilities(const Sid& package_sid, + const std::vector<Sid>& capabilities) + : SECURITY_CAPABILITIES(), + capabilities_(capabilities), + package_sid_(package_sid) { + AppContainerSid = package_sid_.GetPSID(); + if (capabilities_.empty()) + return; + + capability_sids_.resize(capabilities_.size()); + for (size_t index = 0; index < capabilities_.size(); ++index) { + capability_sids_[index].Sid = capabilities_[index].GetPSID(); + capability_sids_[index].Attributes = SE_GROUP_ENABLED; + } + CapabilityCount = base::checked_cast<DWORD>(capability_sids_.size()); + Capabilities = capability_sids_.data(); +} + +SecurityCapabilities::SecurityCapabilities(const Sid& package_sid) + : SecurityCapabilities(package_sid, std::vector<Sid>()) {} + +SecurityCapabilities::~SecurityCapabilities() {} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/security_capabilities.h b/security/sandbox/chromium/sandbox/win/src/security_capabilities.h new file mode 100644 index 0000000000..7a66e59743 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/security_capabilities.h @@ -0,0 +1,34 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SECURITY_CAPABILITIES_H_ +#define SANDBOX_SRC_SECURITY_CAPABILITIES_H_ + +#include <windows.h> + +#include <vector> + +#include "base/macros.h" +#include "sandbox/win/src/sid.h" + +namespace sandbox { + +class SecurityCapabilities final : public SECURITY_CAPABILITIES { + public: + explicit SecurityCapabilities(const Sid& package_sid); + SecurityCapabilities(const Sid& package_sid, + const std::vector<Sid>& capabilities); + ~SecurityCapabilities(); + + private: + std::vector<Sid> capabilities_; + std::vector<SID_AND_ATTRIBUTES> capability_sids_; + Sid package_sid_; + + DISALLOW_COPY_AND_ASSIGN(SecurityCapabilities); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SECURITY_CAPABILITIES_H_
\ No newline at end of file diff --git a/security/sandbox/chromium/sandbox/win/src/security_level.h b/security/sandbox/chromium/sandbox/win/src/security_level.h new file mode 100644 index 0000000000..8e81cf21c9 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/security_level.h @@ -0,0 +1,300 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SECURITY_LEVEL_H_ +#define SANDBOX_SRC_SECURITY_LEVEL_H_ + +#include <stdint.h> + +namespace sandbox { + +// List of all the integrity levels supported in the sandbox. +// The integrity level of the sandboxed process can't be set to a level higher +// than the broker process. +// +// Note: These levels map to SIDs under the hood. +// INTEGRITY_LEVEL_SYSTEM: "S-1-16-16384" System Mandatory Level +// INTEGRITY_LEVEL_HIGH: "S-1-16-12288" High Mandatory Level +// INTEGRITY_LEVEL_MEDIUM: "S-1-16-8192" Medium Mandatory Level +// INTEGRITY_LEVEL_MEDIUM_LOW: "S-1-16-6144" +// INTEGRITY_LEVEL_LOW: "S-1-16-4096" Low Mandatory Level +// INTEGRITY_LEVEL_BELOW_LOW: "S-1-16-2048" +// INTEGRITY_LEVEL_UNTRUSTED: "S-1-16-0" Untrusted Mandatory Level +// +// Not defined: "S-1-16-20480" Protected Process Mandatory Level +// Not defined: "S-1-16-28672" Secure Process Mandatory Level +enum IntegrityLevel { + INTEGRITY_LEVEL_SYSTEM, + INTEGRITY_LEVEL_HIGH, + INTEGRITY_LEVEL_MEDIUM, + INTEGRITY_LEVEL_MEDIUM_LOW, + INTEGRITY_LEVEL_LOW, + INTEGRITY_LEVEL_BELOW_LOW, + INTEGRITY_LEVEL_UNTRUSTED, + INTEGRITY_LEVEL_LAST +}; + +// The Token level specifies a set of security profiles designed to +// provide the bulk of the security of sandbox. +// +// TokenLevel |Restricting |Deny Only |Privileges| +// |Sids |Sids | | +// ----------------------------|--------------|----------------|----------| +// USER_LOCKDOWN | Null Sid | All | None | +// ----------------------------|--------------|----------------|----------| +// USER_RESTRICTED | RESTRICTED | All | Traverse | +// ----------------------------|--------------|----------------|----------| +// USER_LIMITED | Users | All except: | Traverse | +// | Everyone | Users | | +// | RESTRICTED | Everyone | | +// | | Interactive | | +// ----------------------------|--------------|----------------|----------| +// USER_INTERACTIVE | Users | All except: | Traverse | +// | Everyone | Users | | +// | RESTRICTED | Everyone | | +// | Owner | Interactive | | +// | | Local | | +// | | Authent-users | | +// | | User | | +// ----------------------------|--------------|----------------|----------| +// USER_RESTRICTED_NON_ADMIN | Users | All except: | Traverse | +// | Everyone | Users | | +// | Interactive | Everyone | | +// | Local | Interactive | | +// | Authent-users| Local | | +// | User | Authent-users | | +// | | User | | +// ----------------------------|--------------|----------------|----------| +// USER_NON_ADMIN | None | All except: | Traverse | +// | | Users | | +// | | Everyone | | +// | | Interactive | | +// | | Local | | +// | | Authent-users | | +// | | User | | +// ----------------------------|--------------|----------------|----------| +// USER_RESTRICTED_SAME_ACCESS | All | None | All | +// ----------------------------|--------------|----------------|----------| +// USER_UNPROTECTED | None | None | All | +// ----------------------------|--------------|----------------|----------| +// +// The above restrictions are actually a transformation that is applied to +// the existing broker process token. The resulting token that will be +// applied to the target process depends both on the token level selected +// and on the broker token itself. +// +// The LOCKDOWN and RESTRICTED are designed to allow access to almost +// nothing that has security associated with and they are the recommended +// levels to run sandboxed code specially if there is a chance that the +// broker is process might be started by a user that belongs to the Admins +// or power users groups. +enum TokenLevel { + USER_LOCKDOWN = 0, + USER_RESTRICTED, + USER_LIMITED, + USER_INTERACTIVE, + USER_RESTRICTED_NON_ADMIN, + USER_NON_ADMIN, + USER_RESTRICTED_SAME_ACCESS, + USER_UNPROTECTED, + USER_LAST +}; + +// The Job level specifies a set of decreasing security profiles for the +// Job object that the target process will be placed into. +// This table summarizes the security associated with each level: +// +// JobLevel |General |Quota | +// |restrictions |restrictions | +// -----------------|---------------------------------- |--------------------| +// JOB_NONE | No job is assigned to the | None | +// | sandboxed process. | | +// -----------------|---------------------------------- |--------------------| +// JOB_UNPROTECTED | None | *Kill on Job close.| +// -----------------|---------------------------------- |--------------------| +// JOB_INTERACTIVE | *Forbid system-wide changes using | | +// | SystemParametersInfo(). | *Kill on Job close.| +// | *Forbid the creation/switch of | | +// | Desktops. | | +// | *Forbids calls to ExitWindows(). | | +// -----------------|---------------------------------- |--------------------| +// JOB_LIMITED_USER | Same as INTERACTIVE_USER plus: | *One active process| +// | *Forbid changes to the display | limit. | +// | settings. | *Kill on Job close.| +// -----------------|---------------------------------- |--------------------| +// JOB_RESTRICTED | Same as LIMITED_USER plus: | *One active process| +// | * No read/write to the clipboard. | limit. | +// | * No access to User Handles that | *Kill on Job close.| +// | belong to other processes. | | +// | * Forbid message broadcasts. | | +// | * Forbid setting global hooks. | | +// | * No access to the global atoms | | +// | table. | | +// -----------------|-----------------------------------|--------------------| +// JOB_LOCKDOWN | Same as RESTRICTED | *One active process| +// | | limit. | +// | | *Kill on Job close.| +// | | *Kill on unhandled | +// | | exception. | +// | | | +// In the context of the above table, 'user handles' refers to the handles of +// windows, bitmaps, menus, etc. Files, treads and registry handles are kernel +// handles and are not affected by the job level settings. +enum JobLevel { + JOB_LOCKDOWN = 0, + JOB_RESTRICTED, + JOB_LIMITED_USER, + JOB_INTERACTIVE, + JOB_UNPROTECTED, + JOB_NONE +}; + +// These flags correspond to various process-level mitigations (eg. ASLR and +// DEP). Most are implemented via UpdateProcThreadAttribute() plus flags for +// the PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY attribute argument; documented +// here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686880 +// Some mitigations are implemented directly by the sandbox or emulated to +// the greatest extent possible when not directly supported by the OS. +// Flags that are unsupported for the target OS will be silently ignored. +// Flags that are invalid for their application (pre or post startup) will +// return SBOX_ERROR_BAD_PARAMS. +typedef uint64_t MitigationFlags; + +// Permanently enables DEP for the target process. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_DEP_ENABLE. +const MitigationFlags MITIGATION_DEP = 0x00000001; + +// Permanently Disables ATL thunk emulation when DEP is enabled. Valid +// only when MITIGATION_DEP is passed. Corresponds to not passing +// PROCESS_CREATION_MITIGATION_POLICY_DEP_ATL_THUNK_ENABLE. +const MitigationFlags MITIGATION_DEP_NO_ATL_THUNK = 0x00000002; + +// Enables Structured exception handling override prevention. Must be +// enabled prior to process start. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_SEHOP_ENABLE. +const MitigationFlags MITIGATION_SEHOP = 0x00000004; + +// Forces ASLR on all images in the child process. In debug builds, must be +// enabled after startup. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON . +const MitigationFlags MITIGATION_RELOCATE_IMAGE = 0x00000008; + +// Refuses to load DLLs that cannot support ASLR. In debug builds, must be +// enabled after startup. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_FORCE_RELOCATE_IMAGES_ALWAYS_ON_REQ_RELOCS. +const MitigationFlags MITIGATION_RELOCATE_IMAGE_REQUIRED = 0x00000010; + +// Terminates the process on Windows heap corruption. Coresponds to +// PROCESS_CREATION_MITIGATION_POLICY_HEAP_TERMINATE_ALWAYS_ON. +const MitigationFlags MITIGATION_HEAP_TERMINATE = 0x00000020; + +// Sets a random lower bound as the minimum user address. Must be +// enabled prior to process start. On 32-bit processes this is +// emulated to a much smaller degree. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_BOTTOM_UP_ASLR_ALWAYS_ON. +const MitigationFlags MITIGATION_BOTTOM_UP_ASLR = 0x00000040; + +// Increases the randomness range of bottom-up ASLR to up to 1TB. Must be +// enabled prior to process start and with MITIGATION_BOTTOM_UP_ASLR. +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_HIGH_ENTROPY_ASLR_ALWAYS_ON +const MitigationFlags MITIGATION_HIGH_ENTROPY_ASLR = 0x00000080; + +// Immediately raises an exception on a bad handle reference. Must be +// enabled after startup. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_STRICT_HANDLE_CHECKS_ALWAYS_ON. +const MitigationFlags MITIGATION_STRICT_HANDLE_CHECKS = 0x00000100; + +// Sets the DLL search order to LOAD_LIBRARY_SEARCH_DEFAULT_DIRS. Additional +// directories can be added via the Windows AddDllDirectory() function. +// http://msdn.microsoft.com/en-us/library/windows/desktop/hh310515 +// Must be enabled after startup. +const MitigationFlags MITIGATION_DLL_SEARCH_ORDER = 0x00000200; + +// Changes the mandatory integrity level policy on the current process' token +// to enable no-read and no-execute up. This prevents a lower IL process from +// opening the process token for impersonate/duplicate/assignment. +const MitigationFlags MITIGATION_HARDEN_TOKEN_IL_POLICY = 0x00000400; + +// Prevents the process from making Win32k calls. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_WIN32K_SYSTEM_CALL_DISABLE_ALWAYS_ON. +// +// Applications linked to user32.dll or gdi32.dll make Win32k calls during +// setup, even if Win32k is not otherwise used. So they also need to add a rule +// with SUBSYS_WIN32K_LOCKDOWN and semantics FAKE_USER_GDI_INIT to allow the +// initialization to succeed. +const MitigationFlags MITIGATION_WIN32K_DISABLE = 0x00000800; + +// Prevents certain built-in third party extension points from being used. +// - App_Init DLLs +// - Winsock Layered Service Providers (LSPs) +// - Global Windows Hooks (NOT thread-targeted hooks) +// - Legacy Input Method Editors (IMEs) +// I.e.: Disable legacy hooking mechanisms. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_EXTENSION_POINT_DISABLE_ALWAYS_ON. +const MitigationFlags MITIGATION_EXTENSION_POINT_DISABLE = 0x00001000; + +// Prevents the process from generating dynamic code or modifying executable +// code. Second option to allow thread-specific opt-out. +// - VirtualAlloc with PAGE_EXECUTE_* +// - VirtualProtect with PAGE_EXECUTE_* +// - MapViewOfFile with FILE_MAP_EXECUTE | FILE_MAP_WRITE +// - SetProcessValidCallTargets for CFG +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON and +// PROCESS_CREATION_MITIGATION_POLICY_PROHIBIT_DYNAMIC_CODE_ALWAYS_ON_ALLOW_OPT_OUT. +const MitigationFlags MITIGATION_DYNAMIC_CODE_DISABLE = 0x00002000; +const MitigationFlags MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT = 0x00004000; +// The following per-thread flag can be used with the +// ApplyMitigationsToCurrentThread API. Requires the above process mitigation +// to be set on the current process. +const MitigationFlags MITIGATION_DYNAMIC_CODE_OPT_OUT_THIS_THREAD = 0x00008000; + +// Prevents the process from loading non-system fonts into GDI. +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_FONT_DISABLE_ALWAYS_ON +const MitigationFlags MITIGATION_NONSYSTEM_FONT_DISABLE = 0x00010000; + +// Prevents the process from loading binaries NOT signed by MS. +// Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON +const MitigationFlags MITIGATION_FORCE_MS_SIGNED_BINS = 0x00020000; + +// Blocks mapping of images from remote devices. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_REMOTE_ALWAYS_ON. +const MitigationFlags MITIGATION_IMAGE_LOAD_NO_REMOTE = 0x00040000; + +// Blocks mapping of images that have the low manditory label. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_NO_LOW_LABEL_ALWAYS_ON. +const MitigationFlags MITIGATION_IMAGE_LOAD_NO_LOW_LABEL = 0x00080000; + +// Forces image load preference to prioritize the Windows install System32 +// folder before dll load dir, application dir and any user dirs set. +// - Affects IAT resolution standard search path only, NOT direct LoadLibrary or +// executable search path. +// PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON. +const MitigationFlags MITIGATION_IMAGE_LOAD_PREFER_SYS32 = 0x00100000; + +// Prevents hyperthreads from interfering with indirect branch predictions. +// (SPECTRE Variant 2 mitigation.) Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY2_RESTRICT_INDIRECT_BRANCH_PREDICTION_ALWAYS_ON. +const MitigationFlags MITIGATION_RESTRICT_INDIRECT_BRANCH_PREDICTION = + 0x00200000; + +// Begin Mozilla-added flags. +// Working down from the high bit to avoid conflict with new upstream flags. + +// Disable Control Flow Guard. This may seem more like an anti-mitigation, but +// this flag allows code to make targeted changes to CFG to avoid bugs, while +// leaving it enabled in the common case. Corresponds to +// PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_ON. +const MitigationFlags MITIGATION_CONTROL_FLOW_GUARD_DISABLE = 0x80000000; + +// This enables CET User Shadow Stack for compatible modules and corresponds to +// PROCESS_CREATION_MITIGATION_POLICY2_CET_USER_SHADOW_STACKS_ALWAYS_ON. +const MitigationFlags MITIGATION_CET_COMPAT_MODE = 0x40000000; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SECURITY_LEVEL_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver.cc b/security/sandbox/chromium/sandbox/win/src/service_resolver.cc new file mode 100644 index 0000000000..f26322dd63 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver.cc @@ -0,0 +1,47 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/service_resolver.h" + +#include "base/win/pe_image.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::ResolveInterceptor( + const void* interceptor_module, + const char* interceptor_name, + const void** address) { + // After all, we are using a locally mapped version of the exe, so the + // action is the same as for a target function. + return ResolveTarget(interceptor_module, interceptor_name, + const_cast<void**>(address)); +} + +// In this case all the work is done from the parent, so resolve is +// just a simple GetProcAddress. +NTSTATUS ServiceResolverThunk::ResolveTarget(const void* module, + const char* function_name, + void** address) { + if (!module) + return STATUS_UNSUCCESSFUL; + + base::win::PEImage module_image(module); + *address = + reinterpret_cast<void*>(module_image.GetProcAddress(function_name)); + + if (!*address) { + NOTREACHED_NT(); + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +void ServiceResolverThunk::AllowLocalPatches() { + ntdll_base_ = ::GetModuleHandle(kNtdllName); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver.h b/security/sandbox/chromium/sandbox/win/src/service_resolver.h new file mode 100644 index 0000000000..ee292f6a37 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver.h @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SERVICE_RESOLVER_H__ +#define SANDBOX_SRC_SERVICE_RESOLVER_H__ + +#include <stddef.h> + +#include "base/macros.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/resolver.h" + +namespace sandbox { + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll. +class ServiceResolverThunk : public ResolverThunk { + public: + // The service resolver needs a child process to write to. + ServiceResolverThunk(HANDLE process, bool relaxed) + : ntdll_base_(nullptr), + process_(process), + relaxed_(relaxed), + relative_jump_(0) {} + ~ServiceResolverThunk() override {} + + // Implementation of Resolver::Setup. + NTSTATUS Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) override; + + // Implementation of Resolver::ResolveInterceptor. + NTSTATUS ResolveInterceptor(const void* module, + const char* function_name, + const void** address) override; + + // Implementation of Resolver::ResolveTarget. + NTSTATUS ResolveTarget(const void* module, + const char* function_name, + void** address) override; + + // Implementation of Resolver::GetThunkSize. + size_t GetThunkSize() const override; + + // Call this to set up ntdll_base_ which will allow for local patches. + virtual void AllowLocalPatches(); + + // Verifies that the function specified by |target_name| in |target_module| is + // a service and copies the data from that function into |thunk_storage|. If + // |storage_bytes| is too small, then the method fails. + virtual NTSTATUS CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used); + + protected: + // The unit test will use this member to allow local patch on a buffer. + HMODULE ntdll_base_; + + // Handle of the child process. + HANDLE process_; + + private: + // Returns true if the code pointer by target_ corresponds to the expected + // type of function. Saves that code on the first part of the thunk pointed + // by local_thunk (should be directly accessible from the parent). + virtual bool IsFunctionAService(void* local_thunk) const; + + // Performs the actual patch of target_. + // local_thunk must be already fully initialized, and the first part must + // contain the original code. The real type of this buffer is ServiceFullThunk + // (yes, private). remote_thunk (real type ServiceFullThunk), must be + // allocated on the child, and will contain the thunk data, after this call. + // Returns the apropriate status code. + virtual NTSTATUS PerformPatch(void* local_thunk, void* remote_thunk); + + // Provides basically the same functionality as IsFunctionAService but it + // continues even if it does not recognize the function code. remote_thunk + // is the address of our memory on the child. + bool SaveOriginalFunction(void* local_thunk, void* remote_thunk); + + // true if we are allowed to patch already-patched functions. + bool relaxed_; + ULONG relative_jump_; + + DISALLOW_COPY_AND_ASSIGN(ServiceResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on WOW64 (32 bit ntdll on 64 bit Vista). +class Wow64ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Wow64ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Wow64ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Wow64ResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on WOW64 for Windows 8. +class Wow64W8ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Wow64W8ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Wow64W8ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Wow64W8ResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on Windows 8. +class Win8ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Win8ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Win8ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Win8ResolverThunk); +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll on WOW64 for Windows 10. +class Wow64W10ResolverThunk : public ServiceResolverThunk { + public: + // The service resolver needs a child process to write to. + Wow64W10ResolverThunk(HANDLE process, bool relaxed) + : ServiceResolverThunk(process, relaxed) {} + ~Wow64W10ResolverThunk() override {} + + private: + bool IsFunctionAService(void* local_thunk) const override; + + DISALLOW_COPY_AND_ASSIGN(Wow64W10ResolverThunk); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SERVICE_RESOLVER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver_32.cc b/security/sandbox/chromium/sandbox/win/src/service_resolver_32.cc new file mode 100644 index 0000000000..d95ce0086b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver_32.cc @@ -0,0 +1,476 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/service_resolver.h" + +#include <stddef.h> + +#include <memory> + +#include "base/bit_cast.h" +#include "sandbox/win/src/win_utils.h" + +namespace { +#pragma pack(push, 1) + +const BYTE kMovEax = 0xB8; +const BYTE kMovEdx = 0xBA; +const USHORT kMovEdxEsp = 0xD48B; +const USHORT kCallPtrEdx = 0x12FF; +const USHORT kCallEdx = 0xD2FF; +const BYTE kCallEip = 0xE8; +const BYTE kRet = 0xC2; +const BYTE kRet2 = 0xC3; +const USHORT kJmpEdx = 0xE2FF; +const USHORT kXorEcx = 0xC933; +const ULONG kLeaEdx = 0x0424548D; +const ULONG kCallFs1 = 0xC015FF64; +const USHORT kCallFs2 = 0; +const BYTE kCallFs3 = 0; +const BYTE kAddEsp1 = 0x83; +const USHORT kAddEsp2 = 0x4C4; +const BYTE kJmp32 = 0xE9; +const USHORT kSysenter = 0x340F; + +// Service code for 32 bit systems. +// NOTE: on win2003 "call dword ptr [edx]" is "call edx". +struct ServiceEntry { + // This struct contains roughly the following code: + // 00 mov eax,25h + // 05 mov edx,offset SharedUserData!SystemCallStub (7ffe0300) + // 0a call dword ptr [edx] + // 0c ret 2Ch + // 0f nop + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE mov_edx; // = BA + ULONG stub; + USHORT call_ptr_edx; // = FF 12 + BYTE ret; // = C2 + USHORT num_params; + BYTE nop; +}; + +// Service code for 32 bit Windows 8. +struct ServiceEntryW8 { + // This struct contains the following code: + // 00 b825000000 mov eax,25h + // 05 e803000000 call eip+3 + // 0a c22c00 ret 2Ch + // 0d 8bd4 mov edx,esp + // 0f 0f34 sysenter + // 11 c3 ret + // 12 8bff mov edi,edi + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE call_eip; // = E8 + ULONG call_offset; + BYTE ret_p; // = C2 + USHORT num_params; + USHORT mov_edx_esp; // = BD D4 + USHORT sysenter; // = 0F 34 + BYTE ret; // = C3 + USHORT nop; +}; + +// Service code for a 32 bit process running on a 64 bit os. +struct Wow64Entry { + // This struct may contain one of two versions of code: + // 1. For XP, Vista and 2K3: + // 00 b825000000 mov eax, 25h + // 05 33c9 xor ecx, ecx + // 07 8d542404 lea edx, [esp + 4] + // 0b 64ff15c0000000 call dword ptr fs:[0C0h] + // 12 c22c00 ret 2Ch + // + // 2. For Windows 7: + // 00 b825000000 mov eax, 25h + // 05 33c9 xor ecx, ecx + // 07 8d542404 lea edx, [esp + 4] + // 0b 64ff15c0000000 call dword ptr fs:[0C0h] + // 12 83c404 add esp, 4 + // 15 c22c00 ret 2Ch + // + // So we base the structure on the bigger one: + BYTE mov_eax; // = B8 + ULONG service_id; + USHORT xor_ecx; // = 33 C9 + ULONG lea_edx; // = 8D 54 24 04 + ULONG call_fs1; // = 64 FF 15 C0 + USHORT call_fs2; // = 00 00 + BYTE call_fs3; // = 00 + BYTE add_esp1; // = 83 or ret + USHORT add_esp2; // = C4 04 or num_params + BYTE ret; // = C2 + USHORT num_params; +}; + +// Service code for a 32 bit process running on 64 bit Windows 8. +struct Wow64EntryW8 { + // 00 b825000000 mov eax, 25h + // 05 64ff15c0000000 call dword ptr fs:[0C0h] + // 0b c22c00 ret 2Ch + // 0f 90 nop + BYTE mov_eax; // = B8 + ULONG service_id; + ULONG call_fs1; // = 64 FF 15 C0 + USHORT call_fs2; // = 00 00 + BYTE call_fs3; // = 00 + BYTE ret; // = C2 + USHORT num_params; + BYTE nop; +}; + +// Service code for a 32 bit process running on 64 bit Windows 10. +struct Wow64EntryW10 { + // 00 b828000000 mov eax, 28h + // 05 bab0d54877 mov edx, 7748D5B0h + // 09 ffd2 call edx + // 0b c22800 ret 28h + BYTE mov_eax; // = B8 + ULONG service_id; + BYTE mov_edx; // = BA + ULONG mov_edx_param; + USHORT call_edx; // = FF D2 + BYTE ret; // = C2 + USHORT num_params; +}; + +// Make sure that relaxed patching works as expected. +const size_t kMinServiceSize = offsetof(ServiceEntry, ret); +static_assert(sizeof(ServiceEntryW8) >= kMinServiceSize, + "wrong service length"); +static_assert(sizeof(Wow64Entry) >= kMinServiceSize, "wrong service length"); +static_assert(sizeof(Wow64EntryW8) >= kMinServiceSize, "wrong service length"); + +struct ServiceFullThunk { + union { + ServiceEntry original; + ServiceEntryW8 original_w8; + Wow64Entry wow_64; + Wow64EntryW8 wow_64_w8; + }; + int internal_thunk; // Dummy member to the beginning of the internal thunk. +}; + +#pragma pack(pop) + +} // namespace + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = + Init(target_module, interceptor_module, target_name, interceptor_name, + interceptor_entry_point, thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + relative_jump_ = 0; + size_t thunk_bytes = GetThunkSize(); + std::unique_ptr<char[]> thunk_buffer(new char[thunk_bytes]); + ServiceFullThunk* thunk = + reinterpret_cast<ServiceFullThunk*>(thunk_buffer.get()); + + if (!IsFunctionAService(&thunk->original) && + (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) { + return STATUS_OBJECT_NAME_COLLISION; + } + + ret = PerformPatch(thunk, thunk_storage); + + if (storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +size_t ServiceResolverThunk::GetThunkSize() const { + return offsetof(ServiceFullThunk, internal_thunk) + GetInternalThunkSize(); +} + +NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + if (storage_bytes < thunk_bytes) + return STATUS_UNSUCCESSFUL; + + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage); + + if (!IsFunctionAService(&thunk->original) && + (!relaxed_ || !SaveOriginalFunction(&thunk->original, thunk_storage))) { + return STATUS_OBJECT_NAME_COLLISION; + } + + if (storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceEntry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kMovEdx != function_code.mov_edx || + (kCallPtrEdx != function_code.call_ptr_edx && + kCallEdx != function_code.call_ptr_edx) || + kRet != function_code.ret) { + return false; + } + + // Find the system call pointer if we don't already have it. + if (kCallEdx != function_code.call_ptr_edx) { + DWORD ki_system_call; + if (!::ReadProcessMemory(process_, + bit_cast<const void*>(function_code.stub), + &ki_system_call, sizeof(ki_system_call), &read)) { + return false; + } + + if (sizeof(ki_system_call) != read) + return false; + + HMODULE module_1, module_2; + // last check, call_stub should point to a KiXXSystemCall function on ntdll + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + bit_cast<const wchar_t*>(ki_system_call), + &module_1)) { + return false; + } + + if (ntdll_base_) { + // This path is only taken when running the unit tests. We want to be + // able to patch a buffer in memory, so target_ is not inside ntdll. + module_2 = ntdll_base_; + } else { + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<const wchar_t*>(target_), + &module_2)) + return false; + } + + if (module_1 != module_2) + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, + void* remote_thunk) { + ServiceEntry intercepted_code; + size_t bytes_to_write = sizeof(intercepted_code); + ServiceFullThunk* full_local_thunk = + reinterpret_cast<ServiceFullThunk*>(local_thunk); + ServiceFullThunk* full_remote_thunk = + reinterpret_cast<ServiceFullThunk*>(remote_thunk); + + // patch the original code + memcpy(&intercepted_code, &full_local_thunk->original, + sizeof(intercepted_code)); + intercepted_code.mov_eax = kMovEax; + intercepted_code.service_id = full_local_thunk->original.service_id; + intercepted_code.mov_edx = kMovEdx; + intercepted_code.stub = bit_cast<ULONG>(&full_remote_thunk->internal_thunk); + intercepted_code.call_ptr_edx = kJmpEdx; + bytes_to_write = kMinServiceSize; + + if (relative_jump_) { + intercepted_code.mov_eax = kJmp32; + intercepted_code.service_id = relative_jump_; + bytes_to_write = offsetof(ServiceEntry, mov_edx); + } + + // setup the thunk + SetInternalThunk(&full_local_thunk->internal_thunk, GetInternalThunkSize(), + remote_thunk, interceptor_); + + size_t thunk_size = GetThunkSize(); + + // copy the local thunk buffer to the child + SIZE_T written; + if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, thunk_size, + &written)) { + return STATUS_UNSUCCESSFUL; + } + + if (thunk_size != written) + return STATUS_UNSUCCESSFUL; + + // and now change the function to intercept, on the child + if (ntdll_base_) { + // running a unit test + if (!::WriteProcessMemory(process_, target_, &intercepted_code, + bytes_to_write, &written)) + return STATUS_UNSUCCESSFUL; + } else { + if (!WriteProtectedChildMemory(process_, target_, &intercepted_code, + bytes_to_write)) + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +bool ServiceResolverThunk::SaveOriginalFunction(void* local_thunk, + void* remote_thunk) { + ServiceEntry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kJmp32 == function_code.mov_eax) { + // Plain old entry point patch. The relative jump address follows it. + ULONG relative = function_code.service_id; + + // First, fix our copy of their patch. + relative += bit_cast<ULONG>(target_) - bit_cast<ULONG>(remote_thunk); + + function_code.service_id = relative; + + // And now, remember how to re-patch it. + ServiceFullThunk* full_thunk = + reinterpret_cast<ServiceFullThunk*>(remote_thunk); + + const ULONG kJmp32Size = 5; + + relative_jump_ = bit_cast<ULONG>(&full_thunk->internal_thunk) - + bit_cast<ULONG>(target_) - kJmp32Size; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64Entry function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kXorEcx != function_code.xor_ecx || + kLeaEdx != function_code.lea_edx || kCallFs1 != function_code.call_fs1 || + kCallFs2 != function_code.call_fs2 || + kCallFs3 != function_code.call_fs3) { + return false; + } + + if ((kAddEsp1 == function_code.add_esp1 && + kAddEsp2 == function_code.add_esp2 && kRet == function_code.ret) || + kRet == function_code.add_esp1) { + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; + } + + return false; +} + +bool Wow64W8ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64EntryW8 function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kCallFs1 != function_code.call_fs1 || + kCallFs2 != function_code.call_fs2 || + kCallFs3 != function_code.call_fs3 || kRet != function_code.ret) { + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; +} + +bool Win8ResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceEntryW8 function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kCallEip != function_code.call_eip || + function_code.call_offset != 3 || kRet != function_code.ret_p || + kMovEdxEsp != function_code.mov_edx_esp || + kSysenter != function_code.sysenter || kRet2 != function_code.ret) { + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +bool Wow64W10ResolverThunk::IsFunctionAService(void* local_thunk) const { + Wow64EntryW10 function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) { + return false; + } + + if (sizeof(function_code) != read) + return false; + + if (kMovEax != function_code.mov_eax || kMovEdx != function_code.mov_edx || + kCallEdx != function_code.call_edx || kRet != function_code.ret) { + return false; + } + + // Save the verified code + memcpy(local_thunk, &function_code, sizeof(function_code)); + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver_64.cc b/security/sandbox/chromium/sandbox/win/src/service_resolver_64.cc new file mode 100644 index 0000000000..f692d0ea4f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver_64.cc @@ -0,0 +1,290 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/service_resolver.h" + +#include <stddef.h> + +#include <memory> + +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/win_utils.h" + +namespace { +#if defined(_M_X64) +#pragma pack(push, 1) + +const ULONG kMmovR10EcxMovEax = 0xB8D18B4C; +const USHORT kSyscall = 0x050F; +const BYTE kRetNp = 0xC3; +const ULONG64 kMov1 = 0x54894808244C8948; +const ULONG64 kMov2 = 0x4C182444894C1024; +const ULONG kMov3 = 0x20244C89; +const USHORT kTestByte = 0x04F6; +const BYTE kPtr = 0x25; +const BYTE kRet = 0xC3; +const USHORT kJne = 0x0375; + +// Service code for 64 bit systems. +struct ServiceEntry { + // This struct contains roughly the following code: + // 00 mov r10,rcx + // 03 mov eax,52h + // 08 syscall + // 0a ret + // 0b xchg ax,ax + // 0e xchg ax,ax + + ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + BYTE pad; // = 66 + USHORT xchg_ax_ax1; // = 66 90 + USHORT xchg_ax_ax2; // = 66 90 +}; + +// Service code for 64 bit Windows 8. +struct ServiceEntryW8 { + // This struct contains the following code: + // 00 48894c2408 mov [rsp+8], rcx + // 05 4889542410 mov [rsp+10], rdx + // 0a 4c89442418 mov [rsp+18], r8 + // 0f 4c894c2420 mov [rsp+20], r9 + // 14 4c8bd1 mov r10,rcx + // 17 b825000000 mov eax,25h + // 1c 0f05 syscall + // 1e c3 ret + // 1f 90 nop + + ULONG64 mov_1; // = 48 89 4C 24 08 48 89 54 + ULONG64 mov_2; // = 24 10 4C 89 44 24 18 4C + ULONG mov_3; // = 89 4C 24 20 + ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + BYTE nop; // = 90 +}; + +// Service code for 64 bit systems with int 2e fallback. +struct ServiceEntryWithInt2E { + // This struct contains roughly the following code: + // 00 4c8bd1 mov r10,rcx + // 03 b855000000 mov eax,52h + // 08 f604250803fe7f01 test byte ptr SharedUserData!308, 1 + // 10 7503 jne [over syscall] + // 12 0f05 syscall + // 14 c3 ret + // 15 cd2e int 2e + // 17 c3 ret + + ULONG mov_r10_rcx_mov_eax; // = 4C 8B D1 B8 + ULONG service_id; + USHORT test_byte; // = F6 04 + BYTE ptr; // = 25 + ULONG user_shared_data_ptr; + BYTE one; // = 01 + USHORT jne_over_syscall; // = 75 03 + USHORT syscall; // = 0F 05 + BYTE ret; // = C3 + USHORT int2e; // = CD 2E + BYTE ret2; // = C3 +}; + +// We don't have an internal thunk for x64. +struct ServiceFullThunk { + union { + ServiceEntry original; + ServiceEntryW8 original_w8; + ServiceEntryWithInt2E original_int2e_fallback; + }; +}; + +#pragma pack(pop) + +bool IsService(const void* source) { + const ServiceEntry* service = reinterpret_cast<const ServiceEntry*>(source); + + return (kMmovR10EcxMovEax == service->mov_r10_rcx_mov_eax && + kSyscall == service->syscall && kRetNp == service->ret); +} + +bool IsServiceW8(const void* source) { + const ServiceEntryW8* service = + reinterpret_cast<const ServiceEntryW8*>(source); + + return (kMmovR10EcxMovEax == service->mov_r10_rcx_mov_eax && + kMov1 == service->mov_1 && kMov2 == service->mov_2 && + kMov3 == service->mov_3); +} + +bool IsServiceWithInt2E(const void* source) { + const ServiceEntryWithInt2E* service = + reinterpret_cast<const ServiceEntryWithInt2E*>(source); + + return (kMmovR10EcxMovEax == service->mov_r10_rcx_mov_eax && + kTestByte == service->test_byte && kPtr == service->ptr && + kJne == service->jne_over_syscall && kSyscall == service->syscall && + kRet == service->ret && kRet == service->ret2); +} + +bool IsAnyService(const void* source) { + return IsService(source) || IsServiceW8(source) || IsServiceWithInt2E(source); +} + +#elif defined(_M_ARM64) +#pragma pack(push, 4) + +const ULONG kSvc = 0xD4000001; +const ULONG kRetNp = 0xD65F03C0; +const ULONG kServiceIdMask = 0x001FFFE0; + +struct ServiceEntry { + ULONG svc; + ULONG ret; + ULONG64 unused; +}; + +struct ServiceFullThunk { + ServiceEntry original; +}; + +#pragma pack(pop) + +bool IsService(const void* source) { + const ServiceEntry* service = reinterpret_cast<const ServiceEntry*>(source); + + return (kSvc == (service->svc & ~kServiceIdMask) && kRetNp == service->ret && + 0 == service->unused); +} + +bool IsAnyService(const void* source) { + return IsService(source); +} + +#else +#error "Unsupported Windows 64-bit Arch" +#endif + +} // namespace + +namespace sandbox { + +NTSTATUS ServiceResolverThunk::Setup(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = + Init(target_module, interceptor_module, target_name, interceptor_name, + interceptor_entry_point, thunk_storage, storage_bytes); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + std::unique_ptr<char[]> thunk_buffer(new char[thunk_bytes]); + ServiceFullThunk* thunk = + reinterpret_cast<ServiceFullThunk*>(thunk_buffer.get()); + + if (!IsFunctionAService(&thunk->original)) + return STATUS_OBJECT_NAME_COLLISION; + + ret = PerformPatch(thunk, thunk_storage); + + if (storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +size_t ServiceResolverThunk::GetThunkSize() const { + return sizeof(ServiceFullThunk); +} + +NTSTATUS ServiceResolverThunk::CopyThunk(const void* target_module, + const char* target_name, + BYTE* thunk_storage, + size_t storage_bytes, + size_t* storage_used) { + NTSTATUS ret = ResolveTarget(target_module, target_name, &target_); + if (!NT_SUCCESS(ret)) + return ret; + + size_t thunk_bytes = GetThunkSize(); + if (storage_bytes < thunk_bytes) + return STATUS_UNSUCCESSFUL; + + ServiceFullThunk* thunk = reinterpret_cast<ServiceFullThunk*>(thunk_storage); + + if (!IsFunctionAService(&thunk->original)) + return STATUS_OBJECT_NAME_COLLISION; + + if (storage_used) + *storage_used = thunk_bytes; + + return ret; +} + +bool ServiceResolverThunk::IsFunctionAService(void* local_thunk) const { + ServiceFullThunk function_code; + SIZE_T read; + if (!::ReadProcessMemory(process_, target_, &function_code, + sizeof(function_code), &read)) + return false; + + if (sizeof(function_code) != read) + return false; + + if (!IsAnyService(&function_code)) + return false; + + // Save the verified code. + memcpy(local_thunk, &function_code, sizeof(function_code)); + + return true; +} + +NTSTATUS ServiceResolverThunk::PerformPatch(void* local_thunk, + void* remote_thunk) { + // Patch the original code. + ServiceEntry local_service; + DCHECK_NT(GetInternalThunkSize() <= sizeof(local_service)); + if (!SetInternalThunk(&local_service, sizeof(local_service), nullptr, + interceptor_)) + return STATUS_UNSUCCESSFUL; + + // Copy the local thunk buffer to the child. + SIZE_T actual; + if (!::WriteProcessMemory(process_, remote_thunk, local_thunk, + sizeof(ServiceFullThunk), &actual)) + return STATUS_UNSUCCESSFUL; + + if (sizeof(ServiceFullThunk) != actual) + return STATUS_UNSUCCESSFUL; + + // And now change the function to intercept, on the child. + if (ntdll_base_) { + // Running a unit test. + if (!::WriteProcessMemory(process_, target_, &local_service, + sizeof(local_service), &actual)) + return STATUS_UNSUCCESSFUL; + } else { + if (!WriteProtectedChildMemory(process_, target_, &local_service, + sizeof(local_service))) + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +bool Wow64ResolverThunk::IsFunctionAService(void* local_thunk) const { + NOTREACHED_NT(); + return false; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/service_resolver_unittest.cc b/security/sandbox/chromium/sandbox/win/src/service_resolver_unittest.cc new file mode 100644 index 0000000000..63ea26dcbf --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/service_resolver_unittest.cc @@ -0,0 +1,278 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for ServiceResolverThunk. + +#include "sandbox/win/src/service_resolver.h" + +#include <stddef.h> + +#include <memory> + +#include "base/bit_cast.h" +#include "base/macros.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/resolver.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class ResolverThunkTest { + public: + virtual ~ResolverThunkTest() {} + + virtual sandbox::ServiceResolverThunk* resolver() = 0; + + // Sets the interception target to the desired address. + void set_target(void* target) { fake_target_ = target; } + + protected: + // Holds the address of the fake target. + void* fake_target_; +}; + +// This is the concrete resolver used to perform service-call type functions +// inside ntdll.dll. +template <typename T> +class ResolverThunkTestImpl : public T, public ResolverThunkTest { + public: + // The service resolver needs a child process to write to. + explicit ResolverThunkTestImpl(bool relaxed) + : T(::GetCurrentProcess(), relaxed) {} + + sandbox::ServiceResolverThunk* resolver() { return this; } + + protected: + // Overrides Resolver::Init + virtual NTSTATUS Init(const void* target_module, + const void* interceptor_module, + const char* target_name, + const char* interceptor_name, + const void* interceptor_entry_point, + void* thunk_storage, + size_t storage_bytes) { + NTSTATUS ret = STATUS_SUCCESS; + ret = T::Init(target_module, interceptor_module, target_name, + interceptor_name, interceptor_entry_point, thunk_storage, + storage_bytes); + EXPECT_EQ(STATUS_SUCCESS, ret); + + this->target_ = fake_target_; + + return ret; + } + + DISALLOW_COPY_AND_ASSIGN(ResolverThunkTestImpl); +}; + +typedef ResolverThunkTestImpl<sandbox::ServiceResolverThunk> WinXpResolverTest; + +#if !defined(_WIN64) +typedef ResolverThunkTestImpl<sandbox::Win8ResolverThunk> Win8ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64ResolverThunk> Wow64ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64W8ResolverThunk> + Wow64W8ResolverTest; +typedef ResolverThunkTestImpl<sandbox::Wow64W10ResolverThunk> + Wow64W10ResolverTest; +#endif + +const BYTE kJump32 = 0xE9; + +void CheckJump(void* source, void* target) { +#pragma pack(push) +#pragma pack(1) + struct Code { + BYTE jump; + ULONG delta; + }; +#pragma pack(pop) + +#if defined(_WIN64) + FAIL() << "Running 32-bit codepath"; +#else + Code* patched = reinterpret_cast<Code*>(source); + EXPECT_EQ(kJump32, patched->jump); + + ULONG source_addr = bit_cast<ULONG>(source); + ULONG target_addr = bit_cast<ULONG>(target); + EXPECT_EQ(target_addr + 19 - source_addr, patched->delta); +#endif +} + +NTSTATUS PatchNtdllWithResolver(const char* function, + bool relaxed, + ResolverThunkTest* thunk_test) { + HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll"); + EXPECT_TRUE(ntdll_base); + + void* target = + reinterpret_cast<void*>(::GetProcAddress(ntdll_base, function)); + EXPECT_TRUE(target); + if (!target) + return STATUS_UNSUCCESSFUL; + + BYTE service[50]; + memcpy(service, target, sizeof(service)); + + thunk_test->set_target(service); + + sandbox::ServiceResolverThunk* resolver = thunk_test->resolver(); + // Any pointer will do as an interception_entry_point + void* function_entry = resolver; + size_t thunk_size = resolver->GetThunkSize(); + std::unique_ptr<char[]> thunk(new char[thunk_size]); + size_t used; + + resolver->AllowLocalPatches(); + + NTSTATUS ret = + resolver->Setup(ntdll_base, nullptr, function, nullptr, function_entry, + thunk.get(), thunk_size, &used); + if (NT_SUCCESS(ret)) { + EXPECT_EQ(thunk_size, used); + EXPECT_NE(0, memcmp(service, target, sizeof(service))); + EXPECT_NE(kJump32, service[0]); + + if (relaxed) { + // It's already patched, let's patch again, and simulate a direct patch. + service[0] = kJump32; + ret = resolver->Setup(ntdll_base, nullptr, function, nullptr, + function_entry, thunk.get(), thunk_size, &used); + CheckJump(service, thunk.get()); + } + } + + return ret; +} + +std::unique_ptr<ResolverThunkTest> GetTestResolver(bool relaxed) { +#if defined(_WIN64) + return std::make_unique<WinXpResolverTest>(relaxed); +#else + base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); + if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { + if (os_info->version() >= base::win::Version::WIN10) + return std::make_unique<Wow64W10ResolverTest>(relaxed); + if (os_info->version() >= base::win::Version::WIN8) + return std::make_unique<Wow64W8ResolverTest>(relaxed); + return std::make_unique<Wow64ResolverTest>(relaxed); + } + + if (os_info->version() >= base::win::Version::WIN8) + return std::make_unique<Win8ResolverTest>(relaxed); + + return std::make_unique<WinXpResolverTest>(relaxed); +#endif +} + +NTSTATUS PatchNtdll(const char* function, bool relaxed) { + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(relaxed); + return PatchNtdllWithResolver(function, relaxed, thunk_test.get()); +} + +TEST(ServiceResolverTest, PatchesServices) { + NTSTATUS ret = PatchNtdll("NtClose", false); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateFile", false); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateFile, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateMutant", false); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateMutant, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtMapViewOfSection", false); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtMapViewOfSection, last error: " << ::GetLastError(); +} + +TEST(ServiceResolverTest, FailsIfNotService) { +#if !defined(_WIN64) + EXPECT_NE(STATUS_SUCCESS, PatchNtdll("RtlUlongByteSwap", false)); +#endif + + EXPECT_NE(STATUS_SUCCESS, PatchNtdll("LdrLoadDll", false)); +} + +TEST(ServiceResolverTest, PatchesPatchedServices) { +// We don't support "relaxed mode" for Win64 apps. +#if !defined(_WIN64) + NTSTATUS ret = PatchNtdll("NtClose", true); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateFile", true); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateFile, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtCreateMutant", true); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateMutant, last error: " << ::GetLastError(); + + ret = PatchNtdll("NtMapViewOfSection", true); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtMapViewOfSection, last error: " << ::GetLastError(); +#endif +} + +TEST(ServiceResolverTest, MultiplePatchedServices) { +// We don't support "relaxed mode" for Win64 apps. +#if !defined(_WIN64) + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(true); + NTSTATUS ret = PatchNtdllWithResolver("NtClose", true, thunk_test.get()); + EXPECT_EQ(STATUS_SUCCESS, ret) << "NtClose, last error: " << ::GetLastError(); + + ret = PatchNtdllWithResolver("NtCreateFile", true, thunk_test.get()); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateFile, last error: " << ::GetLastError(); + + ret = PatchNtdllWithResolver("NtCreateMutant", true, thunk_test.get()); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtCreateMutant, last error: " << ::GetLastError(); + + ret = PatchNtdllWithResolver("NtMapViewOfSection", true, thunk_test.get()); + EXPECT_EQ(STATUS_SUCCESS, ret) + << "NtMapViewOfSection, last error: " << ::GetLastError(); +#endif +} + +TEST(ServiceResolverTest, LocalPatchesAllowed) { + std::unique_ptr<ResolverThunkTest> thunk_test = GetTestResolver(true); + + HMODULE ntdll_base = ::GetModuleHandle(L"ntdll.dll"); + ASSERT_TRUE(ntdll_base); + + const char kFunctionName[] = "NtClose"; + + void* target = + reinterpret_cast<void*>(::GetProcAddress(ntdll_base, kFunctionName)); + ASSERT_TRUE(target); + + BYTE service[50]; + memcpy(service, target, sizeof(service)); + thunk_test->set_target(service); + + sandbox::ServiceResolverThunk* resolver = thunk_test->resolver(); + // Any pointer will do as an interception_entry_point + void* function_entry = resolver; + size_t thunk_size = resolver->GetThunkSize(); + std::unique_ptr<char[]> thunk(new char[thunk_size]); + size_t used; + + NTSTATUS ret = STATUS_UNSUCCESSFUL; + + // First try patching without having allowed local patches. + ret = resolver->Setup(ntdll_base, nullptr, kFunctionName, nullptr, + function_entry, thunk.get(), thunk_size, &used); + EXPECT_FALSE(NT_SUCCESS(ret)); + + // Now allow local patches and check that things work. + resolver->AllowLocalPatches(); + ret = resolver->Setup(ntdll_base, nullptr, kFunctionName, nullptr, + function_entry, thunk.get(), thunk_size, &used); + EXPECT_EQ(STATUS_SUCCESS, ret); +} + +} // namespace diff --git a/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.cc b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.cc new file mode 100644 index 0000000000..3ddea76350 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.cc @@ -0,0 +1,193 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sharedmem_ipc_client.h" + +#include <stddef.h> +#include <string.h> + +#include "base/logging.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_types.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +namespace { + +DWORD SignalObjectAndWaitWrapper(HANDLE object_to_signal, + HANDLE object_to_wait_on, + DWORD millis, + BOOL alertable) { + // Not running in a sandboxed process so can call directly. + if (!g_nt.SignalAndWaitForSingleObject) + return SignalObjectAndWait(object_to_signal, object_to_wait_on, millis, + alertable); + // Don't support alertable. + CHECK_NT(!alertable); + LARGE_INTEGER timeout; + timeout.QuadPart = millis * -10000LL; + NTSTATUS status = g_nt.SignalAndWaitForSingleObject( + object_to_signal, object_to_wait_on, alertable, + millis == INFINITE ? nullptr : &timeout); + if (!NT_SUCCESS(status)) + return WAIT_FAILED; + return status; +} + +DWORD WaitForSingleObjectWrapper(HANDLE handle, DWORD millis) { + // Not running in a sandboxed process so can call directly. + if (!g_nt.WaitForSingleObject) + return WaitForSingleObject(handle, millis); + LARGE_INTEGER timeout; + timeout.QuadPart = millis * -10000LL; + NTSTATUS status = g_nt.WaitForSingleObject( + handle, FALSE, millis == INFINITE ? nullptr : &timeout); + if (!NT_SUCCESS(status)) + return WAIT_FAILED; + return status; +} + +} // namespace + +// Get the base of the data buffer of the channel; this is where the input +// parameters get serialized. Since they get serialized directly into the +// channel we avoid one copy. +void* SharedMemIPCClient::GetBuffer() { + bool failure = false; + size_t ix = LockFreeChannel(&failure); + if (failure) + return nullptr; + return reinterpret_cast<char*>(control_) + + control_->channels[ix].channel_base; +} + +// If we need to cancel an IPC before issuing DoCall +// our client should call FreeBuffer with the same pointer +// returned by GetBuffer. +void SharedMemIPCClient::FreeBuffer(void* buffer) { + size_t num = ChannelIndexFromBuffer(buffer); + ChannelControl* channel = control_->channels; + LONG result = ::InterlockedExchange(&channel[num].state, kFreeChannel); + DCHECK_NE(kFreeChannel, static_cast<ChannelState>(result)); +} + +// The constructor simply casts the shared memory to the internal +// structures. This is a cheap step that is why this IPC object can +// and should be constructed per call. +SharedMemIPCClient::SharedMemIPCClient(void* shared_mem) + : control_(reinterpret_cast<IPCControl*>(shared_mem)) { + first_base_ = + reinterpret_cast<char*>(shared_mem) + control_->channels[0].channel_base; + // There must be at least one channel. + DCHECK(0 != control_->channels_count); +} + +// Do the IPC. At this point the channel should have already been +// filled with the serialized input parameters. +// We follow the pattern explained in the header file. +ResultCode SharedMemIPCClient::DoCall(CrossCallParams* params, + CrossCallReturn* answer) { + if (!control_->server_alive) + return SBOX_ERROR_CHANNEL_ERROR; + + size_t num = ChannelIndexFromBuffer(params->GetBuffer()); + ChannelControl* channel = control_->channels; + // Note that the IPC tag goes outside the buffer as well inside + // the buffer. This should enable the server to prioritize based on + // IPC tags without having to de-serialize the entire message. + channel[num].ipc_tag = params->GetTag(); + + // Wait for the server to service this IPC call. After kIPCWaitTimeOut1 + // we check if the server_alive mutex was abandoned which will indicate + // that the server has died. + + // While the atomic signaling and waiting is not a requirement, it + // is nice because we save a trip to kernel. + DWORD wait = SignalObjectAndWaitWrapper(channel[num].ping_event, + channel[num].pong_event, + kIPCWaitTimeOut1, false); + if (WAIT_TIMEOUT == wait) { + // The server is taking too long. Enter a loop were we check if the + // server_alive mutex has been abandoned which would signal a server crash + // or else we keep waiting for a response. + while (true) { + wait = WaitForSingleObjectWrapper(control_->server_alive, 0); + if (WAIT_TIMEOUT == wait) { + // Server seems still alive. We already signaled so here we just wait. + wait = WaitForSingleObjectWrapper(channel[num].pong_event, + kIPCWaitTimeOut1); + if (WAIT_OBJECT_0 == wait) { + // The server took a long time but responded. + break; + } else if (WAIT_TIMEOUT == wait) { + continue; + } else { + return SBOX_ERROR_CHANNEL_ERROR; + } + } else { + // The server has crashed and windows has signaled the mutex as + // abandoned. + ::InterlockedExchange(&channel[num].state, kAbandonedChannel); + control_->server_alive = 0; + return SBOX_ERROR_CHANNEL_ERROR; + } + } + } else if (WAIT_OBJECT_0 != wait) { + // Probably the server crashed before the kIPCWaitTimeOut1 occurred. + return SBOX_ERROR_CHANNEL_ERROR; + } + + // The server has returned an answer, copy it and free the channel. + memcpy_wrapper(answer, params->GetCallReturn(), sizeof(CrossCallReturn)); + + // Return the IPC state It can indicate that while the IPC has + // completed some error in the Broker has caused to not return valid + // results. + return answer->call_outcome; +} + +// Locking a channel is a simple as looping over all the channels +// looking for one that is has state = kFreeChannel and atomically +// swapping it to kBusyChannel. +// If there is no free channel, then we must back off so some other +// thread makes progress and frees a channel. To back off we sleep. +size_t SharedMemIPCClient::LockFreeChannel(bool* severe_failure) { + if (0 == control_->channels_count) { + *severe_failure = true; + return 0; + } + ChannelControl* channel = control_->channels; + do { + for (size_t ix = 0; ix != control_->channels_count; ++ix) { + if (kFreeChannel == ::InterlockedCompareExchange( + &channel[ix].state, kBusyChannel, kFreeChannel)) { + *severe_failure = false; + return ix; + } + } + // We did not find any available channel, maybe the server is dead. + DWORD wait = + WaitForSingleObjectWrapper(control_->server_alive, kIPCWaitTimeOut2); + if (WAIT_TIMEOUT != wait) { + // The server is dead and we outlive it enough to get in trouble. + *severe_failure = true; + return 0; + } + } while (true); +} + +// Find out which channel we are from the pointer returned by GetBuffer. +size_t SharedMemIPCClient::ChannelIndexFromBuffer(const void* buffer) { + ptrdiff_t d = reinterpret_cast<const char*>(buffer) - first_base_; + size_t num = d / kIPCChannelSize; + DCHECK_LT(num, control_->channels_count); + return (num); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.h b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.h new file mode 100644 index 0000000000..73f739dece --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_client.h @@ -0,0 +1,140 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ +#define SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ + +#include <stddef.h> +#include <stdint.h> + +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox.h" + +// IPC transport implementation that uses shared memory. +// This is the client side +// +// The shared memory is divided on blocks called channels, and potentially +// it can perform as many concurrent IPC calls as channels. The IPC over +// each channel is strictly synchronous for the client. +// +// Each channel as a channel control section associated with. Each control +// section has two kernel events (known as ping and pong) and a integer +// variable that maintains a state +// +// this is the state diagram of a channel: +// +// locked in service +// kFreeChannel---------->BusyChannel-------------->kAckChannel +// ^ | +// |_________________________________________________| +// answer ready +// +// The protocol is as follows: +// 1) client finds a free channel: state = kFreeChannel +// 2) does an atomic compare-and-swap, now state = BusyChannel +// 3) client writes the data into the channel buffer +// 4) client signals the ping event and waits (blocks) on the pong event +// 5) eventually the server signals the pong event +// 6) the client awakes and reads the answer from the same channel +// 7) the client updates its InOut parameters with the new data from the +// shared memory section. +// 8) the client atomically sets the state = kFreeChannel +// +// In the shared memory the layout is as follows: +// +// [ channel count ] +// [ channel control 0] +// [ channel control 1] +// [ channel control N] +// [ channel buffer 0 ] 1024 bytes +// [ channel buffer 1 ] 1024 bytes +// [ channel buffer N ] 1024 bytes +// +// By default each channel buffer is 1024 bytes +namespace sandbox { + +// the possible channel states as described above +enum ChannelState { + // channel is free + kFreeChannel = 1, + // IPC in progress client side + kBusyChannel, + // IPC in progress server side + kAckChannel, + // not used right now + kReadyChannel, + // IPC abandoned by client side + kAbandonedChannel +}; + +// The next two constants control the time outs for the IPC. +const DWORD kIPCWaitTimeOut1 = 1000; // Milliseconds. +const DWORD kIPCWaitTimeOut2 = 50; // Milliseconds. + +// the channel control structure +struct ChannelControl { + // points to be beginning of the channel buffer, where data goes + size_t channel_base; + // maintains the state from the ChannelState enumeration + volatile LONG state; + // the ping event is signaled by the client when the IPC data is ready on + // the buffer + HANDLE ping_event; + // the client waits on the pong event for the IPC answer back + HANDLE pong_event; + // the IPC unique identifier + IpcTag ipc_tag; +}; + +struct IPCControl { + // total number of channels available, some might be busy at a given time + size_t channels_count; + // handle to a shared mutex to detect when the server is dead + HANDLE server_alive; + // array of channel control structures + ChannelControl channels[1]; +}; + +// the actual shared memory IPC implementation class. This object is designed +// to be lightweight so it can be constructed on-site (at the calling place) +// wherever an IPC call is needed. +class SharedMemIPCClient { + public: + // Creates the IPC client. + // as parameter it takes the base address of the shared memory + explicit SharedMemIPCClient(void* shared_mem); + + // locks a free channel and returns the channel buffer memory base. This call + // blocks until there is a free channel + void* GetBuffer(); + + // releases the lock on the channel, for other to use. call this if you have + // called GetBuffer and you want to abort but have not called yet DoCall() + void FreeBuffer(void* buffer); + + // Performs the actual IPC call. + // params: The blob of packed input parameters. + // answer: upon IPC completion, it contains the server answer to the IPC. + // If the return value is not SBOX_ERROR_CHANNEL_ERROR, the caller has to free + // the channel. + // returns ALL_OK if the IPC mechanism successfully delivered. You still need + // to check on the answer structure to see the actual IPC result. + ResultCode DoCall(CrossCallParams* params, CrossCallReturn* answer); + + private: + // Returns the index of the first free channel. It sets 'severe_failure' + // to true if there is an unrecoverable error that does not allow to + // find a channel. + size_t LockFreeChannel(bool* severe_failure); + // Return the channel index given the address of the buffer. + size_t ChannelIndexFromBuffer(const void* buffer); + IPCControl* control_; + // point to the first channel base + char* first_base_; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SHAREDMEM_IPC_CLIENT_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.cc b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.cc new file mode 100644 index 0000000000..74e5c171e0 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.cc @@ -0,0 +1,346 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sharedmem_ipc_server.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_args.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" + +namespace { +// This handle must not be closed. +volatile HANDLE g_alive_mutex = nullptr; +} // namespace + +namespace sandbox { + +SharedMemIPCServer::ServerControl::ServerControl() {} + +SharedMemIPCServer::ServerControl::~ServerControl() {} + +SharedMemIPCServer::SharedMemIPCServer(HANDLE target_process, + DWORD target_process_id, + ThreadProvider* thread_provider, + Dispatcher* dispatcher) + : client_control_(nullptr), + thread_provider_(thread_provider), + target_process_(target_process), + target_process_id_(target_process_id), + call_dispatcher_(dispatcher) { + // We create a initially owned mutex. If the server dies unexpectedly, + // the thread that owns it will fail to release the lock and windows will + // report to the target (when it tries to acquire it) that the wait was + // abandoned. Note: We purposely leak the local handle because we want it to + // be closed by Windows itself so it is properly marked as abandoned if the + // server dies. + if (!g_alive_mutex) { + HANDLE mutex = ::CreateMutexW(nullptr, true, nullptr); + if (::InterlockedCompareExchangePointer(&g_alive_mutex, mutex, nullptr)) { + // We lost the race to create the mutex. + ::CloseHandle(mutex); + } + } +} + +SharedMemIPCServer::~SharedMemIPCServer() { + // Free the wait handles associated with the thread pool. + if (!thread_provider_->UnRegisterWaits(this)) { + // Better to leak than to crash. + return; + } + server_contexts_.clear(); + + if (client_control_) + ::UnmapViewOfFile(client_control_); +} + +bool SharedMemIPCServer::Init(void* shared_mem, + uint32_t shared_size, + uint32_t channel_size) { + // The shared memory needs to be at least as big as a channel. + if (shared_size < channel_size) { + return false; + } + // The channel size should be aligned. + if (0 != (channel_size % 32)) { + return false; + } + + // Calculate how many channels we can fit in the shared memory. + shared_size -= offsetof(IPCControl, channels); + size_t channel_count = shared_size / (sizeof(ChannelControl) + channel_size); + + // If we cannot fit even one channel we bail out. + if (0 == channel_count) { + return false; + } + // Calculate the start of the first channel. + size_t base_start = + (sizeof(ChannelControl) * channel_count) + offsetof(IPCControl, channels); + + client_control_ = reinterpret_cast<IPCControl*>(shared_mem); + client_control_->channels_count = 0; + + // This is the initialization that we do per-channel. Basically: + // 1) make two events (ping & pong) + // 2) create handles to the events for the client and the server. + // 3) initialize the channel (client_context) with the state. + // 4) initialize the server side of the channel (service_context). + // 5) call the thread provider RegisterWait to register the ping events. + for (size_t ix = 0; ix != channel_count; ++ix) { + ChannelControl* client_context = &client_control_->channels[ix]; + ServerControl* service_context = new ServerControl; + server_contexts_.push_back(base::WrapUnique(service_context)); + + if (!MakeEvents(&service_context->ping_event, &service_context->pong_event, + &client_context->ping_event, &client_context->pong_event)) { + return false; + } + + client_context->channel_base = base_start; + client_context->state = kFreeChannel; + + // Note that some of these values are available as members of this object + // but we put them again into the service_context because we will be called + // on a static method (ThreadPingEventReady). In particular, target_process_ + // is a raw handle that is not owned by this object (it's owned by the + // owner of this object), and we are storing it in multiple places. + service_context->shared_base = reinterpret_cast<char*>(shared_mem); + service_context->channel_size = channel_size; + service_context->channel = client_context; + service_context->channel_buffer = + service_context->shared_base + client_context->channel_base; + service_context->dispatcher = call_dispatcher_; + service_context->target_info.process = target_process_; + service_context->target_info.process_id = target_process_id_; + // Advance to the next channel. + base_start += channel_size; + // Register the ping event with the threadpool. + thread_provider_->RegisterWait(this, service_context->ping_event.Get(), + ThreadPingEventReady, service_context); + } + if (!::DuplicateHandle(::GetCurrentProcess(), g_alive_mutex, target_process_, + &client_control_->server_alive, + SYNCHRONIZE | EVENT_MODIFY_STATE, false, 0)) { + return false; + } + // This last setting indicates to the client all is setup. + client_control_->channels_count = channel_count; + return true; +} + +bool SharedMemIPCServer::InvokeCallback(const ServerControl* service_context, + void* ipc_buffer, + CrossCallReturn* call_result) { + // Set the default error code; + SetCallError(SBOX_ERROR_INVALID_IPC, call_result); + uint32_t output_size = 0; + // Parse, verify and copy the message. The handler operates on a copy + // of the message so the client cannot play dirty tricks by changing the + // data in the channel while the IPC is being processed. + std::unique_ptr<CrossCallParamsEx> params(CrossCallParamsEx::CreateFromBuffer( + ipc_buffer, service_context->channel_size, &output_size)); + if (!params.get()) + return false; + + IpcTag tag = params->GetTag(); + static_assert(0 == INVALID_TYPE, "incorrect type enum"); + IPCParams ipc_params = {tag}; + + void* args[kMaxIpcParams]; + if (!GetArgs(params.get(), &ipc_params, args)) + return false; + + IPCInfo ipc_info = {tag}; + ipc_info.client_info = &service_context->target_info; + Dispatcher* dispatcher = service_context->dispatcher; + DCHECK(dispatcher); + bool error = true; + Dispatcher* handler = nullptr; + + Dispatcher::CallbackGeneric callback_generic; + handler = dispatcher->OnMessageReady(&ipc_params, &callback_generic); + if (handler) { + switch (params->GetParamsCount()) { + case 0: { + // Ask the IPC dispatcher if it can service this IPC. + Dispatcher::Callback0 callback = + reinterpret_cast<Dispatcher::Callback0>(callback_generic); + if (!(handler->*callback)(&ipc_info)) + break; + error = false; + break; + } + case 1: { + Dispatcher::Callback1 callback = + reinterpret_cast<Dispatcher::Callback1>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0])) + break; + error = false; + break; + } + case 2: { + Dispatcher::Callback2 callback = + reinterpret_cast<Dispatcher::Callback2>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1])) + break; + error = false; + break; + } + case 3: { + Dispatcher::Callback3 callback = + reinterpret_cast<Dispatcher::Callback3>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2])) + break; + error = false; + break; + } + case 4: { + Dispatcher::Callback4 callback = + reinterpret_cast<Dispatcher::Callback4>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], + args[3])) + break; + error = false; + break; + } + case 5: { + Dispatcher::Callback5 callback = + reinterpret_cast<Dispatcher::Callback5>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4])) + break; + error = false; + break; + } + case 6: { + Dispatcher::Callback6 callback = + reinterpret_cast<Dispatcher::Callback6>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5])) + break; + error = false; + break; + } + case 7: { + Dispatcher::Callback7 callback = + reinterpret_cast<Dispatcher::Callback7>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6])) + break; + error = false; + break; + } + case 8: { + Dispatcher::Callback8 callback = + reinterpret_cast<Dispatcher::Callback8>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7])) + break; + error = false; + break; + } + case 9: { + Dispatcher::Callback9 callback = + reinterpret_cast<Dispatcher::Callback9>(callback_generic); + if (!(handler->*callback)(&ipc_info, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7], args[8])) + break; + error = false; + break; + } + default: { + NOTREACHED(); + break; + } + } + } + + if (error) { + if (handler) + SetCallError(SBOX_ERROR_FAILED_IPC, call_result); + } else { + memcpy(call_result, &ipc_info.return_info, sizeof(*call_result)); + SetCallSuccess(call_result); + if (params->IsInOut()) { + // Maybe the params got changed by the broker. We need to upadte the + // memory section. + memcpy(ipc_buffer, params.get(), output_size); + } + } + + ReleaseArgs(&ipc_params, args); + + return !error; +} + +// This function gets called by a thread from the thread pool when a +// ping event fires. The context is the same as passed in the RegisterWait() +// call above. +void __stdcall SharedMemIPCServer::ThreadPingEventReady(void* context, + unsigned char) { + if (!context) { + DCHECK(false); + return; + } + ServerControl* service_context = reinterpret_cast<ServerControl*>(context); + // Since the event fired, the channel *must* be busy. Change to kAckChannel + // while we service it. + LONG last_state = ::InterlockedCompareExchange( + &service_context->channel->state, kAckChannel, kBusyChannel); + if (kBusyChannel != last_state) { + DCHECK(false); + return; + } + + // Prepare the result structure. At this point we will return some result + // even if the IPC is invalid, malformed or has no handler. + CrossCallReturn call_result = {0}; + void* buffer = service_context->channel_buffer; + + InvokeCallback(service_context, buffer, &call_result); + + // Copy the answer back into the channel and signal the pong event. This + // should wake up the client so it can finish the ipc cycle. + CrossCallParams* call_params = reinterpret_cast<CrossCallParams*>(buffer); + memcpy(call_params->GetCallReturn(), &call_result, sizeof(call_result)); + ::InterlockedExchange(&service_context->channel->state, kAckChannel); + ::SetEvent(service_context->pong_event.Get()); +} + +bool SharedMemIPCServer::MakeEvents(base::win::ScopedHandle* server_ping, + base::win::ScopedHandle* server_pong, + HANDLE* client_ping, + HANDLE* client_pong) { + // Note that the IPC client has no right to delete the events. That would + // cause problems. The server *owns* the events. + const DWORD kDesiredAccess = SYNCHRONIZE | EVENT_MODIFY_STATE; + + // The events are auto reset, and start not signaled. + server_ping->Set(::CreateEventW(nullptr, false, false, nullptr)); + if (!::DuplicateHandle(::GetCurrentProcess(), server_ping->Get(), + target_process_, client_ping, kDesiredAccess, false, + 0)) { + return false; + } + + server_pong->Set(::CreateEventW(nullptr, false, false, nullptr)); + if (!::DuplicateHandle(::GetCurrentProcess(), server_pong->Get(), + target_process_, client_pong, kDesiredAccess, false, + 0)) { + return false; + } + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.h b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.h new file mode 100644 index 0000000000..fce52c6ad0 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sharedmem_ipc_server.h @@ -0,0 +1,137 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ +#define SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ + +#include <stdint.h> + +#include <list> +#include <memory> + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_params.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" + +// IPC transport implementation that uses shared memory. +// This is the server side +// +// The server side has knowledge about the layout of the shared memory +// and the state transitions. Both are explained in sharedmem_ipc_client.h +// +// As opposed to SharedMemIPClient, the Server object should be one for the +// entire lifetime of the target process. The server is in charge of creating +// the events (ping, pong) both for the client and for the target that are used +// to signal the IPC and also in charge of setting the initial state of the +// channels. +// +// When an IPC is ready, the server relies on being called by on the +// ThreadPingEventReady callback. The IPC server then retrieves the buffer, +// marshals it into a CrossCallParam object and calls the Dispatcher, who is in +// charge of fulfilling the IPC request. +namespace sandbox { + +// the shared memory implementation of the IPC server. There should be one +// of these objects per target (IPC client) process +class SharedMemIPCServer { + public: + // Creates the IPC server. + // target_process: handle to the target process. It must be suspended. It is + // unfortunate to receive a raw handle (and store it inside this object) as + // that dilutes ownership of the process, but in practice a SharedMemIPCServer + // is owned by TargetProcess, which calls this method, and owns the handle, so + // everything is safe. If that changes, we should break this dependency and + // duplicate the handle instead. + // target_process_id: process id of the target process. + // thread_provider: a thread provider object. + // dispatcher: an object that can service IPC calls. + SharedMemIPCServer(HANDLE target_process, + DWORD target_process_id, + ThreadProvider* thread_provider, + Dispatcher* dispatcher); + + ~SharedMemIPCServer(); + + // Initializes the server structures, shared memory structures and + // creates the kernels events used to signal the IPC. + bool Init(void* shared_mem, uint32_t shared_size, uint32_t channel_size); + + private: + // Allow tests to be marked DISABLED_. Note that FLAKY_ and FAILS_ prefixes + // do not work with sandbox tests. + FRIEND_TEST_ALL_PREFIXES(IPCTest, SharedMemServerTests); + // When an event fires (IPC request). A thread from the ThreadProvider + // will call this function. The context parameter should be the same as + // provided when ThreadProvider::RegisterWait was called. + static void __stdcall ThreadPingEventReady(void* context, unsigned char); + + // Makes the client and server events. This function is called once + // per channel. + bool MakeEvents(base::win::ScopedHandle* server_ping, + base::win::ScopedHandle* server_pong, + HANDLE* client_ping, + HANDLE* client_pong); + + // A copy this structure is maintained per channel. + // Note that a lot of the fields are just the same of what we have in the IPC + // object itself. It is better to have the copies since we can dispatch in the + // static method without worrying about converting back to a member function + // call or about threading issues. + struct ServerControl { + ServerControl(); + ~ServerControl(); + + // This channel server ping event. + base::win::ScopedHandle ping_event; + // This channel server pong event. + base::win::ScopedHandle pong_event; + // The size of this channel. + uint32_t channel_size; + // The pointer to the actual channel data. + char* channel_buffer; + // The pointer to the base of the shared memory. + char* shared_base; + // A pointer to this channel's client-side control structure this structure + // lives in the shared memory. + ChannelControl* channel; + // the IPC dispatcher associated with this channel. + Dispatcher* dispatcher; + // The target process information associated with this channel. + ClientInfo target_info; + }; + + // Looks for the appropriate handler for this IPC and invokes it. + static bool InvokeCallback(const ServerControl* service_context, + void* ipc_buffer, + CrossCallReturn* call_result); + + // Points to the shared memory channel control which lives at + // the start of the shared section. + IPCControl* client_control_; + + // Keeps track of the server side objects that are used to answer an IPC. + std::list<std::unique_ptr<ServerControl>> server_contexts_; + + // The thread provider provides the threads that call back into this object + // when the IPC events fire. + ThreadProvider* thread_provider_; + + // The IPC object is associated with a target process. + HANDLE target_process_; + + // The target process id associated with the IPC object. + DWORD target_process_id_; + + // The dispatcher handles 'ready' IPC calls. + Dispatcher* call_dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(SharedMemIPCServer); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SHAREDMEM_IPC_SERVER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sid.cc b/security/sandbox/chromium/sandbox/win/src/sid.cc new file mode 100644 index 0000000000..a97ca340ed --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sid.cc @@ -0,0 +1,163 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sid.h" + +#include <memory> + +#include <sddl.h> +#include <stdlib.h> + +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +DWORD WellKnownCapabilityToRid(WellKnownCapabilities capability) { + switch (capability) { + case kInternetClient: + return SECURITY_CAPABILITY_INTERNET_CLIENT; + case kInternetClientServer: + return SECURITY_CAPABILITY_INTERNET_CLIENT_SERVER; + case kPrivateNetworkClientServer: + return SECURITY_CAPABILITY_PRIVATE_NETWORK_CLIENT_SERVER; + case kPicturesLibrary: + return SECURITY_CAPABILITY_PICTURES_LIBRARY; + case kVideosLibrary: + return SECURITY_CAPABILITY_VIDEOS_LIBRARY; + case kMusicLibrary: + return SECURITY_CAPABILITY_MUSIC_LIBRARY; + case kDocumentsLibrary: + return SECURITY_CAPABILITY_DOCUMENTS_LIBRARY; + case kEnterpriseAuthentication: + return SECURITY_CAPABILITY_ENTERPRISE_AUTHENTICATION; + case kSharedUserCertificates: + return SECURITY_CAPABILITY_SHARED_USER_CERTIFICATES; + case kRemovableStorage: + return SECURITY_CAPABILITY_REMOVABLE_STORAGE; + case kAppointments: + return SECURITY_CAPABILITY_APPOINTMENTS; + case kContacts: + return SECURITY_CAPABILITY_CONTACTS; + default: + break; + } + return 0; +} + +} // namespace + +Sid::Sid() : sid_() {} + +Sid::Sid(PSID sid) : sid_() { + ::CopySid(SECURITY_MAX_SID_SIZE, sid_, sid); +} + +Sid::Sid(const SID* sid) : sid_() { + ::CopySid(SECURITY_MAX_SID_SIZE, sid_, const_cast<SID*>(sid)); +} + +Sid::Sid(WELL_KNOWN_SID_TYPE type) { + DWORD size_sid = SECURITY_MAX_SID_SIZE; + bool result = ::CreateWellKnownSid(type, nullptr, sid_, &size_sid); + DCHECK(result); + (void)result; +} + +Sid Sid::FromKnownCapability(WellKnownCapabilities capability) { + DWORD capability_rid = WellKnownCapabilityToRid(capability); + if (!capability_rid) + return Sid(); + SID_IDENTIFIER_AUTHORITY capability_authority = { + SECURITY_APP_PACKAGE_AUTHORITY}; + DWORD sub_authorities[] = {SECURITY_CAPABILITY_BASE_RID, capability_rid}; + return FromSubAuthorities(&capability_authority, 2, sub_authorities); +} + +Sid Sid::FromNamedCapability(const wchar_t* capability_name) { + RtlDeriveCapabilitySidsFromNameFunction derive_capability_sids = nullptr; + ResolveNTFunctionPtr("RtlDeriveCapabilitySidsFromName", + &derive_capability_sids); + RtlInitUnicodeStringFunction init_unicode_string = nullptr; + ResolveNTFunctionPtr("RtlInitUnicodeString", &init_unicode_string); + + if (!derive_capability_sids || !init_unicode_string) + return Sid(); + + if (!capability_name || ::wcslen(capability_name) == 0) + return Sid(); + + UNICODE_STRING name = {}; + init_unicode_string(&name, capability_name); + Sid capability_sid; + Sid group_sid; + + NTSTATUS status = + derive_capability_sids(&name, group_sid.sid_, capability_sid.sid_); + if (!NT_SUCCESS(status)) + return Sid(); + + return capability_sid; +} + +Sid Sid::FromSddlString(const wchar_t* sddl_sid) { + PSID converted_sid; + if (!::ConvertStringSidToSid(sddl_sid, &converted_sid)) + return Sid(); + + return Sid(converted_sid); +} + +Sid Sid::FromSubAuthorities(PSID_IDENTIFIER_AUTHORITY identifier_authority, + BYTE sub_authority_count, + PDWORD sub_authorities) { + Sid sid; + if (!::InitializeSid(sid.sid_, identifier_authority, sub_authority_count)) + return Sid(); + + for (DWORD index = 0; index < sub_authority_count; ++index) { + PDWORD sub_authority = GetSidSubAuthority(sid.sid_, index); + *sub_authority = sub_authorities[index]; + } + return sid; +} + +Sid Sid::AllRestrictedApplicationPackages() { + SID_IDENTIFIER_AUTHORITY package_authority = {SECURITY_APP_PACKAGE_AUTHORITY}; + DWORD sub_authorities[] = {SECURITY_APP_PACKAGE_BASE_RID, + SECURITY_BUILTIN_PACKAGE_ANY_RESTRICTED_PACKAGE}; + return FromSubAuthorities(&package_authority, 2, sub_authorities); +} + +Sid Sid::GenerateRandomSid() { + SID_IDENTIFIER_AUTHORITY package_authority = {SECURITY_NULL_SID_AUTHORITY}; + DWORD sub_authorities[4] = {}; + base::RandBytes(&sub_authorities, sizeof(sub_authorities)); + return FromSubAuthorities(&package_authority, _countof(sub_authorities), + sub_authorities); +} + +PSID Sid::GetPSID() const { + return const_cast<BYTE*>(sid_); +} + +bool Sid::IsValid() const { + return !!::IsValidSid(GetPSID()); +} + +// Converts the SID to an SDDL format string. +bool Sid::ToSddlString(std::wstring* sddl_string) const { + LPWSTR sid = nullptr; + if (!::ConvertSidToStringSid(GetPSID(), &sid)) + return false; + std::unique_ptr<void, LocalFreeDeleter> sid_ptr(sid); + *sddl_string = sid; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sid.h b/security/sandbox/chromium/sandbox/win/src/sid.h new file mode 100644 index 0000000000..745f471054 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sid.h @@ -0,0 +1,74 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SID_H_ +#define SANDBOX_SRC_SID_H_ + +#include <windows.h> + +#include <string> + +namespace sandbox { + +// Known capabilities defined in Windows 8. +enum WellKnownCapabilities { + kInternetClient, + kInternetClientServer, + kPrivateNetworkClientServer, + kPicturesLibrary, + kVideosLibrary, + kMusicLibrary, + kDocumentsLibrary, + kEnterpriseAuthentication, + kSharedUserCertificates, + kRemovableStorage, + kAppointments, + kContacts, + kMaxWellKnownCapability +}; + +// This class is used to hold and generate SIDS. +class Sid { + public: + // As PSID is just a void* make it explicit. + explicit Sid(PSID sid); + // Constructors initializing the object with the SID passed. + // This is a converting constructor. It is not explicit. + Sid(const SID* sid); + Sid(WELL_KNOWN_SID_TYPE type); + + // Create a Sid from an AppContainer capability name. The name can be + // completely arbitrary. + static Sid FromNamedCapability(const wchar_t* capability_name); + // Create a Sid from a known capability enumeration value. The Sids + // match with the list defined in Windows 8. + static Sid FromKnownCapability(WellKnownCapabilities capability); + // Create a Sid from a SDDL format string, such as S-1-1-0. + static Sid FromSddlString(const wchar_t* sddl_sid); + // Create a Sid from a set of sub authorities. + static Sid FromSubAuthorities(PSID_IDENTIFIER_AUTHORITY identifier_authority, + BYTE sub_authority_count, + PDWORD sub_authorities); + // Create the restricted all application packages sid. + static Sid AllRestrictedApplicationPackages(); + // Generate a random SID value. + static Sid GenerateRandomSid(); + + // Returns sid_. + PSID GetPSID() const; + + // Gets whether the sid is valid. + bool IsValid() const; + + // Converts the SID to a SDDL format string. + bool ToSddlString(std::wstring* sddl_string) const; + + private: + Sid(); + BYTE sid_[SECURITY_MAX_SID_SIZE]; +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SID_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sid_unittest.cc b/security/sandbox/chromium/sandbox/win/src/sid_unittest.cc new file mode 100644 index 0000000000..81186cd0be --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sid_unittest.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains unit tests for the sid class. + +#include "sandbox/win/src/sid.h" + +#include <sddl.h> + +#include "base/win/atl.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +bool EqualSid(const Sid& sid, const ATL::CSid& compare_sid) { + if (!sid.IsValid()) + return false; + return !!::EqualSid(sid.GetPSID(), const_cast<SID*>(compare_sid.GetPSID())); +} + +bool EqualSid(const Sid& sid, const wchar_t* sddl_sid) { + PSID compare_sid; + if (!sid.IsValid()) + return false; + if (!::ConvertStringSidToSid(sddl_sid, &compare_sid)) + return false; + bool equal = !!::EqualSid(sid.GetPSID(), compare_sid); + ::LocalFree(compare_sid); + return equal; +} + +struct KnownCapabilityTestEntry { + WellKnownCapabilities capability; + const wchar_t* sddl_sid; +}; + +struct NamedCapabilityTestEntry { + const wchar_t* capability_name; + const wchar_t* sddl_sid; +}; + +} // namespace + +// Tests the creation of a Sid. +TEST(SidTest, Constructors) { + ATL::CSid sid_world = ATL::Sids::World(); + PSID sid_world_pointer = const_cast<SID*>(sid_world.GetPSID()); + + // Check the SID* constructor. + Sid sid_sid_star(sid_world_pointer); + ASSERT_TRUE(EqualSid(sid_sid_star, sid_world)); + + // Check the copy constructor. + Sid sid_copy(sid_sid_star); + ASSERT_TRUE(EqualSid(sid_copy, sid_world)); + + Sid sid_sddl = Sid::FromSddlString(L"S-1-1-0"); + ASSERT_TRUE(sid_sddl.IsValid()); + ASSERT_TRUE(EqualSid(sid_sddl, sid_world)); + + Sid sid_sddl_invalid = Sid::FromSddlString(L"X-1-1-0"); + ASSERT_FALSE(sid_sddl_invalid.IsValid()); + + Sid sid_sddl_empty = Sid::FromSddlString(L""); + ASSERT_FALSE(sid_sddl_empty.IsValid()); + + // Note that the WELL_KNOWN_SID_TYPE constructor is tested in the GetPSID + // test. AppContainer related constructors are tested in AppContainer. +} + +// Tests the method GetPSID +TEST(SidTest, GetPSID) { + // Check for non-null result; + ASSERT_NE(nullptr, Sid(::WinLocalSid).GetPSID()); + ASSERT_NE(nullptr, Sid(::WinCreatorOwnerSid).GetPSID()); + ASSERT_NE(nullptr, Sid(::WinBatchSid).GetPSID()); + + ASSERT_TRUE(EqualSid(Sid(::WinNullSid), ATL::Sids::Null())); + + ASSERT_TRUE(EqualSid(Sid(::WinWorldSid), ATL::Sids::World())); + + ASSERT_TRUE(EqualSid(Sid(::WinDialupSid), ATL::Sids::Dialup())); + + ASSERT_TRUE(EqualSid(Sid(::WinNetworkSid), ATL::Sids::Network())); + + ASSERT_TRUE( + EqualSid(Sid(::WinBuiltinAdministratorsSid), ATL::Sids::Admins())); + + ASSERT_TRUE(EqualSid(Sid(::WinBuiltinUsersSid), ATL::Sids::Users())); + + ASSERT_TRUE(EqualSid(Sid(::WinBuiltinGuestsSid), ATL::Sids::Guests())); + + ASSERT_TRUE(EqualSid(Sid(::WinProxySid), ATL::Sids::Proxy())); +} + +TEST(SidTest, KnownCapability) { + if (base::win::GetVersion() < base::win::Version::WIN8) + return; + + Sid sid_invalid_well_known = + Sid::FromKnownCapability(kMaxWellKnownCapability); + EXPECT_FALSE(sid_invalid_well_known.IsValid()); + + const KnownCapabilityTestEntry capabilities[] = { + {kInternetClient, L"S-1-15-3-1"}, + {kInternetClientServer, L"S-1-15-3-2"}, + {kPrivateNetworkClientServer, L"S-1-15-3-3"}, + {kPicturesLibrary, L"S-1-15-3-4"}, + {kVideosLibrary, L"S-1-15-3-5"}, + {kMusicLibrary, L"S-1-15-3-6"}, + {kDocumentsLibrary, L"S-1-15-3-7"}, + {kEnterpriseAuthentication, L"S-1-15-3-8"}, + {kSharedUserCertificates, L"S-1-15-3-9"}, + {kRemovableStorage, L"S-1-15-3-10"}, + {kAppointments, L"S-1-15-3-11"}, + {kContacts, L"S-1-15-3-12"}, + }; + + for (auto capability : capabilities) { + EXPECT_TRUE(EqualSid(Sid::FromKnownCapability(capability.capability), + capability.sddl_sid)) + << "Known Capability: " << capability.sddl_sid; + } +} + +TEST(SidTest, NamedCapability) { + if (base::win::GetVersion() < base::win::Version::WIN10) + return; + + Sid sid_nullptr = Sid::FromNamedCapability(nullptr); + EXPECT_FALSE(sid_nullptr.IsValid()); + + Sid sid_empty = Sid::FromNamedCapability(L""); + EXPECT_FALSE(sid_empty.IsValid()); + + const NamedCapabilityTestEntry capabilities[] = { + {L"internetClient", L"S-1-15-3-1"}, + {L"internetClientServer", L"S-1-15-3-2"}, + {L"registryRead", + L"S-1-15-3-1024-1065365936-1281604716-3511738428-" + "1654721687-432734479-3232135806-4053264122-3456934681"}, + {L"lpacCryptoServices", + L"S-1-15-3-1024-3203351429-2120443784-2872670797-" + "1918958302-2829055647-4275794519-765664414-2751773334"}, + {L"enterpriseAuthentication", L"S-1-15-3-8"}, + {L"privateNetworkClientServer", L"S-1-15-3-3"}}; + + for (auto capability : capabilities) { + EXPECT_TRUE(EqualSid(Sid::FromNamedCapability(capability.capability_name), + capability.sddl_sid)) + << "Named Capability: " << capability.sddl_sid; + } +} + +TEST(SidTest, Sddl) { + Sid sid_sddl = Sid::FromSddlString(L"S-1-1-0"); + ASSERT_TRUE(sid_sddl.IsValid()); + std::wstring sddl_str; + ASSERT_TRUE(sid_sddl.ToSddlString(&sddl_str)); + ASSERT_EQ(L"S-1-1-0", sddl_str); +} + +TEST(SidTest, SubAuthorities) { + DWORD world_subauthorities[] = {0}; + SID_IDENTIFIER_AUTHORITY world_authority = {SECURITY_WORLD_SID_AUTHORITY}; + Sid sid_world = + Sid::FromSubAuthorities(&world_authority, 1, world_subauthorities); + ASSERT_TRUE(EqualSid(sid_world, ATL::Sids::World())); + ASSERT_TRUE(Sid::FromSubAuthorities(&world_authority, 0, nullptr).IsValid()); + + DWORD admin_subauthorities[] = {32, 544}; + SID_IDENTIFIER_AUTHORITY nt_authority = {SECURITY_NT_AUTHORITY}; + Sid sid_admin = + Sid::FromSubAuthorities(&nt_authority, 2, admin_subauthorities); + ASSERT_TRUE(EqualSid(sid_admin, ATL::Sids::Admins())); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.cc new file mode 100644 index 0000000000..49e7cbe946 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.cc @@ -0,0 +1,68 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/signed_dispatcher.h" + +#include <stdint.h> + +#include <string> + +#include "base/strings/string_util.h" +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/signed_interception.h" +#include "sandbox/win/src/signed_policy.h" + +namespace sandbox { + +SignedDispatcher::SignedDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::NTCREATESECTION, {VOIDPTR_TYPE}}, + reinterpret_cast<CallbackGeneric>(&SignedDispatcher::CreateSection)}; + + ipc_calls_.push_back(create_params); +} + +bool SignedDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (service == IpcTag::NTCREATESECTION) + return INTERCEPT_NT(manager, NtCreateSection, CREATE_SECTION_ID, 32); + return false; +} + +bool SignedDispatcher::CreateSection(IPCInfo* ipc, HANDLE file_handle) { + // Duplicate input handle from target to broker. + HANDLE local_file_handle = nullptr; + if (!::DuplicateHandle((*ipc->client_info).process, file_handle, + ::GetCurrentProcess(), &local_file_handle, + FILE_MAP_EXECUTE, false, 0)) { + return false; + } + + base::win::ScopedHandle local_handle(local_file_handle); + std::wstring path; + if (!GetPathFromHandle(local_handle.Get(), &path)) + return false; + const wchar_t* module_name = path.c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(module_name); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::NTCREATESECTION, params.GetBase()); + + // Return operation status on the IPC. + HANDLE section_handle = nullptr; + ipc->return_info.nt_status = SignedPolicy::CreateSectionAction( + result, *ipc->client_info, local_handle, §ion_handle); + ipc->return_info.handle = section_handle; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.h new file mode 100644 index 0000000000..38435481f6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_dispatcher.h @@ -0,0 +1,37 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SIGNED_DISPATCHER_H_ +#define SANDBOX_WIN_SRC_SIGNED_DISPATCHER_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles signed-binary related IPC calls. +class SignedDispatcher : public Dispatcher { + public: + explicit SignedDispatcher(PolicyBase* policy_base); + ~SignedDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to CreateSection in the target. + bool CreateSection(IPCInfo* ipc, HANDLE file_handle); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(SignedDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SIGNED_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/signed_interception.cc b/security/sandbox/chromium/sandbox/win/src/signed_interception.cc new file mode 100644 index 0000000000..4f4310d605 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_interception.cc @@ -0,0 +1,97 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/signed_interception.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +NTSTATUS WINAPI +TargetNtCreateSection(NtCreateSectionFunction orig_CreateSection, + PHANDLE section_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PLARGE_INTEGER maximum_size, + ULONG section_page_protection, + ULONG allocation_attributes, + HANDLE file_handle) { + do { + // The section only needs to have SECTION_MAP_EXECUTE, but the permissions + // vary depending on the OS. Windows 1903 and higher requests (SECTION_QUERY + // | SECTION_MAP_READ | SECTION_MAP_EXECUTE) while previous OS versions also + // request SECTION_MAP_WRITE. Just check for EXECUTE. + if (!(desired_access & SECTION_MAP_EXECUTE)) + break; + if (object_attributes) + break; + if (maximum_size) + break; + if (section_page_protection != PAGE_EXECUTE) + break; + if (allocation_attributes != SEC_IMAGE) + break; + + mozilla::sandboxing::LogBlocked("NtCreateSection"); + + // IPC must be fully started. + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + std::unique_ptr<wchar_t, NtAllocDeleter> path; + + if (!NtGetPathFromHandle(file_handle, &path)) + break; + + const wchar_t* const_name = path.get(); + + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(const_name); + + // Check if this will be sent to the broker. + if (!QueryBroker(IpcTag::NTCREATESECTION, params.GetBase())) + break; + + if (!ValidParameter(section_handle, sizeof(HANDLE), WRITE)) + break; + + CrossCallReturn answer = {0}; + answer.nt_status = STATUS_INVALID_IMAGE_HASH; + SharedMemIPCClient ipc(memory); + ResultCode code = + CrossCall(ipc, IpcTag::NTCREATESECTION, file_handle, &answer); + + if (code != SBOX_ALL_OK) + break; + + if (!NT_SUCCESS(answer.nt_status)) + break; + + __try { + *section_handle = answer.handle; + mozilla::sandboxing::LogAllowed("NtCreateSection"); + return answer.nt_status; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + } while (false); + + // Fall back to the original API in all failure cases. + return orig_CreateSection(section_handle, desired_access, object_attributes, + maximum_size, section_page_protection, + allocation_attributes, file_handle); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/signed_interception.h b/security/sandbox/chromium/sandbox/win/src/signed_interception.h new file mode 100644 index 0000000000..a50ec38222 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_interception.h @@ -0,0 +1,30 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SIGNED_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_SIGNED_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interceptor for NtCreateSection +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateSection(NtCreateSectionFunction orig_CreateSection, + PHANDLE section_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + PLARGE_INTEGER maximum_size, + ULONG section_page_protection, + ULONG allocation_attributes, + HANDLE file_handle); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SIGNED_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/signed_policy.cc b/security/sandbox/chromium/sandbox/win/src/signed_policy.cc new file mode 100644 index 0000000000..ccf11defe7 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_policy.cc @@ -0,0 +1,102 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/signed_policy.h" + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/win_utils.h" + +namespace { + +bool IsValidNtPath(const base::FilePath& name) { + UNICODE_STRING uni_name; + OBJECT_ATTRIBUTES obj_attr; + sandbox::InitObjectAttribs(name.value(), OBJ_CASE_INSENSITIVE, nullptr, + &obj_attr, &uni_name, nullptr); + + NtQueryAttributesFileFunction NtQueryAttributesFile = nullptr; + ResolveNTFunctionPtr("NtQueryAttributesFile", &NtQueryAttributesFile); + FILE_BASIC_INFORMATION file_info; + return NtQueryAttributesFile && + NT_SUCCESS(NtQueryAttributesFile(&obj_attr, &file_info)); +} + +} // namespace + +namespace sandbox { + +bool SignedPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + // Only support one semantic. + if (TargetPolicy::SIGNED_ALLOW_LOAD != semantics) { + return false; + } + + base::FilePath file_path(name); + base::FilePath nt_filename; + std::wstring nt_path_name; + if (GetNtPathFromWin32Path(file_path.DirName().value().c_str(), + &nt_path_name)) { + base::FilePath nt_path(nt_path_name); + nt_filename = nt_path.Append(file_path.BaseName()); + } else if (IsValidNtPath(file_path)) { + nt_filename = std::move(file_path); + } else { + return false; + } + + // Create a rule to ASK_BROKER if name matches. + PolicyRule signed_policy(ASK_BROKER); + if (!signed_policy.AddStringMatch( + IF, NameBased::NAME, nt_filename.value().c_str(), CASE_INSENSITIVE)) { + return false; + } + if (!policy->AddRule(IpcTag::NTCREATESECTION, &signed_policy)) { + return false; + } + + return true; +} + +NTSTATUS SignedPolicy::CreateSectionAction( + EvalResult eval_result, + const ClientInfo& client_info, + const base::win::ScopedHandle& local_file_handle, + HANDLE* target_section_handle) { + NtCreateSectionFunction NtCreateSection = nullptr; + ResolveNTFunctionPtr("NtCreateSection", &NtCreateSection); + + // The only action supported is ASK_BROKER which means create the requested + // section as specified. + if (ASK_BROKER != eval_result) + return false; + + HANDLE local_section_handle = nullptr; + NTSTATUS status = NtCreateSection(&local_section_handle, + SECTION_QUERY | SECTION_MAP_WRITE | + SECTION_MAP_READ | SECTION_MAP_EXECUTE, + nullptr, 0, PAGE_EXECUTE, SEC_IMAGE, + local_file_handle.Get()); + if (!local_section_handle) + return status; + + // Duplicate section handle back to the target. + if (!::DuplicateHandle(::GetCurrentProcess(), local_section_handle, + client_info.process, target_section_handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/signed_policy.h b/security/sandbox/chromium/sandbox/win/src/signed_policy.h new file mode 100644 index 0000000000..d22af4d4dc --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/signed_policy.h @@ -0,0 +1,39 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SIGNED_POLICY_H_ +#define SANDBOX_WIN_SRC_SIGNED_POLICY_H_ + +#include <stdint.h> + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to signed policy +class SignedPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Performs the desired policy action on a request. + // client_info is the target process that is making the request and + // eval_result is the desired policy action to accomplish. + static NTSTATUS CreateSectionAction( + EvalResult eval_result, + const ClientInfo& client_info, + const base::win::ScopedHandle& local_file_handle, + HANDLE* section_handle); +}; + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SIGNED_POLICY_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.cc new file mode 100644 index 0000000000..d728e1d831 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sync_dispatcher.h" + +#include <stdint.h> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/interceptors.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_broker.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/sync_policy.h" + +namespace sandbox { + +SyncDispatcher::SyncDispatcher(PolicyBase* policy_base) + : policy_base_(policy_base) { + static const IPCCall create_params = { + {IpcTag::CREATEEVENT, {WCHAR_TYPE, UINT32_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&SyncDispatcher::CreateEvent)}; + + static const IPCCall open_params = { + {IpcTag::OPENEVENT, {WCHAR_TYPE, UINT32_TYPE}}, + reinterpret_cast<CallbackGeneric>(&SyncDispatcher::OpenEvent)}; + + ipc_calls_.push_back(create_params); + ipc_calls_.push_back(open_params); +} + +bool SyncDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (service == IpcTag::CREATEEVENT) { + return INTERCEPT_NT(manager, NtCreateEvent, CREATE_EVENT_ID, 24); + } + return (service == IpcTag::OPENEVENT) && + INTERCEPT_NT(manager, NtOpenEvent, OPEN_EVENT_ID, 16); +} + +bool SyncDispatcher::CreateEvent(IPCInfo* ipc, + std::wstring* name, + uint32_t event_type, + uint32_t initial_state) { + const wchar_t* event_name = name->c_str(); + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(event_name); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::CREATEEVENT, params.GetBase()); + HANDLE handle = nullptr; + // Return operation status on the IPC. + ipc->return_info.nt_status = SyncPolicy::CreateEventAction( + result, *ipc->client_info, *name, event_type, initial_state, &handle); + ipc->return_info.handle = handle; + return true; +} + +bool SyncDispatcher::OpenEvent(IPCInfo* ipc, + std::wstring* name, + uint32_t desired_access) { + const wchar_t* event_name = name->c_str(); + + CountedParameterSet<OpenEventParams> params; + params[OpenEventParams::NAME] = ParamPickerMake(event_name); + params[OpenEventParams::ACCESS] = ParamPickerMake(desired_access); + + EvalResult result = + policy_base_->EvalPolicy(IpcTag::OPENEVENT, params.GetBase()); + HANDLE handle = nullptr; + // Return operation status on the IPC. + ipc->return_info.nt_status = SyncPolicy::OpenEventAction( + result, *ipc->client_info, *name, desired_access, &handle); + ipc->return_info.handle = handle; + return true; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.h new file mode 100644 index 0000000000..d4bc025a0b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_dispatcher.h @@ -0,0 +1,44 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SYNC_DISPATCHER_H_ +#define SANDBOX_SRC_SYNC_DISPATCHER_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// This class handles sync-related IPC calls. +class SyncDispatcher : public Dispatcher { + public: + explicit SyncDispatcher(PolicyBase* policy_base); + ~SyncDispatcher() override {} + + // Dispatcher interface. + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Processes IPC requests coming from calls to CreateEvent in the target. + bool CreateEvent(IPCInfo* ipc, + std::wstring* name, + uint32_t event_type, + uint32_t initial_state); + + // Processes IPC requests coming from calls to OpenEvent in the target. + bool OpenEvent(IPCInfo* ipc, std::wstring* name, uint32_t desired_access); + + PolicyBase* policy_base_; + DISALLOW_COPY_AND_ASSIGN(SyncDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SYNC_DISPATCHER_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sync_interception.cc b/security/sandbox/chromium/sandbox/win/src/sync_interception.cc new file mode 100644 index 0000000000..9ec680e2fd --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_interception.cc @@ -0,0 +1,177 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sync_interception.h" + +#include <stdint.h> + +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/policy_target.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" +#include "sandbox/win/src/target_services.h" +#include "mozilla/sandboxing/sandboxLogging.h" + +namespace sandbox { + +ResultCode ProxyCreateEvent(LPCWSTR name, + uint32_t initial_state, + EVENT_TYPE event_type, + void* ipc_memory, + CrossCallReturn* answer) { + CountedParameterSet<NameBased> params; + params[NameBased::NAME] = ParamPickerMake(name); + + if (!QueryBroker(IpcTag::CREATEEVENT, params.GetBase())) + return SBOX_ERROR_GENERIC; + + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = CrossCall(ipc, IpcTag::CREATEEVENT, name, event_type, + initial_state, answer); + return code; +} + +ResultCode ProxyOpenEvent(LPCWSTR name, + uint32_t desired_access, + void* ipc_memory, + CrossCallReturn* answer) { + CountedParameterSet<OpenEventParams> params; + params[OpenEventParams::NAME] = ParamPickerMake(name); + params[OpenEventParams::ACCESS] = ParamPickerMake(desired_access); + + if (!QueryBroker(IpcTag::OPENEVENT, params.GetBase())) + return SBOX_ERROR_GENERIC; + + SharedMemIPCClient ipc(ipc_memory); + ResultCode code = + CrossCall(ipc, IpcTag::OPENEVENT, name, desired_access, answer); + + return code; +} + +NTSTATUS WINAPI TargetNtCreateEvent(NtCreateEventFunction orig_CreateEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state) { + NTSTATUS status = + orig_CreateEvent(event_handle, desired_access, object_attributes, + event_type, initial_state); + if (status != STATUS_ACCESS_DENIED || !object_attributes) + return status; + + mozilla::sandboxing::LogBlocked("NtCreatEvent", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(event_handle, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + OBJECT_ATTRIBUTES object_attribs_copy = *object_attributes; + // The RootDirectory points to BaseNamedObjects. We can ignore it. + object_attribs_copy.RootDirectory = nullptr; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(&object_attribs_copy, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + CrossCallReturn answer = {0}; + answer.nt_status = status; + ResultCode code = ProxyCreateEvent(name.get(), initial_state, event_type, + memory, &answer); + + if (code != SBOX_ALL_OK) { + status = answer.nt_status; + break; + } + __try { + *event_handle = answer.handle; + status = STATUS_SUCCESS; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtCreateEvent", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +NTSTATUS WINAPI TargetNtOpenEvent(NtOpenEventFunction orig_OpenEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes) { + NTSTATUS status = + orig_OpenEvent(event_handle, desired_access, object_attributes); + if (status != STATUS_ACCESS_DENIED || !object_attributes) + return status; + + mozilla::sandboxing::LogBlocked("NtOpenEvent", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + + // We don't trust that the IPC can work this early. + if (!SandboxFactory::GetTargetServices()->GetState()->InitCalled()) + return status; + + do { + if (!ValidParameter(event_handle, sizeof(HANDLE), WRITE)) + break; + + void* memory = GetGlobalIPCMemory(); + if (!memory) + break; + + OBJECT_ATTRIBUTES object_attribs_copy = *object_attributes; + // The RootDirectory points to BaseNamedObjects. We can ignore it. + object_attribs_copy.RootDirectory = nullptr; + + std::unique_ptr<wchar_t, NtAllocDeleter> name; + uint32_t attributes = 0; + NTSTATUS ret = + AllocAndCopyName(&object_attribs_copy, &name, &attributes, nullptr); + if (!NT_SUCCESS(ret) || !name) + break; + + CrossCallReturn answer = {0}; + answer.nt_status = status; + ResultCode code = + ProxyOpenEvent(name.get(), desired_access, memory, &answer); + + if (code != SBOX_ALL_OK) { + status = answer.nt_status; + break; + } + __try { + *event_handle = answer.handle; + status = STATUS_SUCCESS; + } __except (EXCEPTION_EXECUTE_HANDLER) { + break; + } + mozilla::sandboxing::LogAllowed("NtOpenEvent", + object_attributes->ObjectName->Buffer, + object_attributes->ObjectName->Length); + } while (false); + + return status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sync_interception.h b/security/sandbox/chromium/sandbox/win/src/sync_interception.h new file mode 100644 index 0000000000..c21e7ee7b2 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_interception.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SYNC_INTERCEPTION_H_ +#define SANDBOX_WIN_SRC_SYNC_INTERCEPTION_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +typedef NTSTATUS(WINAPI* NtCreateEventFunction)( + PHANDLE EventHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + EVENT_TYPE EventType, + BOOLEAN InitialState); + +typedef NTSTATUS(WINAPI* NtOpenEventFunction)( + PHANDLE EventHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes); + +// Interceptors for NtCreateEvent/NtOpenEvent +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtCreateEvent(NtCreateEventFunction orig_CreateEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + EVENT_TYPE event_type, + BOOLEAN initial_state); + +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtOpenEvent(NtOpenEventFunction orig_OpenEvent, + PHANDLE event_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SYNC_INTERCEPTION_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/sync_policy.cc b/security/sandbox/chromium/sandbox/win/src/sync_policy.cc new file mode 100644 index 0000000000..cdc34dd241 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_policy.cc @@ -0,0 +1,243 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sync_policy.h" + +#include <stdint.h> + +#include <string> + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_engine_opcodes.h" +#include "sandbox/win/src/policy_params.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sandbox_utils.h" +#include "sandbox/win/src/sync_interception.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +// Provides functionality to resolve a symbolic link within the object +// directory passed in. +NTSTATUS ResolveSymbolicLink(const std::wstring& directory_name, + const std::wstring& name, + std::wstring* target) { + NtOpenDirectoryObjectFunction NtOpenDirectoryObject = nullptr; + ResolveNTFunctionPtr("NtOpenDirectoryObject", &NtOpenDirectoryObject); + + NtQuerySymbolicLinkObjectFunction NtQuerySymbolicLinkObject = nullptr; + ResolveNTFunctionPtr("NtQuerySymbolicLinkObject", &NtQuerySymbolicLinkObject); + + NtOpenSymbolicLinkObjectFunction NtOpenSymbolicLinkObject = nullptr; + ResolveNTFunctionPtr("NtOpenSymbolicLinkObject", &NtOpenSymbolicLinkObject); + + NtCloseFunction NtClose = nullptr; + ResolveNTFunctionPtr("NtClose", &NtClose); + + OBJECT_ATTRIBUTES symbolic_link_directory_attributes = {}; + UNICODE_STRING symbolic_link_directory_string = {}; + InitObjectAttribs(directory_name, OBJ_CASE_INSENSITIVE, nullptr, + &symbolic_link_directory_attributes, + &symbolic_link_directory_string, nullptr); + + HANDLE symbolic_link_directory = nullptr; + NTSTATUS status = + NtOpenDirectoryObject(&symbolic_link_directory, DIRECTORY_QUERY, + &symbolic_link_directory_attributes); + if (!NT_SUCCESS(status)) + return status; + + OBJECT_ATTRIBUTES symbolic_link_attributes = {}; + UNICODE_STRING name_string = {}; + InitObjectAttribs(name, OBJ_CASE_INSENSITIVE, symbolic_link_directory, + &symbolic_link_attributes, &name_string, nullptr); + + HANDLE symbolic_link = nullptr; + status = NtOpenSymbolicLinkObject(&symbolic_link, GENERIC_READ, + &symbolic_link_attributes); + CHECK(NT_SUCCESS(NtClose(symbolic_link_directory))); + if (!NT_SUCCESS(status)) + return status; + + UNICODE_STRING target_path = {}; + unsigned long target_length = 0; + status = + NtQuerySymbolicLinkObject(symbolic_link, &target_path, &target_length); + if (status != STATUS_BUFFER_TOO_SMALL) { + CHECK(NT_SUCCESS(NtClose(symbolic_link))); + return status; + } + + target_path.Length = 0; + target_path.MaximumLength = static_cast<USHORT>(target_length); + target_path.Buffer = new wchar_t[target_path.MaximumLength + 1]; + status = + NtQuerySymbolicLinkObject(symbolic_link, &target_path, &target_length); + if (NT_SUCCESS(status)) + target->assign(target_path.Buffer, target_length); + + CHECK(NT_SUCCESS(NtClose(symbolic_link))); + delete[] target_path.Buffer; + return status; +} + +NTSTATUS GetBaseNamedObjectsDirectory(HANDLE* directory) { + static HANDLE base_named_objects_handle = nullptr; + if (base_named_objects_handle) { + *directory = base_named_objects_handle; + return STATUS_SUCCESS; + } + + NtOpenDirectoryObjectFunction NtOpenDirectoryObject = nullptr; + ResolveNTFunctionPtr("NtOpenDirectoryObject", &NtOpenDirectoryObject); + + DWORD session_id = 0; + ProcessIdToSessionId(::GetCurrentProcessId(), &session_id); + + std::wstring base_named_objects_path; + + NTSTATUS status = ResolveSymbolicLink(L"\\Sessions\\BNOLINKS", + base::StringPrintf(L"%d", session_id), + &base_named_objects_path); + if (!NT_SUCCESS(status)) { + DLOG(ERROR) << "Failed to resolve BaseNamedObjects path. Error: " << status; + return status; + } + + UNICODE_STRING directory_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(base_named_objects_path, OBJ_CASE_INSENSITIVE, nullptr, + &object_attributes, &directory_name, nullptr); + status = NtOpenDirectoryObject(&base_named_objects_handle, + DIRECTORY_ALL_ACCESS, &object_attributes); + if (NT_SUCCESS(status)) + *directory = base_named_objects_handle; + return status; +} + +bool SyncPolicy::GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy) { + std::wstring mod_name(name); + if (mod_name.empty()) { + return false; + } + + if (TargetPolicy::EVENTS_ALLOW_ANY != semantics && + TargetPolicy::EVENTS_ALLOW_READONLY != semantics) { + // Other flags are not valid for sync policy yet. + NOTREACHED(); + return false; + } + + // Add the open rule. + EvalResult result = ASK_BROKER; + PolicyRule open(result); + + if (!open.AddStringMatch(IF, OpenEventParams::NAME, name, CASE_INSENSITIVE)) + return false; + + if (TargetPolicy::EVENTS_ALLOW_READONLY == semantics) { + // We consider all flags that are not known to be readonly as potentially + // used for write. + uint32_t allowed_flags = SYNCHRONIZE | GENERIC_READ | READ_CONTROL; + uint32_t restricted_flags = ~allowed_flags; + open.AddNumberMatch(IF_NOT, OpenEventParams::ACCESS, restricted_flags, AND); + } + + if (!policy->AddRule(IpcTag::OPENEVENT, &open)) + return false; + + // If it's not a read only, add the create rule. + if (TargetPolicy::EVENTS_ALLOW_READONLY != semantics) { + PolicyRule create(result); + if (!create.AddStringMatch(IF, NameBased::NAME, name, CASE_INSENSITIVE)) + return false; + + if (!policy->AddRule(IpcTag::CREATEEVENT, &create)) + return false; + } + + return true; +} + +NTSTATUS SyncPolicy::CreateEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& event_name, + uint32_t event_type, + uint32_t initial_state, + HANDLE* handle) { + NtCreateEventFunction NtCreateEvent = nullptr; + ResolveNTFunctionPtr("NtCreateEvent", &NtCreateEvent); + + // The only action supported is ASK_BROKER which means create the requested + // file as specified. + if (ASK_BROKER != eval_result) + return false; + + HANDLE object_directory = nullptr; + NTSTATUS status = GetBaseNamedObjectsDirectory(&object_directory); + if (status != STATUS_SUCCESS) + return status; + + UNICODE_STRING unicode_event_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(event_name, OBJ_CASE_INSENSITIVE, object_directory, + &object_attributes, &unicode_event_name, nullptr); + + HANDLE local_handle = nullptr; + status = NtCreateEvent(&local_handle, EVENT_ALL_ACCESS, &object_attributes, + static_cast<EVENT_TYPE>(event_type), + static_cast<BOOLEAN>(initial_state != 0)); + if (!local_handle) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return status; +} + +NTSTATUS SyncPolicy::OpenEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& event_name, + uint32_t desired_access, + HANDLE* handle) { + NtOpenEventFunction NtOpenEvent = nullptr; + ResolveNTFunctionPtr("NtOpenEvent", &NtOpenEvent); + + // The only action supported is ASK_BROKER which means create the requested + // event as specified. + if (ASK_BROKER != eval_result) + return false; + + HANDLE object_directory = nullptr; + NTSTATUS status = GetBaseNamedObjectsDirectory(&object_directory); + if (status != STATUS_SUCCESS) + return status; + + UNICODE_STRING unicode_event_name = {}; + OBJECT_ATTRIBUTES object_attributes = {}; + InitObjectAttribs(event_name, OBJ_CASE_INSENSITIVE, object_directory, + &object_attributes, &unicode_event_name, nullptr); + + HANDLE local_handle = nullptr; + status = NtOpenEvent(&local_handle, desired_access, &object_attributes); + if (!local_handle) + return status; + + if (!::DuplicateHandle(::GetCurrentProcess(), local_handle, + client_info.process, handle, 0, false, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return STATUS_ACCESS_DENIED; + } + return status; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sync_policy.h b/security/sandbox/chromium/sandbox/win/src/sync_policy.h new file mode 100644 index 0000000000..2eb4124a82 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_policy.h @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_SYNC_POLICY_H__ +#define SANDBOX_SRC_SYNC_POLICY_H__ + +#include <stdint.h> + +#include <string> + +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace sandbox { + +// This class centralizes most of the knowledge related to sync policy +class SyncPolicy { + public: + // Creates the required low-level policy rules to evaluate a high-level + // policy rule for sync calls, in particular open or create actions. + // name is the sync object name, semantics is the desired semantics for the + // open or create and policy is the policy generator to which the rules are + // going to be added. + static bool GenerateRules(const wchar_t* name, + TargetPolicy::Semantics semantics, + LowLevelPolicy* policy); + + // Performs the desired policy action on a request. + // client_info is the target process that is making the request and + // eval_result is the desired policy action to accomplish. + static NTSTATUS CreateEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& event_name, + uint32_t event_type, + uint32_t initial_state, + HANDLE* handle); + static NTSTATUS OpenEventAction(EvalResult eval_result, + const ClientInfo& client_info, + const std::wstring& event_name, + uint32_t desired_access, + HANDLE* handle); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_SYNC_POLICY_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/sync_policy_test.cc b/security/sandbox/chromium/sandbox/win/src/sync_policy_test.cc new file mode 100644 index 0000000000..97f60d359f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_policy_test.cc @@ -0,0 +1,145 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/sync_policy_test.h" + +#include "base/win/scoped_handle.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_policy.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +SBOX_TESTS_COMMAND int Event_Open(int argc, wchar_t** argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + DWORD desired_access = SYNCHRONIZE; + if (L'f' == argv[0][0]) + desired_access = EVENT_ALL_ACCESS; + + base::win::ScopedHandle event_open( + ::OpenEvent(desired_access, false, argv[1])); + DWORD error_open = ::GetLastError(); + + if (event_open.IsValid()) + return SBOX_TEST_SUCCEEDED; + + if (ERROR_ACCESS_DENIED == error_open || ERROR_BAD_PATHNAME == error_open || + ERROR_FILE_NOT_FOUND == error_open) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + +SBOX_TESTS_COMMAND int Event_CreateOpen(int argc, wchar_t** argv) { + if (argc < 2 || argc > 3) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + wchar_t* event_name = nullptr; + if (3 == argc) + event_name = argv[2]; + + bool manual_reset = false; + bool initial_state = false; + if (L't' == argv[0][0]) + manual_reset = true; + if (L't' == argv[1][0]) + initial_state = true; + + base::win::ScopedHandle event_create( + ::CreateEvent(nullptr, manual_reset, initial_state, event_name)); + DWORD error_create = ::GetLastError(); + base::win::ScopedHandle event_open; + if (event_name) + event_open.Set(::OpenEvent(EVENT_ALL_ACCESS, false, event_name)); + + if (event_create.IsValid()) { + DWORD wait = ::WaitForSingleObject(event_create.Get(), 0); + if (initial_state && WAIT_OBJECT_0 != wait) + return SBOX_TEST_FAILED; + + if (!initial_state && WAIT_TIMEOUT != wait) + return SBOX_TEST_FAILED; + } + + if (event_name) { + // Both event_open and event_create have to be valid. + if (event_open.IsValid() && event_create.IsValid()) + return SBOX_TEST_SUCCEEDED; + + if ((event_open.IsValid() && !event_create.IsValid()) || + (!event_open.IsValid() && event_create.IsValid())) { + return SBOX_TEST_FAILED; + } + } else { + // Only event_create has to be valid. + if (event_create.Get()) + return SBOX_TEST_SUCCEEDED; + } + + if (ERROR_ACCESS_DENIED == error_create || ERROR_BAD_PATHNAME == error_create) + return SBOX_TEST_DENIED; + + return SBOX_TEST_FAILED; +} + +// Tests the creation of events using all the possible combinations. +TEST(SyncPolicyTest, TestEvent) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"test1")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"test2")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f t")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t t")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f test2")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f t test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t t test2")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f f test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t f test4")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f t test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t t test4")); +} + +// Tests opening events with read only access. +TEST(SyncPolicyTest, TestEventReadOnly) { + TestRunner runner; + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, L"test1")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, L"test2")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, L"test5")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_READONLY, L"test6")); + + base::win::ScopedHandle handle1( + ::CreateEvent(nullptr, false, false, L"test1")); + base::win::ScopedHandle handle2( + ::CreateEvent(nullptr, false, false, L"test2")); + base::win::ScopedHandle handle3( + ::CreateEvent(nullptr, false, false, L"test3")); + base::win::ScopedHandle handle4( + ::CreateEvent(nullptr, false, false, L"test4")); + + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen f f")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_CreateOpen t f")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test1")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"Event_Open s test2")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open f test3")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_Open s test4")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f f test5")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t f test6")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen f t test5")); + EXPECT_EQ(SBOX_TEST_DENIED, runner.RunTest(L"Event_CreateOpen t t test6")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/sync_policy_test.h b/security/sandbox/chromium/sandbox/win/src/sync_policy_test.h new file mode 100644 index 0000000000..4f354b35be --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/sync_policy_test.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ +#define SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ + +#include "sandbox/win/tests/common/controller.h" + +namespace sandbox { + +// Opens the named event received on argv[1]. The requested access is +// EVENT_ALL_ACCESS if argv[0] starts with 'f', or SYNCHRONIZE otherwise. +SBOX_TESTS_COMMAND int Event_Open(int argc, wchar_t **argv); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_SYNC_POLICY_TEST_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/target_interceptions.cc b/security/sandbox/chromium/sandbox/win/src/target_interceptions.cc new file mode 100644 index 0000000000..1b467814c6 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_interceptions.cc @@ -0,0 +1,136 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/target_interceptions.h" + +#include "base/win/static_constants.h" +#include "sandbox/win/src/interception_agent.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace sandbox { + +SANDBOX_INTERCEPT NtExports g_nt; + +const char KERNEL32_DLL_NAME[] = "kernel32.dll"; + +enum SectionLoadState { + kBeforeKernel32, + kAfterKernel32, +}; + +// Hooks NtMapViewOfSection to detect the load of DLLs. If hot patching is +// required for this dll, this functions patches it. +NTSTATUS WINAPI +TargetNtMapViewOfSection(NtMapViewOfSectionFunction orig_MapViewOfSection, + HANDLE section, + HANDLE process, + PVOID* base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect) { + NTSTATUS ret = orig_MapViewOfSection(section, process, base, zero_bits, + commit_size, offset, view_size, inherit, + allocation_type, protect); + static SectionLoadState s_state = kBeforeKernel32; + + do { + if (!NT_SUCCESS(ret)) + break; + + if (!IsSameProcess(process)) + break; + + // Only check for verifier.dll or kernel32.dll loading if we haven't moved + // past that state yet. + if (s_state == kBeforeKernel32) { + const char* ansi_module_name = + GetAnsiImageInfoFromModule(reinterpret_cast<HMODULE>(*base)); + + // _strnicmp below may hit read access violations for some sections. We + // find what looks like a valid export directory for a PE module but the + // pointer to the module name will be pointing to invalid memory. + __try { + // Don't initialize the heap if verifier.dll is being loaded. This + // indicates Application Verifier is enabled and we should wait until + // the next module is loaded. + if (ansi_module_name && + (g_nt._strnicmp( + ansi_module_name, base::win::kApplicationVerifierDllName, + g_nt.strlen(base::win::kApplicationVerifierDllName) + 1) == 0)) + break; + + if (ansi_module_name && + (g_nt._strnicmp(ansi_module_name, KERNEL32_DLL_NAME, + sizeof(KERNEL32_DLL_NAME)) == 0)) { + s_state = kAfterKernel32; + } + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + } + + if (!InitHeap()) + break; + + if (!IsValidImageSection(section, base, offset, view_size)) + break; + + UINT image_flags; + UNICODE_STRING* module_name = + GetImageInfoFromModule(reinterpret_cast<HMODULE>(*base), &image_flags); + UNICODE_STRING* file_name = GetBackingFilePath(*base); + + if ((!module_name) && (image_flags & MODULE_HAS_CODE)) { + // If the module has no exports we retrieve the module name from the + // full path of the mapped section. + module_name = ExtractModuleName(file_name); + } + + InterceptionAgent* agent = InterceptionAgent::GetInterceptionAgent(); + + if (agent) { + if (!agent->OnDllLoad(file_name, module_name, *base)) { + // Interception agent is demanding to un-map the module. + g_nt.UnmapViewOfSection(process, *base); + *base = nullptr; + ret = STATUS_UNSUCCESSFUL; + } + } + + if (module_name) + operator delete(module_name, NT_ALLOC); + + if (file_name) + operator delete(file_name, NT_ALLOC); + + } while (false); + + return ret; +} + +NTSTATUS WINAPI +TargetNtUnmapViewOfSection(NtUnmapViewOfSectionFunction orig_UnmapViewOfSection, + HANDLE process, + PVOID base) { + NTSTATUS ret = orig_UnmapViewOfSection(process, base); + + if (!NT_SUCCESS(ret)) + return ret; + + if (!IsSameProcess(process)) + return ret; + + InterceptionAgent* agent = InterceptionAgent::GetInterceptionAgent(); + + if (agent) + agent->OnDllUnload(base); + + return ret; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/target_interceptions.h b/security/sandbox/chromium/sandbox/win/src/target_interceptions.h new file mode 100644 index 0000000000..fac1c65c27 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_interceptions.h @@ -0,0 +1,43 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_TARGET_INTERCEPTIONS_H_ +#define SANDBOX_WIN_SRC_TARGET_INTERCEPTIONS_H_ + +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +extern "C" { + +// Interception of NtMapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being loaded, so we can patch them if needed. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtMapViewOfSection(NtMapViewOfSectionFunction orig_MapViewOfSection, + HANDLE section, + HANDLE process, + PVOID* base, + ULONG_PTR zero_bits, + SIZE_T commit_size, + PLARGE_INTEGER offset, + PSIZE_T view_size, + SECTION_INHERIT inherit, + ULONG allocation_type, + ULONG protect); + +// Interception of NtUnmapViewOfSection on the child process. +// It should never be called directly. This function provides the means to +// detect dlls being unloaded, so we can clean up our interceptions. +SANDBOX_INTERCEPT NTSTATUS WINAPI +TargetNtUnmapViewOfSection(NtUnmapViewOfSectionFunction orig_UnmapViewOfSection, + HANDLE process, + PVOID base); + +} // extern "C" + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_TARGET_INTERCEPTIONS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/target_process.cc b/security/sandbox/chromium/sandbox/win/src/target_process.cc new file mode 100644 index 0000000000..4661267d1e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_process.cc @@ -0,0 +1,393 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/target_process.h" + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/memory/free_deleter.h" +#include "base/numerics/safe_conversions.h" +#include "base/process/environment_internal.h" +#include "base/win/startup_information.h" +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/policy_low_level.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/security_capabilities.h" +#include "sandbox/win/src/sharedmem_ipc_server.h" +#include "sandbox/win/src/sid.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +namespace { + +void CopyPolicyToTarget(const void* source, size_t size, void* dest) { + if (!source || !size) + return; + memcpy(dest, source, size); + sandbox::PolicyGlobal* policy = + reinterpret_cast<sandbox::PolicyGlobal*>(dest); + + size_t offset = reinterpret_cast<size_t>(source); + + for (size_t i = 0; i < sandbox::kMaxServiceCount; i++) { + size_t buffer = reinterpret_cast<size_t>(policy->entry[i]); + if (buffer) { + buffer -= offset; + policy->entry[i] = reinterpret_cast<sandbox::PolicyBuffer*>(buffer); + } + } +} + +bool GetTokenAppContainerSid(HANDLE token_handle, + std::unique_ptr<Sid>* app_container_sid) { + std::vector<char> app_container_info(sizeof(TOKEN_APPCONTAINER_INFORMATION) + + SECURITY_MAX_SID_SIZE); + DWORD return_length; + + if (!::GetTokenInformation( + token_handle, TokenAppContainerSid, app_container_info.data(), + base::checked_cast<DWORD>(app_container_info.size()), + &return_length)) { + return false; + } + + PTOKEN_APPCONTAINER_INFORMATION info = + reinterpret_cast<PTOKEN_APPCONTAINER_INFORMATION>( + app_container_info.data()); + if (!info->TokenAppContainer) + return false; + *app_container_sid = std::unique_ptr<Sid>(new Sid(info->TokenAppContainer)); + return true; +} + +bool GetProcessAppContainerSid(HANDLE process, + std::unique_ptr<Sid>* app_container_sid) { + HANDLE token_handle; + if (!::OpenProcessToken(process, TOKEN_QUERY, &token_handle)) + return false; + base::win::ScopedHandle process_token(token_handle); + + return GetTokenAppContainerSid(process_token.Get(), app_container_sid); +} + +bool GetAppContainerImpersonationToken( + HANDLE process, + HANDLE initial_token, + const std::vector<Sid>& capabilities, + base::win::ScopedHandle* impersonation_token) { + std::unique_ptr<Sid> app_container_sid; + if (!GetProcessAppContainerSid(process, &app_container_sid)) { + return false; + } + SecurityCapabilities security_caps(*app_container_sid, capabilities); + return CreateLowBoxToken(initial_token, IMPERSONATION, &security_caps, + nullptr, 0, impersonation_token) == ERROR_SUCCESS; +} + +} // namespace + +SANDBOX_INTERCEPT HANDLE g_shared_section; +SANDBOX_INTERCEPT size_t g_shared_IPC_size; +SANDBOX_INTERCEPT size_t g_shared_policy_size; + +TargetProcess::TargetProcess(base::win::ScopedHandle initial_token, + base::win::ScopedHandle lockdown_token, + HANDLE job, + ThreadProvider* thread_pool, + const std::vector<Sid>& impersonation_capabilities) + // This object owns everything initialized here except thread_pool and + // the job_ handle. The Job handle is closed by BrokerServices and results + // eventually in a call to our dtor. + : lockdown_token_(std::move(lockdown_token)), + initial_token_(std::move(initial_token)), + job_(job), + thread_pool_(thread_pool), + base_address_(nullptr), + impersonation_capabilities_(impersonation_capabilities) {} + +TargetProcess::~TargetProcess() { + // Give a chance to the process to die. In most cases the JOB_KILL_ON_CLOSE + // will take effect only when the context changes. As far as the testing went, + // this wait was enough to switch context and kill the processes in the job. + // If this process is already dead, the function will return without waiting. + // For now, this wait is there only to do a best effort to prevent some leaks + // from showing up in purify. + if (sandbox_process_info_.IsValid()) { + ::WaitForSingleObject(sandbox_process_info_.process_handle(), 50); + // Terminate the process if it's still alive, as its IPC server is going + // away. 1 is RESULT_CODE_KILLED. + ::TerminateProcess(sandbox_process_info_.process_handle(), 1); + } + + // ipc_server_ references our process handle, so make sure the former is shut + // down before the latter is closed (by ScopedProcessInformation). + ipc_server_.reset(); +} + +// Creates the target (child) process suspended and assigns it to the job +// object. +ResultCode TargetProcess::Create( + const wchar_t* exe_path, + const wchar_t* command_line, + bool inherit_handles, + const base::win::StartupInformation& startup_info, + base::win::ScopedProcessInformation* target_info, + base::EnvironmentMap& env_changes, + DWORD* win_error) { + exe_name_.reset(_wcsdup(exe_path)); + + // the command line needs to be writable by CreateProcess(). + std::unique_ptr<wchar_t, base::FreeDeleter> cmd_line(_wcsdup(command_line)); + + // Start the target process suspended. + DWORD flags = + CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS; + + if (startup_info.has_extended_startup_info()) + flags |= EXTENDED_STARTUPINFO_PRESENT; + + if (job_ && base::win::GetVersion() < base::win::Version::WIN8) { + // Windows 8 implements nested jobs, but for older systems we need to + // break out of any job we're in to enforce our restrictions. + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + + LPTCH original_environment = GetEnvironmentStrings(); + base::NativeEnvironmentString new_environment = + base::internal::AlterEnvironment(original_environment, env_changes); + // Ignore return value? What can we do? + FreeEnvironmentStrings(original_environment); + LPVOID new_env_ptr = (void*)new_environment.data(); + + PROCESS_INFORMATION temp_process_info = {}; + if (!::CreateProcessAsUserW(lockdown_token_.Get(), exe_path, cmd_line.get(), + nullptr, // No security attribute. + nullptr, // No thread attribute. + inherit_handles, flags, + new_env_ptr, + nullptr, // Use current directory of the caller. + startup_info.startup_info(), + &temp_process_info)) { + *win_error = ::GetLastError(); + return SBOX_ERROR_CREATE_PROCESS; + } + base::win::ScopedProcessInformation process_info(temp_process_info); + + if (job_) { + // Assign the suspended target to the windows job object. + if (!::AssignProcessToJobObject(job_, process_info.process_handle())) { + *win_error = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); + return SBOX_ERROR_ASSIGN_PROCESS_TO_JOB_OBJECT; + } + } + + if (initial_token_.IsValid()) { + HANDLE impersonation_token = initial_token_.Get(); + base::win::ScopedHandle app_container_token; + if (GetAppContainerImpersonationToken( + process_info.process_handle(), impersonation_token, + impersonation_capabilities_, &app_container_token)) { + impersonation_token = app_container_token.Get(); + } + + // Change the token of the main thread of the new process for the + // impersonation token with more rights. This allows the target to start; + // otherwise it will crash too early for us to help. + HANDLE temp_thread = process_info.thread_handle(); + if (!::SetThreadToken(&temp_thread, impersonation_token)) { + *win_error = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); + return SBOX_ERROR_SET_THREAD_TOKEN; + } + initial_token_.Close(); + } + + if (!target_info->DuplicateFrom(process_info)) { + *win_error = ::GetLastError(); // This may or may not be correct. + ::TerminateProcess(process_info.process_handle(), 0); + return SBOX_ERROR_DUPLICATE_TARGET_INFO; + } + + base_address_ = GetProcessBaseAddress(process_info.process_handle()); + DCHECK(base_address_); + if (!base_address_) { + *win_error = ::GetLastError(); + ::TerminateProcess(process_info.process_handle(), 0); + return SBOX_ERROR_CANNOT_FIND_BASE_ADDRESS; + } + + sandbox_process_info_.Set(process_info.Take()); + return SBOX_ALL_OK; +} + +ResultCode TargetProcess::TransferVariable(const char* name, + void* address, + size_t size) { + if (!sandbox_process_info_.IsValid()) + return SBOX_ERROR_UNEXPECTED_CALL; + + void* child_var = address; + +#if SANDBOX_EXPORTS + HMODULE module = ::LoadLibrary(exe_name_.get()); + if (!module) + return SBOX_ERROR_CANNOT_LOADLIBRARY_EXECUTABLE; + + child_var = reinterpret_cast<void*>(::GetProcAddress(module, name)); + ::FreeLibrary(module); + + if (!child_var) + return SBOX_ERROR_CANNOT_FIND_VARIABLE_ADDRESS; + + size_t offset = + reinterpret_cast<char*>(child_var) - reinterpret_cast<char*>(module); + child_var = reinterpret_cast<char*>(MainModule()) + offset; +#endif + + SIZE_T written; + if (!::WriteProcessMemory(sandbox_process_info_.process_handle(), child_var, + address, size, &written)) + return SBOX_ERROR_CANNOT_WRITE_VARIABLE_VALUE; + + if (written != size) + return SBOX_ERROR_INVALID_WRITE_VARIABLE_SIZE; + + return SBOX_ALL_OK; +} + +// Construct the IPC server and the IPC dispatcher. When the target does +// an IPC it will eventually call the dispatcher. +ResultCode TargetProcess::Init(Dispatcher* ipc_dispatcher, + void* policy, + uint32_t shared_IPC_size, + uint32_t shared_policy_size, + DWORD* win_error) { + // We need to map the shared memory on the target. This is necessary for + // any IPC that needs to take place, even if the target has not yet hit + // the main( ) function or even has initialized the CRT. So here we set + // the handle to the shared section. The target on the first IPC must do + // the rest, which boils down to calling MapViewofFile() + + // We use this single memory pool for IPC and for policy. + DWORD shared_mem_size = + static_cast<DWORD>(shared_IPC_size + shared_policy_size); + shared_section_.Set(::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, + PAGE_READWRITE | SEC_COMMIT, 0, + shared_mem_size, nullptr)); + if (!shared_section_.IsValid()) { + *win_error = ::GetLastError(); + return SBOX_ERROR_CREATE_FILE_MAPPING; + } + + void* shared_memory = ::MapViewOfFile( + shared_section_.Get(), FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0); + if (!shared_memory) { + *win_error = ::GetLastError(); + return SBOX_ERROR_MAP_VIEW_OF_SHARED_SECTION; + } + + CopyPolicyToTarget(policy, shared_policy_size, + reinterpret_cast<char*>(shared_memory) + shared_IPC_size); + + ResultCode ret; + // Set the global variables in the target. These are not used on the broker. + g_shared_IPC_size = shared_IPC_size; + ret = TransferVariable("g_shared_IPC_size", &g_shared_IPC_size, + sizeof(g_shared_IPC_size)); + g_shared_IPC_size = 0; + if (SBOX_ALL_OK != ret) { + *win_error = ::GetLastError(); + return ret; + } + g_shared_policy_size = shared_policy_size; + ret = TransferVariable("g_shared_policy_size", &g_shared_policy_size, + sizeof(g_shared_policy_size)); + g_shared_policy_size = 0; + if (SBOX_ALL_OK != ret) { + *win_error = ::GetLastError(); + return ret; + } + + ipc_server_.reset(new SharedMemIPCServer( + sandbox_process_info_.process_handle(), + sandbox_process_info_.process_id(), thread_pool_, ipc_dispatcher)); + + if (!ipc_server_->Init(shared_memory, shared_IPC_size, kIPCChannelSize)) + return SBOX_ERROR_NO_SPACE; + + DWORD access = FILE_MAP_READ | FILE_MAP_WRITE | SECTION_QUERY; + HANDLE target_shared_section; + if (!::DuplicateHandle(::GetCurrentProcess(), shared_section_.Get(), + sandbox_process_info_.process_handle(), + &target_shared_section, access, false, 0)) { + *win_error = ::GetLastError(); + return SBOX_ERROR_DUPLICATE_SHARED_SECTION; + } + + g_shared_section = target_shared_section; + ret = TransferVariable("g_shared_section", &g_shared_section, + sizeof(g_shared_section)); + g_shared_section = nullptr; + if (SBOX_ALL_OK != ret) { + *win_error = ::GetLastError(); + return ret; + } + + // After this point we cannot use this handle anymore. + ::CloseHandle(sandbox_process_info_.TakeThreadHandle()); + + return SBOX_ALL_OK; +} + +void TargetProcess::Terminate() { + if (!sandbox_process_info_.IsValid()) + return; + + ::TerminateProcess(sandbox_process_info_.process_handle(), 0); +} + +ResultCode TargetProcess::AssignLowBoxToken( + const base::win::ScopedHandle& token) { + if (!token.IsValid()) + return SBOX_ALL_OK; + PROCESS_ACCESS_TOKEN process_access_token = {}; + process_access_token.token = token.Get(); + + NtSetInformationProcess SetInformationProcess = nullptr; + ResolveNTFunctionPtr("NtSetInformationProcess", &SetInformationProcess); + + NTSTATUS status = SetInformationProcess( + sandbox_process_info_.process_handle(), + static_cast<PROCESS_INFORMATION_CLASS>(NtProcessInformationAccessToken), + &process_access_token, sizeof(process_access_token)); + if (!NT_SUCCESS(status)) { + ::SetLastError(GetLastErrorFromNtStatus(status)); + return SBOX_ERROR_SET_LOW_BOX_TOKEN; + } + return SBOX_ALL_OK; +} + +TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address) { + TargetProcess* target = + new TargetProcess(base::win::ScopedHandle(), base::win::ScopedHandle(), + nullptr, nullptr, std::vector<Sid>()); + PROCESS_INFORMATION process_info = {}; + process_info.hProcess = process; + target->sandbox_process_info_.Set(process_info); + target->base_address_ = base_address; + return target; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/target_process.h b/security/sandbox/chromium/sandbox/win/src/target_process.h new file mode 100644 index 0000000000..c489ed7bc1 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_process.h @@ -0,0 +1,143 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_WIN_SRC_TARGET_PROCESS_H_ +#define SANDBOX_WIN_SRC_TARGET_PROCESS_H_ + +#include <windows.h> + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "base/macros.h" +#include "base/environment.h" +#include "base/memory/free_deleter.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/sandbox_types.h" + +namespace base { +namespace win { + +class StartupInformation; + +} // namespace win +} // namespace base + +namespace sandbox { + +class SharedMemIPCServer; +class Sid; +class ThreadProvider; + +// TargetProcess models a target instance (child process). Objects of this +// class are owned by the Policy used to create them. +class TargetProcess { + public: + // The constructor takes ownership of |initial_token| and |lockdown_token| + TargetProcess(base::win::ScopedHandle initial_token, + base::win::ScopedHandle lockdown_token, + HANDLE job, + ThreadProvider* thread_pool, + const std::vector<Sid>& impersonation_capabilities); + ~TargetProcess(); + + // TODO(cpu): Currently there does not seem to be a reason to implement + // reference counting for this class since is internal, but kept the + // the same interface so the interception framework does not need to be + // touched at this point. + void AddRef() {} + void Release() {} + + // Creates the new target process. The process is created suspended. + ResultCode Create(const wchar_t* exe_path, + const wchar_t* command_line, + bool inherit_handles, + const base::win::StartupInformation& startup_info, + base::win::ScopedProcessInformation* target_info, + base::EnvironmentMap& env_map, + DWORD* win_error); + + // Assign a new lowbox token to the process post creation. The process + // must still be in its initial suspended state, however this still + // might fail in the presence of third-party software. + ResultCode AssignLowBoxToken(const base::win::ScopedHandle& token); + + // Destroys the target process. + void Terminate(); + + // Creates the IPC objects such as the BrokerDispatcher and the + // IPC server. The IPC server uses the services of the thread_pool. + ResultCode Init(Dispatcher* ipc_dispatcher, + void* policy, + uint32_t shared_IPC_size, + uint32_t shared_policy_size, + DWORD* win_error); + + // Returns the handle to the target process. + HANDLE Process() const { return sandbox_process_info_.process_handle(); } + + // Returns the handle to the job object that the target process belongs to. + HANDLE Job() const { return job_; } + + // Returns the address of the target main exe. This is used by the + // interceptions framework. + HMODULE MainModule() const { + return reinterpret_cast<HMODULE>(base_address_); + } + + // Returns the name of the executable. + const wchar_t* Name() const { return exe_name_.get(); } + + // Returns the process id. + DWORD ProcessId() const { return sandbox_process_info_.process_id(); } + + // Returns the handle to the main thread. + HANDLE MainThread() const { return sandbox_process_info_.thread_handle(); } + + // Transfers variable at |address| of |size| bytes from broker to target. + ResultCode TransferVariable(const char* name, void* address, size_t size); + + private: + // Details of the target process. + base::win::ScopedProcessInformation sandbox_process_info_; + // The token associated with the process. It provides the core of the + // sbox security. + base::win::ScopedHandle lockdown_token_; + // The token given to the initial thread so that the target process can + // start. It has more powers than the lockdown_token. + base::win::ScopedHandle initial_token_; + // Kernel handle to the shared memory used by the IPC server. + base::win::ScopedHandle shared_section_; + // Job object containing the target process. + HANDLE job_; + // Reference to the IPC subsystem. + std::unique_ptr<SharedMemIPCServer> ipc_server_; + // Provides the threads used by the IPC. This class does not own this pointer. + ThreadProvider* thread_pool_; + // Base address of the main executable + void* base_address_; + // Full name of the target executable. + std::unique_ptr<wchar_t, base::FreeDeleter> exe_name_; + /// List of capability sids for use when impersonating in an AC process. + std::vector<Sid> impersonation_capabilities_; + + // Function used for testing. + friend TargetProcess* MakeTestTargetProcess(HANDLE process, + HMODULE base_address); + + DISALLOW_IMPLICIT_CONSTRUCTORS(TargetProcess); +}; + +// Creates a mock TargetProcess used for testing interceptions. +// TODO(cpu): It seems that this method is not going to be used anymore. +TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address); + +} // namespace sandbox + +#endif // SANDBOX_WIN_SRC_TARGET_PROCESS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/target_services.cc b/security/sandbox/chromium/sandbox/win/src/target_services.cc new file mode 100644 index 0000000000..a80e0106ef --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_services.cc @@ -0,0 +1,264 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/target_services.h" + +#include <new> + +#include <process.h> +#include <stdint.h> + +#include "base/win/windows_version.h" +#include "sandbox/win/src/crosscall_client.h" +#include "sandbox/win/src/handle_closer_agent.h" +#include "sandbox/win/src/handle_interception.h" +#include "sandbox/win/src/heap_helper.h" +#include "sandbox/win/src/line_break_interception.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/restricted_token_utils.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_nt_util.h" +#include "sandbox/win/src/sandbox_types.h" +#include "sandbox/win/src/sharedmem_ipc_client.h" + +namespace sandbox { +namespace { + +// Flushing a cached key is triggered by just opening the key and closing the +// resulting handle. RegDisablePredefinedCache() is the documented way to flush +// HKCU so do not use it with this function. +bool FlushRegKey(HKEY root) { + HKEY key; + if (ERROR_SUCCESS == + ::RegOpenKeyExW(root, nullptr, 0, MAXIMUM_ALLOWED, &key)) { + if (ERROR_SUCCESS != ::RegCloseKey(key)) + return false; + } + return true; +} + +// This function forces advapi32.dll to release some internally cached handles +// that were made during calls to RegOpenkey and RegOpenKeyEx if it is called +// with a more restrictive token. Returns true if the flushing is succesful +// although this behavior is undocumented and there is no guarantee that in +// fact this will happen in future versions of windows. +bool FlushCachedRegHandles() { + return (FlushRegKey(HKEY_LOCAL_MACHINE) && FlushRegKey(HKEY_CLASSES_ROOT) && + FlushRegKey(HKEY_USERS)); +} + +// Cleans up this process if CSRSS will be disconnected, as this disconnection +// is not supported Windows behavior. +// Currently, this step requires closing a heap that this shared with csrss.exe. +// Closing the ALPC Port handle to csrss.exe leaves this heap in an invalid +// state. This causes problems if anyone enumerates the heap. +bool CsrssDisconnectCleanup() { + HANDLE csr_port_heap = FindCsrPortHeap(); + if (!csr_port_heap) { + DLOG(ERROR) << "Failed to find CSR Port heap handle"; + return false; + } + HeapDestroy(csr_port_heap); + return true; +} + +// Used by EnumSystemLocales for warming up. +static BOOL CALLBACK EnumLocalesProcEx(LPWSTR lpLocaleString, + DWORD dwFlags, + LPARAM lParam) { + return TRUE; +} + +// Additional warmup done just when CSRSS is being disconnected. +bool CsrssDisconnectWarmup() { + return ::EnumSystemLocalesEx(EnumLocalesProcEx, LOCALE_WINDOWS, 0, 0); +} + +// Checks if we have handle entries pending and runs the closer. +// Updates is_csrss_connected based on which handle types are closed. +bool CloseOpenHandles(bool* is_csrss_connected) { + if (HandleCloserAgent::NeedsHandlesClosed()) { + HandleCloserAgent handle_closer; + handle_closer.InitializeHandlesToClose(is_csrss_connected); + if (!*is_csrss_connected) { + if (!CsrssDisconnectWarmup() || !CsrssDisconnectCleanup()) { + return false; + } + } + if (!handle_closer.CloseHandles()) + return false; + } + return true; +} + +// Warm up language subsystems before the sandbox is turned on. +// Tested on Win8.1 x64: +// This needs to happen after RevertToSelf() is called, because (at least) in +// the case of GetUserDefaultLCID() it checks the TEB to see if the process is +// impersonating (TEB!IsImpersonating). If it is, the cached locale information +// is not used, nor is it set. Therefore, calls after RevertToSelf() will not +// have warmed-up values to use. +bool WarmupWindowsLocales() { + // NOTE(liamjm): When last checked (Win 8.1 x64) it wasn't necessary to + // warmup all of these functions, but let's not assume that. + ::GetUserDefaultLangID(); + ::GetUserDefaultLCID(); + wchar_t localeName[LOCALE_NAME_MAX_LENGTH] = {0}; + return (0 != ::GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH)); +} + +// Used as storage for g_target_services, because other allocation facilities +// are not available early. We can't use a regular function static because on +// VS2015, because the CRT tries to acquire a lock to guard initialization, but +// this code runs before the CRT is initialized. +char g_target_services_memory[sizeof(TargetServicesBase)]; +TargetServicesBase* g_target_services = nullptr; + +} // namespace + +SANDBOX_INTERCEPT IntegrityLevel g_shared_delayed_integrity_level = + INTEGRITY_LEVEL_LAST; +SANDBOX_INTERCEPT MitigationFlags g_shared_delayed_mitigations = 0; + +TargetServicesBase::TargetServicesBase() {} + +ResultCode TargetServicesBase::Init() { + process_state_.SetInitCalled(); + return SBOX_ALL_OK; +} + +// Failure here is a breach of security so the process is terminated. +void TargetServicesBase::LowerToken() { + if (ERROR_SUCCESS != + SetProcessIntegrityLevel(g_shared_delayed_integrity_level)) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_INTEGRITY); + process_state_.SetRevertedToSelf(); + // If the client code as called RegOpenKey, advapi32.dll has cached some + // handles. The following code gets rid of them. + if (!::RevertToSelf()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_DROPTOKEN); + if (!FlushCachedRegHandles()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_FLUSHANDLES); + if (ERROR_SUCCESS != ::RegDisablePredefinedCache()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_CACHEDISABLE); + if (!WarmupWindowsLocales()) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_WARMUP); + bool is_csrss_connected = true; + if (!CloseOpenHandles(&is_csrss_connected)) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_CLOSEHANDLES); + process_state_.SetCsrssConnected(is_csrss_connected); + // Enabling mitigations must happen last otherwise handle closing breaks + if (g_shared_delayed_mitigations && + !ApplyProcessMitigationsToCurrentProcess(g_shared_delayed_mitigations)) + ::TerminateProcess(::GetCurrentProcess(), SBOX_FATAL_MITIGATION); +} + +ProcessState* TargetServicesBase::GetState() { + return &process_state_; +} + +TargetServicesBase* TargetServicesBase::GetInstance() { + // Leak on purpose TargetServicesBase. + if (!g_target_services) + g_target_services = new (g_target_services_memory) TargetServicesBase; + return g_target_services; +} + +// The broker services a 'test' IPC service with the PING tag. +bool TargetServicesBase::TestIPCPing(int version) { + void* memory = GetGlobalIPCMemory(); + if (!memory) + return false; + SharedMemIPCClient ipc(memory); + CrossCallReturn answer = {0}; + + if (1 == version) { + uint32_t tick1 = ::GetTickCount(); + uint32_t cookie = 717115; + ResultCode code = CrossCall(ipc, IpcTag::PING1, cookie, &answer); + + if (SBOX_ALL_OK != code) { + return false; + } + // We should get two extended returns values from the IPC, one is the + // tick count on the broker and the other is the cookie times two. + if ((answer.extended_count != 2)) { + return false; + } + // We test the first extended answer to be within the bounds of the tick + // count only if there was no tick count wraparound. + uint32_t tick2 = ::GetTickCount(); + if (tick2 >= tick1) { + if ((answer.extended[0].unsigned_int < tick1) || + (answer.extended[0].unsigned_int > tick2)) { + return false; + } + } + + if (answer.extended[1].unsigned_int != cookie * 2) { + return false; + } + } else if (2 == version) { + uint32_t cookie = 717111; + InOutCountedBuffer counted_buffer(&cookie, sizeof(cookie)); + ResultCode code = CrossCall(ipc, IpcTag::PING2, counted_buffer, &answer); + + if (SBOX_ALL_OK != code) { + return false; + } + if (cookie != 717111 * 3) { + return false; + } + } else { + return false; + } + return true; +} + +ProcessState::ProcessState() + : process_state_(ProcessStateInternal::NONE), csrss_connected_(true) {} + +bool ProcessState::InitCalled() const { + return process_state_ >= ProcessStateInternal::INIT_CALLED; +} + +bool ProcessState::RevertedToSelf() const { + return process_state_ >= ProcessStateInternal::REVERTED_TO_SELF; +} + +bool ProcessState::IsCsrssConnected() const { + return csrss_connected_; +} + +void ProcessState::SetInitCalled() { + if (process_state_ < ProcessStateInternal::INIT_CALLED) + process_state_ = ProcessStateInternal::INIT_CALLED; +} + +void ProcessState::SetRevertedToSelf() { + if (process_state_ < ProcessStateInternal::REVERTED_TO_SELF) + process_state_ = ProcessStateInternal::REVERTED_TO_SELF; +} + +void ProcessState::SetCsrssConnected(bool csrss_connected) { + csrss_connected_ = csrss_connected; +} + +ResultCode TargetServicesBase::DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) { + return sandbox::DuplicateHandleProxy(source_handle, target_process_id, + target_handle, desired_access, options); +} + +ResultCode TargetServicesBase::GetComplexLineBreaks(const WCHAR* text, + uint32_t length, + uint8_t* break_before) { + return sandbox::GetComplexLineBreaksProxy(text, length, break_before); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/target_services.h b/security/sandbox/chromium/sandbox/win/src/target_services.h new file mode 100644 index 0000000000..1d70d4cd34 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/target_services.h @@ -0,0 +1,73 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_TARGET_SERVICES_H__ +#define SANDBOX_SRC_TARGET_SERVICES_H__ + +#include "base/macros.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +class ProcessState { + public: + ProcessState(); + // Returns true if main has been called. + bool InitCalled() const; + // Returns true if LowerToken has been called. + bool RevertedToSelf() const; + // Returns true if Csrss is connected. + bool IsCsrssConnected() const; + // Set the current state. + void SetInitCalled(); + void SetRevertedToSelf(); + void SetCsrssConnected(bool csrss_connected); + + private: + enum class ProcessStateInternal { NONE = 0, INIT_CALLED, REVERTED_TO_SELF }; + + ProcessStateInternal process_state_; + bool csrss_connected_; + DISALLOW_COPY_AND_ASSIGN(ProcessState); +}; + +// This class is an implementation of the TargetServices. +// Look in the documentation of sandbox::TargetServices for more info. +// Do NOT add a destructor to this class without changing the implementation of +// the factory method. +class TargetServicesBase : public TargetServices { + public: + TargetServicesBase(); + + // Public interface of TargetServices. + ResultCode Init() override; + void LowerToken() override; + ProcessState* GetState() override; + ResultCode DuplicateHandle(HANDLE source_handle, + DWORD target_process_id, + HANDLE* target_handle, + DWORD desired_access, + DWORD options) override; + ResultCode GetComplexLineBreaks(const WCHAR* text, uint32_t length, + uint8_t* break_before) final; + + // Factory method. + static TargetServicesBase* GetInstance(); + + // Sends a simple IPC Message that has a well-known answer. Returns true + // if the IPC was successful and false otherwise. There are 2 versions of + // this test: 1 and 2. The first one send a simple message while the + // second one send a message with an in/out param. + bool TestIPCPing(int version); + + private: + ~TargetServicesBase() {} + ProcessState process_state_; + DISALLOW_COPY_AND_ASSIGN(TargetServicesBase); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_TARGET_SERVICES_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/threadpool_unittest.cc b/security/sandbox/chromium/sandbox/win/src/threadpool_unittest.cc new file mode 100644 index 0000000000..3f951b761b --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/threadpool_unittest.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/win2k_threadpool.h" + +#include <stdint.h> + +#include "testing/gtest/include/gtest/gtest.h" + +void __stdcall EmptyCallBack(void*, unsigned char) {} + +void __stdcall TestCallBack(void* context, unsigned char) { + HANDLE event = reinterpret_cast<HANDLE>(context); + ::SetEvent(event); +} + +namespace sandbox { + +// Test that register and unregister work, part 1. +TEST(IPCTest, ThreadPoolRegisterTest1) { + Win2kThreadPool thread_pool; + + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + HANDLE event1 = ::CreateEventW(nullptr, false, false, nullptr); + HANDLE event2 = ::CreateEventW(nullptr, false, false, nullptr); + + uint32_t context = 0; + EXPECT_FALSE(thread_pool.RegisterWait(0, event1, EmptyCallBack, &context)); + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.RegisterWait(this, event1, EmptyCallBack, &context)); + EXPECT_EQ(1u, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.RegisterWait(this, event2, EmptyCallBack, &context)); + EXPECT_EQ(2u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(this)); + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(::CloseHandle(event1)); + EXPECT_TRUE(::CloseHandle(event2)); +} + +// Test that register and unregister work, part 2. +TEST(IPCTest, ThreadPoolRegisterTest2) { + Win2kThreadPool thread_pool; + + HANDLE event1 = ::CreateEventW(nullptr, false, false, nullptr); + HANDLE event2 = ::CreateEventW(nullptr, false, false, nullptr); + + uint32_t context = 0; + uint32_t c1 = 0; + uint32_t c2 = 0; + + EXPECT_TRUE(thread_pool.RegisterWait(&c1, event1, EmptyCallBack, &context)); + EXPECT_EQ(1u, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.RegisterWait(&c2, event2, EmptyCallBack, &context)); + EXPECT_EQ(2u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c2)); + EXPECT_EQ(1u, thread_pool.OutstandingWaits()); + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c2)); + EXPECT_EQ(1u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(&c1)); + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + EXPECT_TRUE(::CloseHandle(event1)); + EXPECT_TRUE(::CloseHandle(event2)); +} + +// Test that the thread pool has at least a thread that services an event. +// Test that when the event is un-registered is no longer serviced. +TEST(IPCTest, ThreadPoolSignalAndWaitTest) { + Win2kThreadPool thread_pool; + + // The events are auto reset and start not signaled. + HANDLE event1 = ::CreateEventW(nullptr, false, false, nullptr); + HANDLE event2 = ::CreateEventW(nullptr, false, false, nullptr); + + EXPECT_TRUE(thread_pool.RegisterWait(this, event1, TestCallBack, event2)); + + EXPECT_EQ(WAIT_OBJECT_0, ::SignalObjectAndWait(event1, event2, 5000, false)); + EXPECT_EQ(WAIT_OBJECT_0, ::SignalObjectAndWait(event1, event2, 5000, false)); + + EXPECT_TRUE(thread_pool.UnRegisterWaits(this)); + EXPECT_EQ(0u, thread_pool.OutstandingWaits()); + + EXPECT_EQ(static_cast<DWORD>(WAIT_TIMEOUT), + ::SignalObjectAndWait(event1, event2, 1000, false)); + + EXPECT_TRUE(::CloseHandle(event1)); + EXPECT_TRUE(::CloseHandle(event2)); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.cc b/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.cc new file mode 100644 index 0000000000..3c8f8e25e5 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.cc @@ -0,0 +1,178 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/top_level_dispatcher.h" + +#include <stdint.h> +#include <string.h> + +#include "base/logging.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/filesystem_dispatcher.h" +#include "sandbox/win/src/handle_dispatcher.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/line_break_dispatcher.h" +#include "sandbox/win/src/named_pipe_dispatcher.h" +#include "sandbox/win/src/process_mitigations_win32k_dispatcher.h" +#include "sandbox/win/src/process_thread_dispatcher.h" +#include "sandbox/win/src/registry_dispatcher.h" +#include "sandbox/win/src/sandbox_policy_base.h" +#include "sandbox/win/src/signed_dispatcher.h" +#include "sandbox/win/src/sync_dispatcher.h" + +namespace sandbox { + +TopLevelDispatcher::TopLevelDispatcher(PolicyBase* policy) : policy_(policy) { + // Initialize the IPC dispatcher array. + memset(ipc_targets_, 0, sizeof(ipc_targets_)); + Dispatcher* dispatcher; + + dispatcher = new FilesystemDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::NTCREATEFILE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENFILE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTSETINFO_RENAME)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTQUERYATTRIBUTESFILE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTQUERYFULLATTRIBUTESFILE)] = + dispatcher; + filesystem_dispatcher_.reset(dispatcher); + + dispatcher = new NamedPipeDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::CREATENAMEDPIPEW)] = dispatcher; + named_pipe_dispatcher_.reset(dispatcher); + + dispatcher = new ThreadProcessDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENTHREAD)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENPROCESS)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::CREATEPROCESSW)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENPROCESSTOKEN)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENPROCESSTOKENEX)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::CREATETHREAD)] = dispatcher; + thread_process_dispatcher_.reset(dispatcher); + + dispatcher = new SyncDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::CREATEEVENT)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::OPENEVENT)] = dispatcher; + sync_dispatcher_.reset(dispatcher); + + dispatcher = new RegistryDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::NTCREATEKEY)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::NTOPENKEY)] = dispatcher; + registry_dispatcher_.reset(dispatcher); + + dispatcher = new HandleDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::DUPLICATEHANDLEPROXY)] = dispatcher; + handle_dispatcher_.reset(dispatcher); + + dispatcher = new ProcessMitigationsWin32KDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GDIDLLINITIALIZE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETSTOCKOBJECT)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::USER_REGISTERCLASSW)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::USER_ENUMDISPLAYMONITORS)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::USER_ENUMDISPLAYDEVICES)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::USER_GETMONITORINFO)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_CREATEOPMPROTECTEDOUTPUTS)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETCERTIFICATE)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETCERTIFICATESIZE)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_DESTROYOPMPROTECTEDOUTPUT)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_CONFIGUREOPMPROTECTEDOUTPUT)] = + dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETOPMINFORMATION)] = dispatcher; + ipc_targets_[static_cast<size_t>(IpcTag::GDI_GETOPMRANDOMNUMBER)] = + dispatcher; + ipc_targets_[static_cast<size_t>( + IpcTag::GDI_GETSUGGESTEDOPMPROTECTEDOUTPUTARRAYSIZE)] = dispatcher; + ipc_targets_[static_cast<size_t>( + IpcTag::GDI_SETOPMSIGNINGKEYANDSEQUENCENUMBERS)] = dispatcher; + process_mitigations_win32k_dispatcher_.reset(dispatcher); + + dispatcher = new SignedDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::NTCREATESECTION)] = dispatcher; + signed_dispatcher_.reset(dispatcher); + + dispatcher = new LineBreakDispatcher(policy_); + ipc_targets_[static_cast<size_t>(IpcTag::GETCOMPLEXLINEBREAKS)] = dispatcher; + line_break_dispatcher_.reset(dispatcher); +} + +TopLevelDispatcher::~TopLevelDispatcher() {} + +// When an IPC is ready in any of the targets we get called. We manage an array +// of IPC dispatchers which are keyed on the IPC tag so we normally delegate +// to the appropriate dispatcher unless we can handle the IPC call ourselves. +Dispatcher* TopLevelDispatcher::OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) { + DCHECK(callback); + static const IPCParams ping1 = {IpcTag::PING1, {UINT32_TYPE}}; + static const IPCParams ping2 = {IpcTag::PING2, {INOUTPTR_TYPE}}; + + if (ping1.Matches(ipc) || ping2.Matches(ipc)) { + *callback = reinterpret_cast<CallbackGeneric>( + static_cast<Callback1>(&TopLevelDispatcher::Ping)); + return this; + } + + Dispatcher* dispatcher = GetDispatcher(ipc->ipc_tag); + if (!dispatcher) { + NOTREACHED(); + return nullptr; + } + return dispatcher->OnMessageReady(ipc, callback); +} + +// Delegate to the appropriate dispatcher. +bool TopLevelDispatcher::SetupService(InterceptionManager* manager, + IpcTag service) { + if (IpcTag::PING1 == service || IpcTag::PING2 == service) + return true; + + Dispatcher* dispatcher = GetDispatcher(service); + if (!dispatcher) { + NOTREACHED(); + return false; + } + return dispatcher->SetupService(manager, service); +} + +// We service PING message which is a way to test a round trip of the +// IPC subsystem. We receive a integer cookie and we are expected to return the +// cookie times two (or three) and the current tick count. +bool TopLevelDispatcher::Ping(IPCInfo* ipc, void* arg1) { + switch (ipc->ipc_tag) { + case IpcTag::PING1: { + IPCInt ipc_int(arg1); + uint32_t cookie = ipc_int.As32Bit(); + ipc->return_info.extended_count = 2; + ipc->return_info.extended[0].unsigned_int = ::GetTickCount(); + ipc->return_info.extended[1].unsigned_int = 2 * cookie; + return true; + } + case IpcTag::PING2: { + CountedBuffer* io_buffer = reinterpret_cast<CountedBuffer*>(arg1); + if (sizeof(uint32_t) != io_buffer->Size()) + return false; + + uint32_t* cookie = reinterpret_cast<uint32_t*>(io_buffer->Buffer()); + *cookie = (*cookie) * 3; + return true; + } + default: + return false; + } +} + +Dispatcher* TopLevelDispatcher::GetDispatcher(IpcTag ipc_tag) { + if (ipc_tag >= IpcTag::LAST || ipc_tag <= IpcTag::UNUSED) + return nullptr; + + return ipc_targets_[static_cast<size_t>(ipc_tag)]; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.h b/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.h new file mode 100644 index 0000000000..a80ce1ef5d --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/top_level_dispatcher.h @@ -0,0 +1,54 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_TOP_LEVEL_DISPATCHER_H__ +#define SANDBOX_SRC_TOP_LEVEL_DISPATCHER_H__ + +#include <memory> + +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" +#include "sandbox/win/src/interception.h" +#include "sandbox/win/src/ipc_tags.h" +#include "sandbox/win/src/sandbox_policy_base.h" + +namespace sandbox { + +// Top level dispatcher which hands requests to the appropriate service +// dispatchers. +class TopLevelDispatcher : public Dispatcher { + public: + // |policy| must outlive this class. + explicit TopLevelDispatcher(PolicyBase* policy); + ~TopLevelDispatcher() override; + + Dispatcher* OnMessageReady(IPCParams* ipc, + CallbackGeneric* callback) override; + bool SetupService(InterceptionManager* manager, IpcTag service) override; + + private: + // Test IPC provider. + bool Ping(IPCInfo* ipc, void* cookie); + + // Returns a dispatcher from ipc_targets_. + Dispatcher* GetDispatcher(IpcTag ipc_tag); + + PolicyBase* policy_; + std::unique_ptr<Dispatcher> filesystem_dispatcher_; + std::unique_ptr<Dispatcher> named_pipe_dispatcher_; + std::unique_ptr<Dispatcher> thread_process_dispatcher_; + std::unique_ptr<Dispatcher> sync_dispatcher_; + std::unique_ptr<Dispatcher> registry_dispatcher_; + std::unique_ptr<Dispatcher> handle_dispatcher_; + std::unique_ptr<Dispatcher> process_mitigations_win32k_dispatcher_; + std::unique_ptr<Dispatcher> signed_dispatcher_; + std::unique_ptr<Dispatcher> line_break_dispatcher_; + Dispatcher* ipc_targets_[kMaxIpcTag]; + + DISALLOW_COPY_AND_ASSIGN(TopLevelDispatcher); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_TOP_LEVEL_DISPATCHER_H__ diff --git a/security/sandbox/chromium/sandbox/win/src/unload_dll_test.cc b/security/sandbox/chromium/sandbox/win/src/unload_dll_test.cc new file mode 100644 index 0000000000..0acb178987 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/unload_dll_test.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/win/scoped_handle.h" +#include "build/build_config.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "sandbox/win/src/target_services.h" +#include "sandbox/win/tests/common/controller.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +// Loads and or unloads a DLL passed in the second parameter of argv. +// The first parameter of argv is 'L' = load, 'U' = unload or 'B' for both. +SBOX_TESTS_COMMAND int UseOneDLL(int argc, wchar_t** argv) { + if (argc != 2) + return SBOX_TEST_FAILED_TO_RUN_TEST; + int rv = SBOX_TEST_FAILED_TO_RUN_TEST; + + wchar_t option = (argv[0])[0]; + if ((option == L'L') || (option == L'B')) { + HMODULE module1 = ::LoadLibraryW(argv[1]); + rv = (!module1) ? SBOX_TEST_FAILED : SBOX_TEST_SUCCEEDED; + } + + if ((option == L'U') || (option == L'B')) { + HMODULE module2 = ::GetModuleHandleW(argv[1]); + rv = ::FreeLibrary(module2) ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; + } + return rv; +} + +// Opens an event passed as the first parameter of argv. +SBOX_TESTS_COMMAND int SimpleOpenEvent(int argc, wchar_t** argv) { + if (argc != 1) + return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; + + base::win::ScopedHandle event_open(::OpenEvent(SYNCHRONIZE, false, argv[0])); + return event_open.Get() ? SBOX_TEST_SUCCEEDED : SBOX_TEST_FAILED; +} + +// Fails on Windows ARM64: https://crbug.com/905526 +#if defined(ARCH_CPU_ARM64) +#define MAYBE_BaselineAvicapDll DISABLED_BaselineAvicapDll +#else +#define MAYBE_BaselineAvicapDll BaselineAvicapDll +#endif +TEST(UnloadDllTest, MAYBE_BaselineAvicapDll) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + runner.SetTimeout(2000); + // Add a sync rule, because that ensures that the interception agent has + // more than one item in its internal table. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"t0001")); + + // Note for the puzzled: avicap32.dll is a 64-bit dll in 64-bit versions of + // windows so this test and the others just work. + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"UseOneDLL B avicap32.dll")); +} + +TEST(UnloadDllTest, UnloadAviCapDllNoPatching) { + TestRunner runner; + runner.SetTestState(BEFORE_REVERT); + runner.SetTimeout(2000); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + policy->AddDllToUnload(L"avicap32.dll"); + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL B avicap32.dll")); +} + +TEST(UnloadDllTest, UnloadAviCapDllWithPatching) { + TestRunner runner; + runner.SetTimeout(2000); + runner.SetTestState(BEFORE_REVERT); + sandbox::TargetPolicy* policy = runner.GetPolicy(); + policy->AddDllToUnload(L"avicap32.dll"); + + base::win::ScopedHandle handle1( + ::CreateEvent(nullptr, false, false, L"tst0001")); + + // Add a couple of rules that ensures that the interception agent add EAT + // patching on the client which makes sure that the unload dll record does + // not interact badly with them. + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_REGISTRY, + TargetPolicy::REG_ALLOW_ANY, + L"HKEY_LOCAL_MACHINE\\Software\\Microsoft")); + EXPECT_TRUE(runner.AddRule(TargetPolicy::SUBSYS_SYNC, + TargetPolicy::EVENTS_ALLOW_ANY, L"tst0001")); + + EXPECT_EQ(SBOX_TEST_FAILED, runner.RunTest(L"UseOneDLL L avicap32.dll")); + + runner.SetTestState(AFTER_REVERT); + EXPECT_EQ(SBOX_TEST_SUCCEEDED, runner.RunTest(L"SimpleOpenEvent tst0001")); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.cc b/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.cc new file mode 100644 index 0000000000..49cc68bb00 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/win2k_threadpool.h" + +#include <stddef.h> + +#include "sandbox/win/src/win_utils.h" + +namespace sandbox { + +Win2kThreadPool::Win2kThreadPool() { + ::InitializeCriticalSection(&lock_); +} + +bool Win2kThreadPool::RegisterWait(const void* cookie, + HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) { + if (0 == cookie) { + return false; + } + HANDLE pool_object = nullptr; + // create a wait for a kernel object, with no timeout + if (!::RegisterWaitForSingleObject(&pool_object, waitable_object, callback, + context, INFINITE, WT_EXECUTEDEFAULT)) { + return false; + } + PoolObject pool_obj = {cookie, pool_object}; + AutoLock lock(&lock_); + pool_objects_.push_back(pool_obj); + return true; +} + +bool Win2kThreadPool::UnRegisterWaits(void* cookie) { + if (0 == cookie) { + return false; + } + AutoLock lock(&lock_); + bool success = true; + PoolObjects::iterator it = pool_objects_.begin(); + while (it != pool_objects_.end()) { + if (it->cookie == cookie) { + HANDLE wait = it->wait; + it = pool_objects_.erase(it); + success &= (::UnregisterWaitEx(wait, INVALID_HANDLE_VALUE) != 0); + } else { + ++it; + } + } + return success; +} + +size_t Win2kThreadPool::OutstandingWaits() { + AutoLock lock(&lock_); + return pool_objects_.size(); +} + +Win2kThreadPool::~Win2kThreadPool() { + // Here we used to unregister all the pool wait handles. Now, following the + // rest of the code we avoid lengthy or blocking calls given that the process + // is being torn down. + ::DeleteCriticalSection(&lock_); +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.h b/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.h new file mode 100644 index 0000000000..c4d539dd7f --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win2k_threadpool.h @@ -0,0 +1,61 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WIN2K_THREADPOOL_H_ +#define SANDBOX_SRC_WIN2K_THREADPOOL_H_ + +#include <stddef.h> + +#include <algorithm> +#include <list> +#include "base/macros.h" +#include "sandbox/win/src/crosscall_server.h" + +namespace sandbox { + +// Win2kThreadPool a simple implementation of a thread provider as required +// for the sandbox IPC subsystem. See sandbox\crosscall_server.h for the details +// and requirements of this interface. +// +// Implementing the thread provider as a thread pool is desirable in the case +// of shared memory IPC because it can generate a large number of waitable +// events: as many as channels. A thread pool does not create a thread per +// event, instead maintains a few idle threads but can create more if the need +// arises. +// +// This implementation simply thunks to the nice thread pool API of win2k. +class Win2kThreadPool : public ThreadProvider { + public: + Win2kThreadPool(); + ~Win2kThreadPool() override; + + bool RegisterWait(const void* cookie, + HANDLE waitable_object, + CrossCallIPCCallback callback, + void* context) override; + + bool UnRegisterWaits(void* cookie) override; + + // Returns the total number of wait objects associated with + // the thread pool. + size_t OutstandingWaits(); + + private: + // record to keep track of a wait and its associated cookie. + struct PoolObject { + const void* cookie; + HANDLE wait; + }; + // The list of pool wait objects. + typedef std::list<PoolObject> PoolObjects; + PoolObjects pool_objects_; + // This lock protects the list of pool wait objects. + CRITICAL_SECTION lock_; + + DISALLOW_COPY_AND_ASSIGN(Win2kThreadPool); +}; + +} // namespace sandbox + +#endif // SANDBOX_SRC_WIN2K_THREADPOOL_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/win_utils.cc b/security/sandbox/chromium/sandbox/win/src/win_utils.cc new file mode 100644 index 0000000000..8fb345a239 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win_utils.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/win_utils.h" + +#include <psapi.h> +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/numerics/safe_math.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/win/pe_image.h" +#include "sandbox/win/src/internal_types.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/src/sandbox_nt_util.h" + +namespace { + +const size_t kDriveLetterLen = 3; + +constexpr wchar_t kNTDotPrefix[] = L"\\\\.\\"; +const size_t kNTDotPrefixLen = base::size(kNTDotPrefix) - 1; + +// Holds the information about a known registry key. +struct KnownReservedKey { + const wchar_t* name; + HKEY key; +}; + +// Contains all the known registry key by name and by handle. +const KnownReservedKey kKnownKey[] = { + {L"HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT}, + {L"HKEY_CURRENT_USER", HKEY_CURRENT_USER}, + {L"HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE}, + {L"HKEY_USERS", HKEY_USERS}, + {L"HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA}, + {L"HKEY_PERFORMANCE_TEXT", HKEY_PERFORMANCE_TEXT}, + {L"HKEY_PERFORMANCE_NLSTEXT", HKEY_PERFORMANCE_NLSTEXT}, + {L"HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG}, + {L"HKEY_DYN_DATA", HKEY_DYN_DATA}}; + +// These functions perform case independent path comparisons. +bool EqualPath(const std::wstring& first, const std::wstring& second) { + return _wcsicmp(first.c_str(), second.c_str()) == 0; +} + +bool EqualPath(const std::wstring& first, + size_t first_offset, + const std::wstring& second, + size_t second_offset) { + return _wcsicmp(first.c_str() + first_offset, + second.c_str() + second_offset) == 0; +} + +bool EqualPath(const std::wstring& first, + const wchar_t* second, + size_t second_len) { + return _wcsnicmp(first.c_str(), second, second_len) == 0; +} + +bool EqualPath(const std::wstring& first, + size_t first_offset, + const wchar_t* second, + size_t second_len) { + return _wcsnicmp(first.c_str() + first_offset, second, second_len) == 0; +} + +// Returns true if |path| starts with "\??\" and returns a path without that +// component. +bool IsNTPath(const std::wstring& path, std::wstring* trimmed_path) { + if ((path.size() < sandbox::kNTPrefixLen) || + !EqualPath(path, sandbox::kNTPrefix, sandbox::kNTPrefixLen)) { + *trimmed_path = path; + return false; + } + + *trimmed_path = path.substr(sandbox::kNTPrefixLen); + return true; +} + +// Returns true if |path| starts with "\Device\" and returns a path without that +// component. +bool IsDevicePath(const std::wstring& path, std::wstring* trimmed_path) { + if ((path.size() < sandbox::kNTDevicePrefixLen) || + (!EqualPath(path, sandbox::kNTDevicePrefix, + sandbox::kNTDevicePrefixLen))) { + *trimmed_path = path; + return false; + } + + *trimmed_path = path.substr(sandbox::kNTDevicePrefixLen); + return true; +} + +// Returns the offset to the path seperator following +// "\Device\HarddiskVolumeX" in |path|. +size_t PassHarddiskVolume(const std::wstring& path) { + static constexpr wchar_t pattern[] = L"\\Device\\HarddiskVolume"; + const size_t patternLen = base::size(pattern) - 1; + + // First, check for |pattern|. + if ((path.size() < patternLen) || (!EqualPath(path, pattern, patternLen))) + return std::wstring::npos; + + // Find the next path separator, after the pattern match. + return path.find_first_of(L'\\', patternLen - 1); +} + +// Returns true if |path| starts with "\Device\HarddiskVolumeX\" and returns a +// path without that component. |removed| will hold the prefix removed. +bool IsDeviceHarddiskPath(const std::wstring& path, + std::wstring* trimmed_path, + std::wstring* removed) { + size_t offset = PassHarddiskVolume(path); + if (offset == std::wstring::npos) + return false; + + // Remove up to and including the path separator. + *removed = path.substr(0, offset + 1); + // Remaining path starts after the path separator. + *trimmed_path = path.substr(offset + 1); + return true; +} + +bool StartsWithDriveLetter(const std::wstring& path) { + if (path.size() < kDriveLetterLen) + return false; + + if (path[1] != L':' || path[2] != L'\\') + return false; + + return base::IsAsciiAlpha(path[0]); +} + +// Removes "\\\\.\\" from the path. +void RemoveImpliedDevice(std::wstring* path) { + if (EqualPath(*path, kNTDotPrefix, kNTDotPrefixLen)) + *path = path->substr(kNTDotPrefixLen); +} + +} // namespace + +namespace sandbox { + +// Returns true if the provided path points to a pipe. +bool IsPipe(const std::wstring& path) { + size_t start = 0; + if (EqualPath(path, sandbox::kNTPrefix, sandbox::kNTPrefixLen)) + start = sandbox::kNTPrefixLen; + + const wchar_t kPipe[] = L"pipe\\"; + if (path.size() < start + base::size(kPipe) - 1) + return false; + + return EqualPath(path, start, kPipe, base::size(kPipe) - 1); +} + +HKEY GetReservedKeyFromName(const std::wstring& name) { + for (size_t i = 0; i < base::size(kKnownKey); ++i) { + if (name == kKnownKey[i].name) + return kKnownKey[i].key; + } + + return nullptr; +} + +bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name) { + for (size_t i = 0; i < base::size(kKnownKey); ++i) { + if (name.find(kKnownKey[i].name) == 0) { + HKEY key; + DWORD disposition; + if (ERROR_SUCCESS != ::RegCreateKeyEx(kKnownKey[i].key, L"", 0, nullptr, + 0, MAXIMUM_ALLOWED, nullptr, &key, + &disposition)) + return false; + + bool result = GetPathFromHandle(key, resolved_name); + ::RegCloseKey(key); + + if (!result) + return false; + + *resolved_name += name.substr(wcslen(kKnownKey[i].name)); + return true; + } + } + + return false; +} + +// |full_path| can have any of the following forms: +// \??\c:\some\foo\bar +// \Device\HarddiskVolume0\some\foo\bar +// \??\HarddiskVolume0\some\foo\bar +// \??\UNC\SERVER\Share\some\foo\bar +DWORD IsReparsePoint(const std::wstring& full_path) { + // Check if it's a pipe. We can't query the attributes of a pipe. + if (IsPipe(full_path)) + return ERROR_NOT_A_REPARSE_POINT; + + std::wstring path; + bool nt_path = IsNTPath(full_path, &path); + bool has_drive = StartsWithDriveLetter(path); + bool is_device_path = IsDevicePath(path, &path); + + if (!has_drive && !is_device_path && !nt_path) + return ERROR_INVALID_NAME; + + if (!has_drive) { + // Add Win32 device namespace prefix, required for some Windows APIs. + path.insert(0, kNTDotPrefix); + } + + // Ensure that volume path matches start of path. + wchar_t vol_path[MAX_PATH]; + if (!::GetVolumePathNameW(path.c_str(), vol_path, MAX_PATH)) { + // This will fail if this is a device that isn't volume related, which can't + // then be a reparse point. + return is_device_path ? ERROR_NOT_A_REPARSE_POINT : ERROR_INVALID_NAME; + } + + // vol_path includes a trailing slash, so reduce size for path and loop check. + size_t vol_path_len = wcslen(vol_path) - 1; + if (!EqualPath(path, vol_path, vol_path_len)) { + return ERROR_INVALID_NAME; + } + + do { + DWORD attributes = ::GetFileAttributes(path.c_str()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + DWORD error = ::GetLastError(); + if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND && + error != ERROR_INVALID_FUNCTION && + error != ERROR_INVALID_NAME) { + // Unexpected error. + return error; + } + } else if (FILE_ATTRIBUTE_REPARSE_POINT & attributes) { + // This is a reparse point. + return ERROR_SUCCESS; + } + + path.resize(path.rfind(L'\\')); + } while (path.size() > vol_path_len); // Skip root dir. + + return ERROR_NOT_A_REPARSE_POINT; +} + +// We get a |full_path| of the forms accepted by IsReparsePoint(), and the name +// we'll get from |handle| will be \device\harddiskvolume1\some\foo\bar. +bool SameObject(HANDLE handle, const wchar_t* full_path) { + // Check if it's a pipe. + if (IsPipe(full_path)) + return true; + + std::wstring actual_path; + if (!GetPathFromHandle(handle, &actual_path)) + return false; + + std::wstring path(full_path); + DCHECK_NT(!path.empty()); + + // This may end with a backslash. + if (path.back() == L'\\') { + path.pop_back(); + } + + // Perfect match (case-insensitive check). + if (EqualPath(actual_path, path)) + return true; + + bool nt_path = IsNTPath(path, &path); + bool has_drive = StartsWithDriveLetter(path); + + if (!has_drive && nt_path) { + std::wstring simple_actual_path; + if (IsDevicePath(path, &path)) { + if (IsDevicePath(actual_path, &simple_actual_path)) { + // Perfect match (case-insensitive check). + return (EqualPath(simple_actual_path, path)); + } else { + return false; + } + } else { + // Add Win32 device namespace for GetVolumePathName. + path.insert(0, kNTDotPrefix); + } + } + + // Get the volume path in the same format as actual_path. + wchar_t vol_path[MAX_PATH]; + if (!::GetVolumePathName(path.c_str(), vol_path, MAX_PATH)) { + return false; + } + size_t vol_path_len = wcslen(vol_path); + base::string16 nt_vol; + if (!GetNtPathFromWin32Path(vol_path, &nt_vol)) { + return false; + } + + // The two paths should be the same length. + if (nt_vol.size() + path.size() - vol_path_len != actual_path.size()) { + return false; + } + + // Check the volume matches. + if (!EqualPath(actual_path, nt_vol.c_str(), nt_vol.size())) { + return false; + } + + // Check the path after the volume matches. + if (!EqualPath(actual_path, nt_vol.size(), path, vol_path_len)) { + return false; + } + + return true; +} + +// Just make a best effort here. There are lots of corner cases that we're +// not expecting - and will fail to make long. +bool ConvertToLongPath(std::wstring* native_path, + const std::wstring* drive_letter) { + if (IsPipe(*native_path)) + return true; + + bool is_device_harddisk_path = false; + bool is_nt_path = false; + bool added_implied_device = false; + std::wstring temp_path; + std::wstring to_restore; + + // Process a few prefix types. + if (IsNTPath(*native_path, &temp_path)) { + // "\??\" + if (!StartsWithDriveLetter(temp_path)) { + // Prepend with "\\.\". + temp_path = std::wstring(kNTDotPrefix) + temp_path; + added_implied_device = true; + } + is_nt_path = true; + } else if (IsDeviceHarddiskPath(*native_path, &temp_path, &to_restore)) { + // "\Device\HarddiskVolumeX\" - hacky attempt making ::GetLongPathName + // work for native device paths. Remove "\Device\HarddiskVolumeX\" and + // replace with drive letter. + + // Nothing we can do if we don't have a drive letter. Leave |native_path| + // as is. + if (!drive_letter || drive_letter->empty()) + return false; + temp_path = *drive_letter + temp_path; + is_device_harddisk_path = true; + } else if (IsDevicePath(*native_path, &temp_path)) { + // "\Device\" - there's nothing we can do to convert to long here. + return false; + } + + DWORD size = MAX_PATH; + std::unique_ptr<wchar_t[]> long_path_buf(new wchar_t[size]); + + DWORD return_value = + ::GetLongPathName(temp_path.c_str(), long_path_buf.get(), size); + while (return_value >= size) { + size *= 2; + long_path_buf.reset(new wchar_t[size]); + return_value = + ::GetLongPathName(temp_path.c_str(), long_path_buf.get(), size); + } + + DWORD last_error = ::GetLastError(); + if (0 == return_value && (ERROR_FILE_NOT_FOUND == last_error || + ERROR_PATH_NOT_FOUND == last_error || + ERROR_INVALID_NAME == last_error)) { + // The file does not exist, but maybe a sub path needs to be expanded. + std::wstring::size_type last_slash = temp_path.rfind(L'\\'); + if (std::wstring::npos == last_slash) + return false; + + std::wstring begin = temp_path.substr(0, last_slash); + std::wstring end = temp_path.substr(last_slash); + if (!ConvertToLongPath(&begin)) + return false; + + // Ok, it worked. Let's reset the return value. + temp_path = begin + end; + return_value = 1; + } else if (0 != return_value) { + temp_path = long_path_buf.get(); + } + + // If successful, re-apply original namespace prefix before returning. + if (return_value != 0) { + if (added_implied_device) + RemoveImpliedDevice(&temp_path); + + if (is_nt_path) { + *native_path = kNTPrefix; + *native_path += temp_path; + } else if (is_device_harddisk_path) { + // Remove the added drive letter. + temp_path = temp_path.substr(kDriveLetterLen); + *native_path = to_restore; + *native_path += temp_path; + } else { + *native_path = temp_path; + } + + return true; + } + + return false; +} + +bool GetPathFromHandle(HANDLE handle, std::wstring* path) { + NtQueryObjectFunction NtQueryObject = nullptr; + ResolveNTFunctionPtr("NtQueryObject", &NtQueryObject); + + OBJECT_NAME_INFORMATION initial_buffer; + OBJECT_NAME_INFORMATION* name = &initial_buffer; + ULONG size = sizeof(initial_buffer); + // Query the name information a first time to get the size of the name. + // Windows XP requires that the size of the buffer passed in here be != 0. + NTSTATUS status = + NtQueryObject(handle, ObjectNameInformation, name, size, &size); + + std::unique_ptr<BYTE[]> name_ptr; + if (size) { + name_ptr.reset(new BYTE[size]); + name = reinterpret_cast<OBJECT_NAME_INFORMATION*>(name_ptr.get()); + + // Query the name information a second time to get the name of the + // object referenced by the handle. + status = NtQueryObject(handle, ObjectNameInformation, name, size, &size); + } + + if (STATUS_SUCCESS != status) + return false; + + path->assign(name->ObjectName.Buffer, + name->ObjectName.Length / sizeof(name->ObjectName.Buffer[0])); + return true; +} + +bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path) { + HANDLE file = ::CreateFileW( + path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (file == INVALID_HANDLE_VALUE) + return false; + bool rv = GetPathFromHandle(file, nt_path); + ::CloseHandle(file); + return rv; +} + +bool WriteProtectedChildMemory(HANDLE child_process, + void* address, + const void* buffer, + size_t length, + DWORD writeProtection) { + // First, remove the protections. + DWORD old_protection; + if (!::VirtualProtectEx(child_process, address, length, writeProtection, + &old_protection)) + return false; + + SIZE_T written; + bool ok = + ::WriteProcessMemory(child_process, address, buffer, length, &written) && + (length == written); + + // Always attempt to restore the original protection. + if (!::VirtualProtectEx(child_process, address, length, old_protection, + &old_protection)) + return false; + + return ok; +} + +bool CopyToChildMemory(HANDLE child, + const void* local_buffer, + size_t buffer_bytes, + void** remote_buffer) { + DCHECK(remote_buffer); + if (0 == buffer_bytes) { + *remote_buffer = nullptr; + return true; + } + + // Allocate memory in the target process without specifying the address + void* remote_data = ::VirtualAllocEx(child, nullptr, buffer_bytes, MEM_COMMIT, + PAGE_READWRITE); + if (!remote_data) + return false; + + SIZE_T bytes_written; + bool success = ::WriteProcessMemory(child, remote_data, local_buffer, + buffer_bytes, &bytes_written); + if (!success || bytes_written != buffer_bytes) { + ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); + return false; + } + + *remote_buffer = remote_data; + + return true; +} + +DWORD GetLastErrorFromNtStatus(NTSTATUS status) { + RtlNtStatusToDosErrorFunction NtStatusToDosError = nullptr; + ResolveNTFunctionPtr("RtlNtStatusToDosError", &NtStatusToDosError); + return NtStatusToDosError(status); +} + +// This function uses the undocumented PEB ImageBaseAddress field to extract +// the base address of the new process. +void* GetProcessBaseAddress(HANDLE process) { + NtQueryInformationProcessFunction query_information_process = nullptr; + ResolveNTFunctionPtr("NtQueryInformationProcess", &query_information_process); + if (!query_information_process) + return nullptr; + PROCESS_BASIC_INFORMATION process_basic_info = {}; + NTSTATUS status = query_information_process( + process, ProcessBasicInformation, &process_basic_info, + sizeof(process_basic_info), nullptr); + if (STATUS_SUCCESS != status) + return nullptr; + + PEB peb = {}; + SIZE_T bytes_read = 0; + if (!::ReadProcessMemory(process, process_basic_info.PebBaseAddress, &peb, + sizeof(peb), &bytes_read) || + (sizeof(peb) != bytes_read)) { + return nullptr; + } + + void* base_address = peb.ImageBaseAddress; + char magic[2] = {}; + if (!::ReadProcessMemory(process, base_address, magic, sizeof(magic), + &bytes_read) || + (sizeof(magic) != bytes_read)) { + return nullptr; + } + + if (magic[0] != 'M' || magic[1] != 'Z') + return nullptr; + +#if defined(_M_ARM64) + // Windows 10 on ARM64 has multi-threaded DLL loading that does not work with + // the sandbox. (On x86 this gets disabled by hook detection code that was not + // ported to ARM64). This overwrites the LoaderThreads value in the process + // parameters part of the PEB, if it is set to the default of 0 (which + // actually means it defaults to 4 loading threads). This is an undocumented + // field so there is a, probably small, risk that it might change or move in + // the future. In order to slightly guard against that we only update if the + // value is currently 0. + auto processParameters = reinterpret_cast<uint8_t*>(peb.ProcessParameters); + const uint32_t loaderThreadsOffset = 0x40c; + uint32_t maxLoaderThreads = 0; + BOOL memoryRead = ::ReadProcessMemory( + process, processParameters + loaderThreadsOffset, &maxLoaderThreads, + sizeof(maxLoaderThreads), &bytes_read); + if (memoryRead && (sizeof(maxLoaderThreads) == bytes_read) && + (maxLoaderThreads == 0)) { + maxLoaderThreads = 1; + WriteProtectedChildMemory(process, processParameters + loaderThreadsOffset, + &maxLoaderThreads, sizeof(maxLoaderThreads), + PAGE_READWRITE); + } +#endif + + return base_address; +} + +DWORD GetTokenInformation(HANDLE token, + TOKEN_INFORMATION_CLASS info_class, + std::unique_ptr<BYTE[]>* buffer) { + // Get the required buffer size. + DWORD size = 0; + ::GetTokenInformation(token, info_class, nullptr, 0, &size); + if (!size) { + return ::GetLastError(); + } + + auto temp_buffer = std::make_unique<BYTE[]>(size); + if (!::GetTokenInformation(token, info_class, temp_buffer.get(), size, + &size)) { + return ::GetLastError(); + } + + *buffer = std::move(temp_buffer); + return ERROR_SUCCESS; +} + +} // namespace sandbox + +void ResolveNTFunctionPtr(const char* name, void* ptr) { + static volatile HMODULE ntdll = nullptr; + + if (!ntdll) { + HMODULE ntdll_local = ::GetModuleHandle(sandbox::kNtdllName); + // Use PEImage to sanity-check that we have a valid ntdll handle. + base::win::PEImage ntdll_peimage(ntdll_local); + CHECK_NT(ntdll_peimage.VerifyMagic()); + // Race-safe way to set static ntdll. + ::InterlockedCompareExchangePointer( + reinterpret_cast<PVOID volatile*>(&ntdll), ntdll_local, nullptr); + } + + CHECK_NT(ntdll); + FARPROC* function_ptr = reinterpret_cast<FARPROC*>(ptr); + *function_ptr = ::GetProcAddress(ntdll, name); + CHECK_NT(*function_ptr); +} diff --git a/security/sandbox/chromium/sandbox/win/src/win_utils.h b/security/sandbox/chromium/sandbox/win/src/win_utils.h new file mode 100644 index 0000000000..7c848f2f08 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win_utils.h @@ -0,0 +1,156 @@ +// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WIN_UTILS_H_ +#define SANDBOX_SRC_WIN_UTILS_H_ + +#include <stddef.h> +#include <windows.h> +#include <memory> +#include <string> + +#include "base/macros.h" +#include "base/stl_util.h" +#include "sandbox/win/src/nt_internals.h" + +namespace sandbox { + +// Prefix for path used by NT calls. +const wchar_t kNTPrefix[] = L"\\??\\"; +const size_t kNTPrefixLen = base::size(kNTPrefix) - 1; + +const wchar_t kNTDevicePrefix[] = L"\\Device\\"; +const size_t kNTDevicePrefixLen = base::size(kNTDevicePrefix) - 1; + +// Automatically acquires and releases a lock when the object is +// is destroyed. +class AutoLock { + public: + // Acquires the lock. + explicit AutoLock(CRITICAL_SECTION* lock) : lock_(lock) { + ::EnterCriticalSection(lock); + } + + // Releases the lock; + ~AutoLock() { ::LeaveCriticalSection(lock_); } + + private: + CRITICAL_SECTION* lock_; + DISALLOW_IMPLICIT_CONSTRUCTORS(AutoLock); +}; + +// Basic implementation of a singleton which calls the destructor +// when the exe is shutting down or the DLL is being unloaded. +template <typename Derived> +class SingletonBase { + public: + static Derived* GetInstance() { + static Derived* instance = nullptr; + if (!instance) { + instance = new Derived(); + // Microsoft CRT extension. In an exe this this called after + // winmain returns, in a dll is called in DLL_PROCESS_DETACH + _onexit(OnExit); + } + return instance; + } + + private: + // this is the function that gets called by the CRT when the + // process is shutting down. + static int __cdecl OnExit() { + delete GetInstance(); + return 0; + } +}; + +// Function object which invokes LocalFree on its parameter, which must be +// a pointer. Can be used to store LocalAlloc pointers in std::unique_ptr: +// +// std::unique_ptr<int, sandbox::LocalFreeDeleter> foo_ptr( +// static_cast<int*>(LocalAlloc(LMEM_FIXED, sizeof(int)))); +struct LocalFreeDeleter { + inline void operator()(void* ptr) const { ::LocalFree(ptr); } +}; + +// Convert a short path (C:\path~1 or \\??\\c:\path~1) to the long version of +// the path. If the path is not a valid filesystem path, the function returns +// false and argument is not modified. +// - If passing in a short native device path (\Device\HarddiskVolumeX\path~1), +// a drive letter string (c:\) must also be provided. +bool ConvertToLongPath(std::wstring* path, + const std::wstring* drive_letter = nullptr); + +// Returns ERROR_SUCCESS if the path contains a reparse point, +// ERROR_NOT_A_REPARSE_POINT if there's no reparse point in this path, or an +// error code when the function fails. +// This function is not smart. It looks for each element in the path and +// returns true if any of them is a reparse point. +DWORD IsReparsePoint(const std::wstring& full_path); + +// Returns true if the handle corresponds to the object pointed by this path. +bool SameObject(HANDLE handle, const wchar_t* full_path); + +// Resolves a handle to an nt path. Returns true if the handle can be resolved. +bool GetPathFromHandle(HANDLE handle, std::wstring* path); + +// Resolves a win32 path to an nt path using GetPathFromHandle. The path must +// exist. Returs true if the translation was succesful. +bool GetNtPathFromWin32Path(const std::wstring& path, std::wstring* nt_path); + +// Translates a reserved key name to its handle. +// For example "HKEY_LOCAL_MACHINE" returns HKEY_LOCAL_MACHINE. +// Returns nullptr if the name does not represent any reserved key name. +HKEY GetReservedKeyFromName(const std::wstring& name); + +// Resolves a user-readable registry path to a system-readable registry path. +// For example, HKEY_LOCAL_MACHINE\\Software\\microsoft is translated to +// \\registry\\machine\\software\\microsoft. Returns false if the path +// cannot be resolved. +bool ResolveRegistryName(std::wstring name, std::wstring* resolved_name); + +// Writes |length| bytes from the provided |buffer| into the address space of +// |child_process|, at the specified |address|, preserving the original write +// protection attributes. Returns true on success. +bool WriteProtectedChildMemory(HANDLE child_process, + void* address, + const void* buffer, + size_t length, + DWORD writeProtection = PAGE_WRITECOPY); + +// Allocates |buffer_bytes| in child (PAGE_READWRITE) and copies data +// from |local_buffer| in this process into |child|. |remote_buffer| +// contains the address in the chile. If a zero byte copy is +// requested |true| is returned and no allocation or copying is +// attempted. Returns false if allocation or copying fails. If +// copying fails, the allocation will be reversed. +bool CopyToChildMemory(HANDLE child, + const void* local_buffer, + size_t buffer_bytes, + void** remote_buffer); + +// Returns true if the provided path points to a pipe. +bool IsPipe(const std::wstring& path); + +// Converts a NTSTATUS code to a Win32 error code. +DWORD GetLastErrorFromNtStatus(NTSTATUS status); + +// Returns the address of the main exe module in memory taking in account +// address space layout randomization. This uses the process' PEB to extract +// the base address. This should only be called on new, suspended processes. +void* GetProcessBaseAddress(HANDLE process); + +// Calls GetTokenInformation with the desired |info_class| and returns a +// |buffer| and the Win32 error code. +DWORD GetTokenInformation(HANDLE token, + TOKEN_INFORMATION_CLASS info_class, + std::unique_ptr<BYTE[]>* buffer); + +} // namespace sandbox + +// Resolves a function name in NTDLL to a function pointer. The second parameter +// is a pointer to the function pointer. +void ResolveNTFunctionPtr(const char* name, void* ptr); + +#endif // SANDBOX_SRC_WIN_UTILS_H_ diff --git a/security/sandbox/chromium/sandbox/win/src/win_utils_unittest.cc b/security/sandbox/chromium/sandbox/win/src/win_utils_unittest.cc new file mode 100644 index 0000000000..36e7d4dd1e --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/win_utils_unittest.cc @@ -0,0 +1,258 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/win_utils.h" + +#include <windows.h> + +#include <psapi.h> + +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/numerics/safe_conversions.h" +#include "base/path_service.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "sandbox/win/src/nt_internals.h" +#include "sandbox/win/tests/common/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +class ScopedTerminateProcess { + public: + ScopedTerminateProcess(HANDLE process) : process_(process) {} + + ~ScopedTerminateProcess() { ::TerminateProcess(process_, 0); } + + private: + HANDLE process_; +}; + +bool GetModuleList(HANDLE process, std::vector<HMODULE>* result) { + std::vector<HMODULE> modules(256); + DWORD size_needed = 0; + if (EnumProcessModules( + process, &modules[0], + base::checked_cast<DWORD>(modules.size() * sizeof(HMODULE)), + &size_needed)) { + result->assign(modules.begin(), + modules.begin() + (size_needed / sizeof(HMODULE))); + return true; + } + modules.resize(size_needed / sizeof(HMODULE)); + if (EnumProcessModules( + process, &modules[0], + base::checked_cast<DWORD>(modules.size() * sizeof(HMODULE)), + &size_needed)) { + result->assign(modules.begin(), + modules.begin() + (size_needed / sizeof(HMODULE))); + return true; + } + return false; +} + +} // namespace + +TEST(WinUtils, IsReparsePoint) { + using sandbox::IsReparsePoint; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t my_folder[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, my_folder), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(my_folder)); + ASSERT_TRUE(::CreateDirectory(my_folder, nullptr)); + + EXPECT_EQ(static_cast<DWORD>(ERROR_NOT_A_REPARSE_POINT), + IsReparsePoint(my_folder)); + + std::wstring not_found = std::wstring(my_folder) + L"\\foo\\bar"; + EXPECT_EQ(static_cast<DWORD>(ERROR_NOT_A_REPARSE_POINT), + IsReparsePoint(not_found)); + + std::wstring new_file = std::wstring(my_folder) + L"\\foo"; + EXPECT_EQ(static_cast<DWORD>(ERROR_NOT_A_REPARSE_POINT), + IsReparsePoint(new_file)); + + // Replace the directory with a reparse point to %temp%. + HANDLE dir = ::CreateFile(my_folder, FILE_ALL_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + EXPECT_NE(INVALID_HANDLE_VALUE, dir); + + std::wstring temp_dir_nt = std::wstring(L"\\??\\") + temp_directory; + EXPECT_TRUE(SetReparsePoint(dir, temp_dir_nt.c_str())); + + EXPECT_EQ(static_cast<DWORD>(ERROR_SUCCESS), IsReparsePoint(new_file)); + + EXPECT_TRUE(DeleteReparsePoint(dir)); + EXPECT_TRUE(::CloseHandle(dir)); + EXPECT_TRUE(::RemoveDirectory(my_folder)); +} + +TEST(WinUtils, SameObject) { + using sandbox::SameObject; + + // Create a temp file because we need write access to it. + wchar_t temp_directory[MAX_PATH]; + wchar_t my_folder[MAX_PATH]; + ASSERT_NE(::GetTempPath(MAX_PATH, temp_directory), 0u); + ASSERT_NE(::GetTempFileName(temp_directory, L"test", 0, my_folder), 0u); + + // Delete the file and create a directory instead. + ASSERT_TRUE(::DeleteFile(my_folder)); + ASSERT_TRUE(::CreateDirectory(my_folder, nullptr)); + + std::wstring folder(my_folder); + std::wstring file_name = folder + L"\\foo.txt"; + const ULONG kSharing = FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE; + base::win::ScopedHandle file(CreateFile(file_name.c_str(), GENERIC_WRITE, + kSharing, nullptr, CREATE_ALWAYS, + FILE_FLAG_DELETE_ON_CLOSE, nullptr)); + + EXPECT_TRUE(file.IsValid()); + std::wstring file_name_nt1 = std::wstring(L"\\??\\") + file_name; + std::wstring file_name_nt2 = std::wstring(L"\\??\\") + folder + L"\\FOO.txT"; + EXPECT_TRUE(SameObject(file.Get(), file_name_nt1.c_str())); + EXPECT_TRUE(SameObject(file.Get(), file_name_nt2.c_str())); + + file.Close(); + EXPECT_TRUE(::RemoveDirectory(my_folder)); +} + +TEST(WinUtils, IsPipe) { + using sandbox::IsPipe; + + std::wstring pipe_name = L"\\??\\pipe\\mypipe"; + EXPECT_TRUE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\PiPe\\mypipe"; + EXPECT_TRUE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\pipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\_pipe_\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\??\\ABCD\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + // Written as two strings to prevent trigraph '?' '?' '/'. + pipe_name = + L"/?" + L"?/pipe/mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\XX\\pipe\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); + + pipe_name = L"\\Device\\NamedPipe\\mypipe"; + EXPECT_FALSE(IsPipe(pipe_name)); +} + +TEST(WinUtils, NtStatusToWin32Error) { + using sandbox::GetLastErrorFromNtStatus; + EXPECT_EQ(static_cast<DWORD>(ERROR_SUCCESS), + GetLastErrorFromNtStatus(STATUS_SUCCESS)); + EXPECT_EQ(static_cast<DWORD>(ERROR_NOT_SUPPORTED), + GetLastErrorFromNtStatus(STATUS_NOT_SUPPORTED)); + EXPECT_EQ(static_cast<DWORD>(ERROR_ALREADY_EXISTS), + GetLastErrorFromNtStatus(STATUS_OBJECT_NAME_COLLISION)); + EXPECT_EQ(static_cast<DWORD>(ERROR_ACCESS_DENIED), + GetLastErrorFromNtStatus(STATUS_ACCESS_DENIED)); +} + +TEST(WinUtils, GetProcessBaseAddress) { + using sandbox::GetProcessBaseAddress; + STARTUPINFO start_info = {}; + PROCESS_INFORMATION proc_info = {}; + WCHAR command_line[] = L"notepad"; + start_info.cb = sizeof(start_info); + start_info.dwFlags = STARTF_USESHOWWINDOW; + start_info.wShowWindow = SW_HIDE; + ASSERT_TRUE(::CreateProcessW(nullptr, command_line, nullptr, nullptr, false, + CREATE_SUSPENDED, nullptr, nullptr, &start_info, + &proc_info)); + base::win::ScopedProcessInformation scoped_proc_info(proc_info); + ScopedTerminateProcess process_terminate(scoped_proc_info.process_handle()); + void* base_address = GetProcessBaseAddress(scoped_proc_info.process_handle()); + ASSERT_NE(nullptr, base_address); + ASSERT_NE(static_cast<DWORD>(-1), + ::ResumeThread(scoped_proc_info.thread_handle())); + ::WaitForInputIdle(scoped_proc_info.process_handle(), 1000); + ASSERT_NE(static_cast<DWORD>(-1), + ::SuspendThread(scoped_proc_info.thread_handle())); + + std::vector<HMODULE> modules; + // Compare against the loader's module list (which should now be initialized). + ASSERT_TRUE(GetModuleList(scoped_proc_info.process_handle(), &modules)); + ASSERT_GT(modules.size(), 0U); + EXPECT_EQ(base_address, modules[0]); +} + +// This test requires an elevated prompt to setup. +TEST(WinUtils, ConvertToLongPath) { + // Test setup. + base::FilePath orig_path; + ASSERT_TRUE(base::PathService::Get(base::DIR_SYSTEM, &orig_path)); + orig_path = orig_path.Append(L"calc.exe"); + + base::FilePath temp_path; + ASSERT_TRUE(base::PathService::Get(base::DIR_PROGRAM_FILES, &temp_path)); + temp_path = temp_path.Append(L"test_calc.exe"); + + ASSERT_TRUE(base::CopyFile(orig_path, temp_path)); + // No more asserts until cleanup. + + // WIN32 long path: "c:\Program Files\test_calc.exe" + wchar_t short_path[MAX_PATH] = {}; + DWORD size = + ::GetShortPathNameW(temp_path.value().c_str(), short_path, MAX_PATH); + EXPECT_TRUE(size > 0 && size < MAX_PATH); + // WIN32 short path: "C:\PROGRA~1\TEST_C~1.exe" + + // Sanity check that we actually got a short path above! Small chance + // it was disabled in the filesystem setup. + EXPECT_NE(temp_path.value().length(), ::wcslen(short_path)); + + std::wstring short_form_native_path; + EXPECT_TRUE(sandbox::GetNtPathFromWin32Path(std::wstring(short_path), + &short_form_native_path)); + // NT short path: "\Device\HarddiskVolume4\PROGRA~1\TEST_C~1.EXE" + + // Test 1: convert win32 short path to long: + std::wstring test1(short_path); + EXPECT_TRUE(sandbox::ConvertToLongPath(&test1)); + EXPECT_TRUE(::wcsicmp(temp_path.value().c_str(), test1.c_str()) == 0); + // Expected result: "c:\Program Files\test_calc.exe" + + // Test 2: convert native short path to long: + std::wstring drive_letter = temp_path.value().substr(0, 3); + std::wstring test2(short_form_native_path); + EXPECT_TRUE(sandbox::ConvertToLongPath(&test2, &drive_letter)); + + size_t index = short_form_native_path.find_first_of( + L'\\', ::wcslen(L"\\Device\\HarddiskVolume")); + EXPECT_TRUE(index != std::wstring::npos); + std::wstring expected_result = short_form_native_path.substr(0, index + 1); + expected_result.append(temp_path.value().substr(3)); + EXPECT_TRUE(::wcsicmp(expected_result.c_str(), test2.c_str()) == 0); + // Expected result: "\Device\HarddiskVolumeX\Program Files\test_calc.exe" + + // clean up + EXPECT_TRUE(base::DeleteFileW(temp_path, false)); + + return; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/window.cc b/security/sandbox/chromium/sandbox/win/src/window.cc new file mode 100644 index 0000000000..27645a9006 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/window.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/win/src/window.h" + +#include <aclapi.h> + +#include <memory> + +#include "base/logging.h" +#include "base/win/win_util.h" +#include "sandbox/win/src/acl.h" +#include "sandbox/win/src/sid.h" + +namespace { + +// Gets the security attributes of a window object referenced by |handle|. The +// lpSecurityDescriptor member of the SECURITY_ATTRIBUTES parameter returned +// must be freed using LocalFree by the caller. +bool GetSecurityAttributes(HANDLE handle, SECURITY_ATTRIBUTES* attributes) { + attributes->bInheritHandle = false; + attributes->nLength = sizeof(SECURITY_ATTRIBUTES); + + PACL dacl = nullptr; + DWORD result = ::GetSecurityInfo( + handle, SE_WINDOW_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, + &dacl, nullptr, &attributes->lpSecurityDescriptor); + if (ERROR_SUCCESS == result) + return true; + + return false; +} + +} // namespace + +namespace sandbox { + +ResultCode CreateAltWindowStation(HWINSTA* winsta) { + // Get the security attributes from the current window station; we will + // use this as the base security attributes for the new window station. + HWINSTA current_winsta = ::GetProcessWindowStation(); + if (!current_winsta) + return SBOX_ERROR_CANNOT_GET_WINSTATION; + + SECURITY_ATTRIBUTES attributes = {0}; + if (!GetSecurityAttributes(current_winsta, &attributes)) + return SBOX_ERROR_CANNOT_QUERY_WINSTATION_SECURITY; + + // Create the window station using nullptr for the name to ask the os to + // generate it. + *winsta = ::CreateWindowStationW( + nullptr, 0, GENERIC_READ | WINSTA_CREATEDESKTOP, &attributes); + if (!*winsta && ::GetLastError() == ERROR_ACCESS_DENIED) { + *winsta = ::CreateWindowStationW( + nullptr, 0, WINSTA_READATTRIBUTES | WINSTA_CREATEDESKTOP, &attributes); + } + LocalFree(attributes.lpSecurityDescriptor); + + if (*winsta) + return SBOX_ALL_OK; + + return SBOX_ERROR_CANNOT_CREATE_WINSTATION; +} + +ResultCode CreateAltDesktop(HWINSTA winsta, HDESK* desktop) { + std::wstring desktop_name = L"sbox_alternate_desktop_"; + + if (!winsta) { + desktop_name += L"local_winstation_"; + } + + // Append the current PID to the desktop name. + wchar_t buffer[16]; + _snwprintf_s(buffer, sizeof(buffer) / sizeof(wchar_t), L"0x%X", + ::GetCurrentProcessId()); + desktop_name += buffer; + + HDESK current_desktop = GetThreadDesktop(GetCurrentThreadId()); + + if (!current_desktop) + return SBOX_ERROR_CANNOT_GET_DESKTOP; + + // Get the security attributes from the current desktop, we will use this as + // the base security attributes for the new desktop. + SECURITY_ATTRIBUTES attributes = {0}; + if (!GetSecurityAttributes(current_desktop, &attributes)) + return SBOX_ERROR_CANNOT_QUERY_DESKTOP_SECURITY; + + // Back up the current window station, in case we need to switch it. + HWINSTA current_winsta = ::GetProcessWindowStation(); + + if (winsta) { + // We need to switch to the alternate window station before creating the + // desktop. + if (!::SetProcessWindowStation(winsta)) { + ::LocalFree(attributes.lpSecurityDescriptor); + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; + } + } + + // Create the destkop. + *desktop = ::CreateDesktop(desktop_name.c_str(), nullptr, nullptr, 0, + DESKTOP_CREATEWINDOW | DESKTOP_READOBJECTS | + READ_CONTROL | WRITE_DAC | WRITE_OWNER, + &attributes); + ::LocalFree(attributes.lpSecurityDescriptor); + + if (winsta) { + // Revert to the right window station. + if (!::SetProcessWindowStation(current_winsta)) { + return SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION; + } + } + + if (*desktop) { + // Replace the DACL on the new Desktop with a reduced privilege version. + // We can soft fail on this for now, as it's just an extra mitigation. + static const ACCESS_MASK kDesktopDenyMask = + WRITE_DAC | WRITE_OWNER | DELETE | DESKTOP_CREATEMENU | + DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALPLAYBACK | + DESKTOP_JOURNALRECORD | DESKTOP_SWITCHDESKTOP; + AddKnownSidToObject(*desktop, SE_WINDOW_OBJECT, Sid(WinRestrictedCodeSid), + DENY_ACCESS, kDesktopDenyMask); + return SBOX_ALL_OK; + } + + return SBOX_ERROR_CANNOT_CREATE_DESKTOP; +} + +std::wstring GetFullDesktopName(HWINSTA winsta, HDESK desktop) { + if (!desktop) { + NOTREACHED(); + return std::wstring(); + } + + std::wstring name; + if (winsta) { + name = base::win::GetWindowObjectName(winsta); + name += L'\\'; + } + + name += base::win::GetWindowObjectName(desktop); + return name; +} + +} // namespace sandbox diff --git a/security/sandbox/chromium/sandbox/win/src/window.h b/security/sandbox/chromium/sandbox/win/src/window.h new file mode 100644 index 0000000000..1fe5b6cc72 --- /dev/null +++ b/security/sandbox/chromium/sandbox/win/src/window.h @@ -0,0 +1,37 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_SRC_WINDOW_H_ +#define SANDBOX_SRC_WINDOW_H_ + +#include <windows.h> + +#include <string> + +#include "sandbox/win/src/sandbox_types.h" + +namespace sandbox { + +// Creates a window station. The name is generated by the OS. The security +// descriptor is based on the security descriptor of the current window +// station. +ResultCode CreateAltWindowStation(HWINSTA* winsta); + +// Creates a desktop. The name is a static string followed by the pid of the +// current process. The security descriptor on the new desktop is based on the +// security descriptor of the desktop associated with the current thread. +// If a winsta is specified, the function will switch to it before creating +// the desktop. If the functions fails the switch back to the current winsta, +// the function will return SBOX_ERROR_FAILED_TO_SWITCH_BACK_WINSTATION. +ResultCode CreateAltDesktop(HWINSTA winsta, HDESK* desktop); + +// Returns the name of the desktop referenced by |desktop|. If a window +// station is specified, the name is prepended with the window station name, +// followed by a backslash. This name can be used as the lpDesktop parameter +// to CreateProcess. +std::wstring GetFullDesktopName(HWINSTA winsta, HDESK desktop); + +} // namespace sandbox + +#endif // SANDBOX_SRC_WINDOW_H_ |