[kernel][object] Refactor PolicyManager into JobPolicy

Refactor PolicyManager into JobPolicy in preparation for adding
support for timer slack job policy.

Encapsulate pol_cookie_t behind JobPolicy.

Eliminate the PolicyManager singleton.

Add some new tests for JobPolicy.

Bug: ZX-931 #comment Refactor PolicyManager into JobPolicy
Test: new "k ut job_policy" and /boot/test/sys/policy-test
Change-Id: I5ffd1a0f76a7e9cdf745b466232339f3409ccb18
diff --git a/kernel/object/glue.cpp b/kernel/object/glue.cpp
index 8f8063e..8660559 100644
--- a/kernel/object/glue.cpp
+++ b/kernel/object/glue.cpp
@@ -8,8 +8,6 @@
 // * Initialization code for kernel/object module
 // * Singleton instances and global locks
 // * Helper functions
-//
-// TODO(dbort): Split this file into self-consistent pieces.
 
 #include <inttypes.h>
 
@@ -24,7 +22,6 @@
 #include <object/diagnostics.h>
 #include <object/excp_port.h>
 #include <object/job_dispatcher.h>
-#include <object/policy_manager.h>
 #include <object/port_dispatcher.h>
 #include <object/process_dispatcher.h>
 
@@ -37,18 +34,10 @@
 // All jobs and processes are rooted at the |root_job|.
 static fbl::RefPtr<JobDispatcher> root_job;
 
-// The singleton policy manager, for jobs and processes. This is
-// not a Dispatcher, just a plain class.
-static PolicyManager* policy_manager;
-
 fbl::RefPtr<JobDispatcher> GetRootJobDispatcher() {
     return root_job;
 }
 
-PolicyManager* GetSystemPolicyManager() {
-    return policy_manager;
-}
-
 static void oom_lowmem(size_t shortfall_bytes) {
     printf("OOM: oom_lowmem(shortfall_bytes=%zu) called\n", shortfall_bytes);
 
@@ -76,7 +65,6 @@
 static void object_glue_init(uint level) TA_NO_THREAD_SAFETY_ANALYSIS {
     Handle::Init();
     root_job = JobDispatcher::CreateRootJob();
-    policy_manager = PolicyManager::Create();
     PortDispatcher::Init();
     // Be sure to update kernel_cmdline.md if any of these defaults change.
     oom_init(cmdline_get_bool("kernel.oom.enable", true),
diff --git a/kernel/object/include/object/job_dispatcher.h b/kernel/object/include/object/job_dispatcher.h
index 693d013..891fbe1 100644
--- a/kernel/object/include/object/job_dispatcher.h
+++ b/kernel/object/include/object/job_dispatcher.h
@@ -10,7 +10,7 @@
 
 #include <object/dispatcher.h>
 #include <object/excp_port.h>
-#include <object/policy_manager.h>
+#include <object/job_policy.h>
 #include <object/process_dispatcher.h>
 
 #include <zircon/types.h>
@@ -103,7 +103,7 @@
     // Set policy. |mode| is is either ZX_JOB_POL_RELATIVE or ZX_JOB_POL_ABSOLUTE and
     // in_policy is an array of |count| elements.
     zx_status_t SetPolicy(uint32_t mode, const zx_policy_basic* in_policy, size_t policy_count);
-    pol_cookie_t GetPolicy();
+    JobPolicy GetPolicy() const;
 
     // Calls the provided |zx_status_t func(JobDispatcher*)| on every
     // JobDispatcher in the system. Stops if |func| returns an error,
@@ -146,7 +146,7 @@
 
     using LiveRefsArray = fbl::Array<fbl::RefPtr<Dispatcher>>;
 
-    JobDispatcher(uint32_t flags, fbl::RefPtr<JobDispatcher> parent, pol_cookie_t policy);
+    JobDispatcher(uint32_t flags, fbl::RefPtr<JobDispatcher> parent, JobPolicy policy);
 
     bool AddChildJob(const fbl::RefPtr<JobDispatcher>& job);
     void RemoveChildJob(JobDispatcher* job);
@@ -197,7 +197,7 @@
     RawJobList jobs_ TA_GUARDED(get_lock());
     RawProcessList procs_ TA_GUARDED(get_lock());
 
-    pol_cookie_t policy_ TA_GUARDED(get_lock());
+    JobPolicy policy_ TA_GUARDED(get_lock());
 
     fbl::RefPtr<ExceptionPort> exception_port_ TA_GUARDED(get_lock());
     fbl::RefPtr<ExceptionPort> debugger_exception_port_ TA_GUARDED(get_lock());
diff --git a/kernel/object/include/object/job_policy.h b/kernel/object/include/object/job_policy.h
new file mode 100644
index 0000000..6e03283
--- /dev/null
+++ b/kernel/object/include/object/job_policy.h
@@ -0,0 +1,58 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <stdint.h>
+
+#include <zircon/syscalls/policy.h>
+#include <zircon/types.h>
+
+typedef uint64_t pol_cookie_t;
+
+// JobPolicy is a value type that provides a space-efficient encoding of the policies defined in the
+// policy.h public header.
+//
+// JobPolicy encodes one type of policy, basic, which is logically an array of zx_policy_basic
+// elements. For example:
+//
+//   zx_policy_basic policy[] = {
+//      { ZX_POL_BAD_HANDLE, ZX_POL_ACTION_KILL },
+//      { ZX_POL_NEW_CHANNEL, ZX_POL_ACTION_ALLOW },
+//      { ZX_POL_NEW_FIFO, ZX_POL_ACTION_ALLOW | ZX_POL_ACTION_EXCEPTION },
+//      { ZX_POL_VMAR_WX, ZX_POL_ACTION_DENY | ZX_POL_ACTION_KILL }}
+//
+class JobPolicy {
+public:
+    // Merge array |policy| of length |count| into this object.
+    //
+    // |mode| controls what happens when the policies in |policy| and this object intersect. |mode|
+    // must be one of:
+    //
+    // ZX_JOB_POL_RELATIVE - Conflicting policies are ignored and will not cause the call to fail.
+    //
+    // ZX_JOB_POL_ABSOLUTE - If any of the policies in |policy| conflict with those in this object,
+    //   the call will fail with an error and this object will not be modified.
+    //
+    zx_status_t AddBasicPolicy(uint32_t mode, const zx_policy_basic_t* policy, size_t count);
+
+    // Returns the set of actions for the specified |condition|.
+    //
+    // If the |condition| is allowed, returns ZX_POL_ACTION_ALLOW, optionally ORed with
+    // ZX_POL_ACTION_EXCEPTION.
+    //
+    // If the condition is not allowed, returns ZX_POL_ACTION_DENY, optionally ORed with zero or
+    // more of ZX_POL_ACTION_EXCEPTION and ZX_POL_ACTION_KILL.
+    //
+    // This method asserts if |policy| is invalid, and returns ZX_POL_ACTION_DENY for all other
+    // failure modes.
+    uint32_t QueryBasicPolicy(uint32_t condition) const;
+
+    bool operator==(const JobPolicy& rhs) const;
+    bool operator!=(const JobPolicy& rhs) const;
+
+private:
+    // Remember, JobPolicy is a value type so think carefully before increasing its size.
+    pol_cookie_t cookie_{};
+};
diff --git a/kernel/object/include/object/policy_manager.h b/kernel/object/include/object/policy_manager.h
deleted file mode 100644
index 20b5802..0000000
--- a/kernel/object/include/object/policy_manager.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2017 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#pragma once
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <zircon/syscalls/policy.h>
-#include <zircon/types.h>
-
-#include <fbl/ref_ptr.h>
-
-struct zx_policy_basic;
-class PortDispatcher;
-
-typedef uint64_t pol_cookie_t;
-constexpr pol_cookie_t kPolicyEmpty = 0u;
-
-// PolicyManager is in charge of providing a space-efficient encoding of
-// the external policy as defined in the policy.h public header which
-// the client expresses as an array of zx_policy_basic elements.
-//
-// For example:
-//
-//   zx_policy_basic in_policy[] = {
-//      { ZX_POL_BAD_HANDLE, ZX_POL_ACTION_KILL },
-//      { ZX_POL_NEW_CHANNEL, ZX_POL_ACTION_ALLOW },
-//      { ZX_POL_NEW_FIFO, ZX_POL_ACTION_ALLOW | ZX_POL_ACTION_EXCEPTION },
-//      { ZX_POL_VMAR_WX, ZX_POL_ACTION_DENY | ZX_POL_ACTION_KILL }}
-//
-//  Which is 64 bytes but PolicyManager can encode it in the pol_cookie_t
-//  itself if it is a simple policy.
-
-class PolicyManager {
-public:
-    // Creates in the heap a policy manager with a |default_action|
-    // which is returned when QueryBasicPolicy() matches no known condition.
-    static PolicyManager* Create(uint32_t default_action = ZX_POL_ACTION_ALLOW);
-
-    // Creates a |new_policy| based on an |existing_policy| or based on
-    // kPolicyEmpty and an array of |policy_input|. When done with the
-    // new policy RemovePolicy() must be called.
-    //
-    // |mode| can be:
-    // - ZX_JOB_POL_RELATIVE which creates a new policy that only uses
-    //   the |policy_input| entries that are unspecified in |existing_policy|
-    // - ZX_JOB_POL_ABSOLUTE which creates a new policy that requires
-    //   that all |policy_input| entries are used.
-    //
-    // This call can fail in low memory cases and when the |existing_policy|
-    // and the policy_input are in conflict given the |mode| parameter.
-    zx_status_t AddPolicy(
-        uint32_t mode, pol_cookie_t existing_policy,
-        const zx_policy_basic* policy_input, size_t policy_count,
-        pol_cookie_t* new_policy);
-
-    // Returns whether |condition| (from the ZX_POL_xxxx set) is allowed
-    // by |policy| (created using AddPolicy()).
-    //
-    // If the condition is allowed, returns ZX_POL_ACTION_ALLOW,
-    // optionally ORed with ZX_POL_ACTION_EXCEPTION according to the
-    // policy.
-    //
-    // If the condition is not allowed, returns ZX_POL_ACTION_DENY,
-    // optionally ORed with zero or more of ZX_POL_ACTION_EXCEPTION and
-    // ZX_POL_ACTION_KILL.
-    //
-    // This method asserts if |policy| is invalid, and returns
-    // ZX_POL_ACTION_DENY all other failure modes.
-    uint32_t QueryBasicPolicy(pol_cookie_t policy, uint32_t condition);
-
-private:
-    explicit PolicyManager(uint32_t default_action);
-    ~PolicyManager() = default;
-
-    uint32_t GetEffectiveAction(uint64_t policy);
-    bool CanSetEntry(uint64_t existing, uint32_t new_action);
-
-    zx_status_t AddPartial(uint32_t mode, pol_cookie_t existing_policy,
-        uint32_t condition, uint32_t policy, uint64_t* partial);
-
-    const uint32_t default_action_;
-};
-
-// Returns the singleton policy manager for jobs and processes.
-PolicyManager* GetSystemPolicyManager();
diff --git a/kernel/object/include/object/process_dispatcher.h b/kernel/object/include/object/process_dispatcher.h
index 3fc91d3..7ad292f 100644
--- a/kernel/object/include/object/process_dispatcher.h
+++ b/kernel/object/include/object/process_dispatcher.h
@@ -8,12 +8,12 @@
 
 #include <kernel/event.h>
 #include <kernel/thread.h>
-#include <vm/vm_aspace.h>
 #include <object/dispatcher.h>
 #include <object/futex_context.h>
 #include <object/handle.h>
-#include <object/policy_manager.h>
+#include <object/job_policy.h>
 #include <object/thread_dispatcher.h>
+#include <vm/vm_aspace.h>
 
 #include <zircon/syscalls/object.h>
 #include <zircon/types.h>
@@ -301,7 +301,7 @@
     const fbl::RefPtr<JobDispatcher> job_;
 
     // Policy set by the Job during Create().
-    const pol_cookie_t policy_;
+    const JobPolicy policy_;
 
     // The process can belong to either of these lists independently.
     fbl::DoublyLinkedListNodeState<ProcessDispatcher*> dll_job_raw_;
diff --git a/kernel/object/job_dispatcher.cpp b/kernel/object/job_dispatcher.cpp
index bc19145..25528c0 100644
--- a/kernel/object/job_dispatcher.cpp
+++ b/kernel/object/job_dispatcher.cpp
@@ -107,7 +107,7 @@
 
 fbl::RefPtr<JobDispatcher> JobDispatcher::CreateRootJob() {
     fbl::AllocChecker ac;
-    auto job = fbl::AdoptRef(new (&ac) JobDispatcher(0u, nullptr, kPolicyEmpty));
+    auto job = fbl::AdoptRef(new (&ac) JobDispatcher(0u, nullptr, JobPolicy()));
     if (!ac.check())
         return nullptr;
     job->set_name(kRootJobName, sizeof(kRootJobName));
@@ -140,7 +140,7 @@
 
 JobDispatcher::JobDispatcher(uint32_t /*flags*/,
                              fbl::RefPtr<JobDispatcher> parent,
-                             pol_cookie_t policy)
+                             JobPolicy policy)
     : SoloDispatcher(ZX_JOB_NO_PROCESSES | ZX_JOB_NO_JOBS),
       parent_(ktl::move(parent)),
       max_height_(parent_ ? parent_->max_height() - 1 : kRootJobMaxHeight),
@@ -299,7 +299,7 @@
     UpdateStateLocked(clear, 0u);
 }
 
-pol_cookie_t JobDispatcher::GetPolicy() {
+JobPolicy JobDispatcher::GetPolicy() const {
     Guard<fbl::Mutex> guard{get_lock()};
     return policy_;
 }
@@ -353,14 +353,11 @@
     if (!procs_.is_empty() || !jobs_.is_empty())
         return ZX_ERR_BAD_STATE;
 
-    pol_cookie_t new_policy;
-    auto status = GetSystemPolicyManager()->AddPolicy(
-        mode, policy_, in_policy, policy_count, &new_policy);
+    auto status = policy_.AddBasicPolicy(mode, in_policy, policy_count);
 
     if (status != ZX_OK)
         return status;
 
-    policy_ = new_policy;
     return ZX_OK;
 }
 
diff --git a/kernel/object/policy_manager.cpp b/kernel/object/job_policy.cpp
similarity index 74%
rename from kernel/object/policy_manager.cpp
rename to kernel/object/job_policy.cpp
index db5682c..bdb1e0f 100644
--- a/kernel/object/policy_manager.cpp
+++ b/kernel/object/job_policy.cpp
@@ -1,17 +1,20 @@
-// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Copyright 2018 The Fuchsia 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 <object/policy_manager.h>
+#include <object/job_policy.h>
 
 #include <assert.h>
 #include <err.h>
 
-#include <zircon/types.h>
-#include <fbl/alloc_checker.h>
+#include <zircon/syscalls/policy.h>
 
 namespace {
 
+constexpr uint32_t kDefaultAction = ZX_POL_ACTION_ALLOW;
+
+constexpr pol_cookie_t kPolicyEmpty = 0u;
+
 // The encoding of the basic policy is done 4 bits per each item.
 //
 // - When the top bit is 1, the lower 3 bits track the action:
@@ -52,8 +55,6 @@
     static bool is_default(uint64_t item) { return item == 0; }
 };
 
-}  // namespace
-
 constexpr uint32_t kPolicyActionValidBits =
     ZX_POL_ACTION_ALLOW | ZX_POL_ACTION_DENY | ZX_POL_ACTION_EXCEPTION | ZX_POL_ACTION_KILL;
 
@@ -65,124 +66,30 @@
 // Make sure that adding new policies forces updating this file.
 static_assert(ZX_POL_MAX == 13u, "please update PolicyManager AddPolicy and QueryBasicPolicy");
 
-PolicyManager* PolicyManager::Create(uint32_t default_action) {
-    fbl::AllocChecker ac;
-    auto pm = new (&ac) PolicyManager(default_action);
-    return ac.check() ? pm : nullptr;
-}
-
-PolicyManager::PolicyManager(uint32_t default_action)
-    : default_action_(default_action) {
-}
-
-zx_status_t PolicyManager::AddPolicy(
-    uint32_t mode, pol_cookie_t existing_policy,
-    const zx_policy_basic* policy_input, size_t policy_count,
-    pol_cookie_t* new_policy) {
-
-    // Don't allow overlong policies.
-    if (policy_count > ZX_POL_MAX)
-        return ZX_ERR_OUT_OF_RANGE;
-
-    uint64_t partials[ZX_POL_MAX] = {0};
-
-    // The policy computation algorithm is as follows:
-    //
-    //    loop over all input entries
-    //        if existing item is default or same then
-    //            store new policy in partial result array
-    //        else if mode is absolute exit with failure
-    //        else continue
-    //
-    // A special case is ZX_POL_NEW_ANY which applies the algorithm with
-    // the same input over all ZX_NEW_ actions so that the following can
-    // be expressed:
-    //
-    //   [0] ZX_POL_NEW_ANY     --> ZX_POL_ACTION_DENY
-    //   [1] ZX_POL_NEW_CHANNEL --> ZX_POL_ACTION_ALLOW
-    //
-    // Which means "deny all object creation except for channel".
-
-    zx_status_t res = ZX_OK;
-
-    for (size_t ix = 0; ix != policy_count; ++ix) {
-        const auto& in = policy_input[ix];
-
-        if (in.condition >= ZX_POL_MAX)
-            return ZX_ERR_INVALID_ARGS;
-
-        if (in.condition == ZX_POL_NEW_ANY) {
-            // loop over all ZX_POL_NEW_xxxx conditions.
-            for (uint32_t it = ZX_POL_NEW_VMO; it <= ZX_POL_NEW_TIMER; ++it) {
-                if ((res = AddPartial(mode, existing_policy, it, in.policy, &partials[it])) < 0)
-                    return res;
-            }
-        } else {
-            if ((res = AddPartial(
-                mode, existing_policy, in.condition, in.policy, &partials[in.condition])) < 0)
-                return res;
-        }
-    }
-
-    // Compute the resultant policy. The simple OR works because the only items that
-    // can change are the items that have zero. See Encoding::is_default().
-    for (const auto& partial : partials) {
-        existing_policy |= partial;
-    }
-
-    *new_policy = existing_policy;
-    return ZX_OK;
-}
-
-uint32_t PolicyManager::QueryBasicPolicy(pol_cookie_t policy, uint32_t condition) {
-    if (policy == kPolicyEmpty)
-        return default_action_;
-
-    Encoding existing = { policy };
-    DEBUG_ASSERT(existing.cookie_mode == Encoding::kPolicyInCookie);
-
-    switch (condition) {
-    case ZX_POL_BAD_HANDLE: return GetEffectiveAction(existing.bad_handle);
-    case ZX_POL_WRONG_OBJECT: return GetEffectiveAction(existing.wrong_obj);
-    case ZX_POL_NEW_VMO: return GetEffectiveAction(existing.new_vmo);
-    case ZX_POL_NEW_CHANNEL: return GetEffectiveAction(existing.new_channel);
-    case ZX_POL_NEW_EVENT: return GetEffectiveAction(existing.new_event);
-    case ZX_POL_NEW_EVENTPAIR: return GetEffectiveAction(existing.new_eventpair);
-    case ZX_POL_NEW_PORT: return GetEffectiveAction(existing.new_port);
-    case ZX_POL_NEW_SOCKET: return GetEffectiveAction(existing.new_socket);
-    case ZX_POL_NEW_FIFO: return GetEffectiveAction(existing.new_fifo);
-    case ZX_POL_NEW_TIMER: return GetEffectiveAction(existing.new_timer);
-    case ZX_POL_NEW_PROCESS: return GetEffectiveAction(existing.new_process);
-    case ZX_POL_VMAR_WX: return GetEffectiveAction(existing.vmar_wx);
-    default: return ZX_POL_ACTION_DENY;
-    }
-}
-
-uint32_t PolicyManager::GetEffectiveAction(uint64_t policy) {
-    return Encoding::is_default(policy) ?
-        default_action_ : Encoding::action(policy);
-}
-
-bool PolicyManager::CanSetEntry(uint64_t existing, uint32_t new_action) {
+bool CanSetEntry(uint64_t existing, uint32_t new_action) {
     if (Encoding::is_default(existing))
         return true;
     return (new_action == Encoding::action(existing)) ? true : false;
 }
 
-#define POLMAN_SET_ENTRY(mode, existing, in_pol, resultant)             \
-    do {                                                                \
-        if (CanSetEntry(existing, in_pol)) {                            \
-            resultant = in_pol & Encoding::kActionBits;                 \
-            resultant |= Encoding::kExplicitBit;                        \
-        } else if (mode == ZX_JOB_POL_ABSOLUTE) {                       \
-            return ZX_ERR_ALREADY_EXISTS;                               \
-        }                                                               \
+uint32_t GetEffectiveAction(uint64_t policy) {
+    return Encoding::is_default(policy) ? kDefaultAction : Encoding::action(policy);
+}
+
+#define POLMAN_SET_ENTRY(mode, existing, in_pol, resultant) \
+    do {                                                    \
+        if (CanSetEntry(existing, in_pol)) {                \
+            resultant = in_pol & Encoding::kActionBits;     \
+            resultant |= Encoding::kExplicitBit;            \
+        } else if (mode == ZX_JOB_POL_ABSOLUTE) {           \
+            return ZX_ERR_ALREADY_EXISTS;                   \
+        }                                                   \
     } while (0)
 
-zx_status_t PolicyManager::AddPartial(uint32_t mode, pol_cookie_t existing_policy,
-                                      uint32_t condition, uint32_t policy, uint64_t* partial) {
-    Encoding existing = { existing_policy };
-    Encoding result = {0};
+zx_status_t AddPartial(uint32_t mode, pol_cookie_t existing_policy,
+                       uint32_t condition, uint32_t policy, uint64_t* partial) {
+    Encoding existing = {existing_policy};
+    Encoding result = {};
 
     if (policy & ~kPolicyActionValidBits)
         return ZX_ERR_NOT_SUPPORTED;
@@ -233,3 +140,106 @@
 }
 
 #undef POLMAN_SET_ENTRY
+
+}  // namespace
+
+
+zx_status_t JobPolicy::AddBasicPolicy(uint32_t mode,
+                                      const zx_policy_basic_t* policy_input,
+                                      size_t policy_count) {
+    // Don't allow overlong policies.
+    if (policy_count > ZX_POL_MAX) {
+        return ZX_ERR_OUT_OF_RANGE;
+    }
+
+    uint64_t partials[ZX_POL_MAX] = {};
+
+    // The policy computation algorithm is as follows:
+    //
+    //    loop over all input entries
+    //        if existing item is default or same then
+    //            store new policy in partial result array
+    //        else if mode is absolute exit with failure
+    //        else continue
+    //
+    // A special case is ZX_POL_NEW_ANY which applies the algorithm with
+    // the same input over all ZX_NEW_ actions so that the following can
+    // be expressed:
+    //
+    //   [0] ZX_POL_NEW_ANY     --> ZX_POL_ACTION_DENY
+    //   [1] ZX_POL_NEW_CHANNEL --> ZX_POL_ACTION_ALLOW
+    //
+    // Which means "deny all object creation except for channel".
+
+    zx_status_t res = ZX_OK;
+
+    pol_cookie_t new_cookie = cookie_;
+
+    for (size_t ix = 0; ix != policy_count; ++ix) {
+        const auto& in = policy_input[ix];
+
+        if (in.condition >= ZX_POL_MAX) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
+        if (in.condition == ZX_POL_NEW_ANY) {
+            // loop over all ZX_POL_NEW_xxxx conditions.
+            for (uint32_t it = ZX_POL_NEW_VMO; it <= ZX_POL_NEW_TIMER; ++it) {
+                if ((res = AddPartial(mode, new_cookie, it, in.policy, &partials[it])) < 0) {
+                    return res;
+                }
+            }
+        } else {
+            if ((res = AddPartial(
+                     mode, new_cookie, in.condition, in.policy, &partials[in.condition])) < 0) {
+                return res;
+            }
+        }
+    }
+
+    // Compute the resultant policy. The simple OR works because the only items that
+    // can change are the items that have zero. See Encoding::is_default().
+    for (const auto& partial : partials) {
+        new_cookie |= partial;
+    }
+
+    cookie_ = new_cookie;
+    return ZX_OK;
+}
+
+uint32_t JobPolicy::QueryBasicPolicy(uint32_t condition) const {
+    if (cookie_ == kPolicyEmpty) {
+        return kDefaultAction;
+    }
+
+    Encoding existing = {cookie_};
+    DEBUG_ASSERT(existing.cookie_mode == Encoding::kPolicyInCookie);
+
+    switch (condition) {
+    case ZX_POL_BAD_HANDLE: return GetEffectiveAction(existing.bad_handle);
+    case ZX_POL_WRONG_OBJECT: return GetEffectiveAction(existing.wrong_obj);
+    case ZX_POL_NEW_VMO: return GetEffectiveAction(existing.new_vmo);
+    case ZX_POL_NEW_CHANNEL: return GetEffectiveAction(existing.new_channel);
+    case ZX_POL_NEW_EVENT: return GetEffectiveAction(existing.new_event);
+    case ZX_POL_NEW_EVENTPAIR: return GetEffectiveAction(existing.new_eventpair);
+    case ZX_POL_NEW_PORT: return GetEffectiveAction(existing.new_port);
+    case ZX_POL_NEW_SOCKET: return GetEffectiveAction(existing.new_socket);
+    case ZX_POL_NEW_FIFO: return GetEffectiveAction(existing.new_fifo);
+    case ZX_POL_NEW_TIMER: return GetEffectiveAction(existing.new_timer);
+    case ZX_POL_NEW_PROCESS: return GetEffectiveAction(existing.new_process);
+    case ZX_POL_VMAR_WX: return GetEffectiveAction(existing.vmar_wx);
+    default: return ZX_POL_ACTION_DENY;
+    }
+}
+
+bool JobPolicy::operator==(const JobPolicy& rhs) const {
+    if (this == &rhs) {
+        return true;
+    }
+
+    return cookie_ == rhs.cookie_;
+}
+
+bool JobPolicy::operator!=(const JobPolicy& rhs) const {
+    return !operator==(rhs);
+}
diff --git a/kernel/object/job_policy_tests.cpp b/kernel/object/job_policy_tests.cpp
new file mode 100644
index 0000000..86166df
--- /dev/null
+++ b/kernel/object/job_policy_tests.cpp
@@ -0,0 +1,93 @@
+// Copyright 2018 The Fuchsia Authors
+//
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file or at
+// https://opensource.org/licenses/MIT
+
+#include <object/job_policy.h>
+
+#include <fbl/algorithm.h>
+#include <lib/unittest/unittest.h>
+
+namespace {
+
+static bool initial_state() {
+    BEGIN_TEST;
+
+    JobPolicy p;
+    for (uint32_t pol = 0; pol < ZX_POL_MAX; ++pol) {
+        EXPECT_EQ(ZX_POL_ACTION_ALLOW, p.QueryBasicPolicy(pol), "");
+    }
+
+    END_TEST;
+}
+
+static bool add_basic_policy_absolute() {
+    BEGIN_TEST;
+
+    JobPolicy p;
+    zx_policy_basic_t repeated[2]{{ZX_POL_NEW_ANY, ZX_POL_ACTION_DENY},
+                                  {ZX_POL_NEW_ANY, ZX_POL_ACTION_DENY}};
+    ASSERT_EQ(ZX_OK, p.AddBasicPolicy(ZX_JOB_POL_ABSOLUTE, repeated, 2), "");
+    ASSERT_EQ(ZX_POL_ACTION_DENY, p.QueryBasicPolicy(ZX_POL_NEW_EVENT), "");
+
+    zx_policy_basic_t conflicting[2]{{ZX_POL_NEW_ANY, ZX_POL_ACTION_DENY},
+                                     {ZX_POL_NEW_ANY, ZX_POL_ACTION_ALLOW}};
+    ASSERT_EQ(ZX_ERR_ALREADY_EXISTS, p.AddBasicPolicy(ZX_JOB_POL_ABSOLUTE, conflicting, 2), "");
+    ASSERT_EQ(ZX_POL_ACTION_DENY, p.QueryBasicPolicy(ZX_POL_NEW_VMO), "");
+
+    END_TEST;
+}
+
+static bool add_basic_policy_relative() {
+    BEGIN_TEST;
+
+    JobPolicy p;
+    zx_policy_basic_t repeated[2]{{ZX_POL_NEW_ANY, ZX_POL_ACTION_DENY},
+                                  {ZX_POL_NEW_ANY, ZX_POL_ACTION_DENY}};
+    ASSERT_EQ(ZX_OK, p.AddBasicPolicy(ZX_JOB_POL_RELATIVE, repeated, 2), "");
+    ASSERT_EQ(ZX_POL_ACTION_DENY, p.QueryBasicPolicy(ZX_POL_NEW_TIMER), "");
+
+    zx_policy_basic_t conflicting[2]{{ZX_POL_NEW_ANY, ZX_POL_ACTION_DENY},
+                                     {ZX_POL_NEW_ANY, ZX_POL_ACTION_ALLOW}};
+    ASSERT_EQ(ZX_OK, p.AddBasicPolicy(ZX_JOB_POL_RELATIVE, conflicting, 2), "");
+    ASSERT_EQ(ZX_POL_ACTION_DENY, p.QueryBasicPolicy(ZX_POL_NEW_FIFO), "");
+
+    END_TEST;
+}
+
+// Test that AddBasicPolicy does not modify JobPolicy when it fails.
+static bool add_basic_policy_unmodified_on_error() {
+    BEGIN_TEST;
+
+    JobPolicy p;
+    zx_policy_basic_t policy[2]{{ZX_POL_NEW_VMO,
+                                 ZX_POL_ACTION_ALLOW | ZX_POL_ACTION_EXCEPTION},
+                                {ZX_POL_NEW_CHANNEL,
+                                 ZX_POL_ACTION_KILL}};
+    ASSERT_EQ(ZX_OK, p.AddBasicPolicy(ZX_JOB_POL_ABSOLUTE, policy, fbl::count_of(policy)), "");
+    ASSERT_EQ(ZX_POL_ACTION_ALLOW | ZX_POL_ACTION_EXCEPTION,
+              p.QueryBasicPolicy(ZX_POL_NEW_VMO), "");
+    ASSERT_EQ(ZX_POL_ACTION_KILL, p.QueryBasicPolicy(ZX_POL_NEW_CHANNEL), "");
+
+    const JobPolicy orig = p;
+
+    zx_policy_basic_t new_policy{ZX_POL_NEW_ANY, UINT32_MAX};
+    ASSERT_EQ(ZX_ERR_NOT_SUPPORTED, p.AddBasicPolicy(ZX_JOB_POL_ABSOLUTE, &new_policy, 1), "");
+    ASSERT_TRUE(orig == p, "");
+
+    new_policy = {ZX_POL_NEW_VMO, ZX_POL_ACTION_ALLOW};
+    ASSERT_EQ(ZX_ERR_ALREADY_EXISTS, p.AddBasicPolicy(ZX_JOB_POL_ABSOLUTE, &new_policy, 1), "");
+    ASSERT_TRUE(orig == p, "");
+
+    END_TEST;
+}
+
+} // namespace
+
+UNITTEST_START_TESTCASE(job_policy_tests)
+UNITTEST("initial_state", initial_state)
+UNITTEST("add_basic_policy_absolute", add_basic_policy_absolute)
+UNITTEST("add_basic_policy_relative", add_basic_policy_relative)
+UNITTEST("add_basic_policy_unmodified_on_error", add_basic_policy_unmodified_on_error)
+UNITTEST_END_TESTCASE(job_policy_tests, "job_policy", "JobPolicy tests");
diff --git a/kernel/object/process_dispatcher.cpp b/kernel/object/process_dispatcher.cpp
index cdeb8be..060c966 100644
--- a/kernel/object/process_dispatcher.cpp
+++ b/kernel/object/process_dispatcher.cpp
@@ -778,7 +778,7 @@
 }
 
 zx_status_t ProcessDispatcher::QueryPolicy(uint32_t condition) const {
-    auto action = GetSystemPolicyManager()->QueryBasicPolicy(policy_, condition);
+    auto action = policy_.QueryBasicPolicy(condition);
     if (action & ZX_POL_ACTION_EXCEPTION) {
         thread_signal_policy_exception();
     }
diff --git a/kernel/object/rules.mk b/kernel/object/rules.mk
index f23ab03..8bb0a10 100644
--- a/kernel/object/rules.mk
+++ b/kernel/object/rules.mk
@@ -29,6 +29,7 @@
     $(LOCAL_DIR)/interrupt_event_dispatcher.cpp \
     $(LOCAL_DIR)/iommu_dispatcher.cpp \
     $(LOCAL_DIR)/job_dispatcher.cpp \
+    $(LOCAL_DIR)/job_policy.cpp \
     $(LOCAL_DIR)/log_dispatcher.cpp \
     $(LOCAL_DIR)/mbuf.cpp \
     $(LOCAL_DIR)/message_packet.cpp \
@@ -36,7 +37,6 @@
     $(LOCAL_DIR)/pci_device_dispatcher.cpp \
     $(LOCAL_DIR)/pci_interrupt_dispatcher.cpp \
     $(LOCAL_DIR)/pinned_memory_token_dispatcher.cpp \
-    $(LOCAL_DIR)/policy_manager.cpp \
     $(LOCAL_DIR)/port_dispatcher.cpp \
     $(LOCAL_DIR)/process_dispatcher.cpp \
     $(LOCAL_DIR)/profile_dispatcher.cpp \
@@ -56,6 +56,7 @@
 # Tests
 MODULE_SRCS += \
     $(LOCAL_DIR)/buffer_chain_tests.cpp \
+    $(LOCAL_DIR)/job_policy_tests.cpp \
     $(LOCAL_DIR)/mbuf_tests.cpp \
     $(LOCAL_DIR)/message_packet_tests.cpp \
     $(LOCAL_DIR)/state_tracker_tests.cpp \