[utest][core] Add tests for pager api violations

Also minor tweaks to pager syscall checking to match the docs.

Test: self
Change-Id: Ic2b3a6b5884c81af41540b79ac2fc70eb0198c5a
diff --git a/kernel/syscalls/pager.cpp b/kernel/syscalls/pager.cpp
index 3d5f481..dcd7975 100644
--- a/kernel/syscalls/pager.cpp
+++ b/kernel/syscalls/pager.cpp
@@ -80,7 +80,7 @@
     }
 
     fbl::RefPtr<VmObjectDispatcher> vmo_dispatcher;
-    status = up->GetDispatcherWithRights(vmo, ZX_RIGHT_READ | ZX_RIGHT_WRITE, &vmo_dispatcher);
+    status = up->GetDispatcher(vmo, &vmo_dispatcher);
     if (status != ZX_OK) {
         return status;
     }
@@ -105,8 +105,7 @@
     }
 
     fbl::RefPtr<VmObjectDispatcher> pager_vmo_dispatcher;
-    status = up->GetDispatcherWithRights(pager_vmo,
-                                         ZX_RIGHT_READ | ZX_RIGHT_WRITE, &pager_vmo_dispatcher);
+    status = up->GetDispatcherWithRights(pager_vmo, 0, &pager_vmo_dispatcher);
     if (status != ZX_OK) {
         return status;
     }
@@ -115,13 +114,12 @@
         return ZX_ERR_INVALID_ARGS;
     }
 
+    if (!IS_PAGE_ALIGNED(offset) || !IS_PAGE_ALIGNED(size)) {
+        return ZX_ERR_INVALID_ARGS;
+    }
+
     switch (op) {
     case ZX_PAGER_OP_SUPPLY_PAGES: {
-
-        if (!IS_PAGE_ALIGNED(offset) || !IS_PAGE_ALIGNED(size) || !IS_PAGE_ALIGNED(aux_offset)) {
-            return ZX_ERR_INVALID_ARGS;
-        }
-
         fbl::RefPtr<VmObjectDispatcher> aux_vmo_dispatcher;
         status = up->GetDispatcherWithRights(aux_vmo_handle,
                                              ZX_RIGHT_READ | ZX_RIGHT_WRITE, &aux_vmo_dispatcher);
@@ -129,6 +127,10 @@
             return status;
         }
 
+        if (!IS_PAGE_ALIGNED(aux_offset)) {
+            return ZX_ERR_INVALID_ARGS;
+        }
+
         VmPageSpliceList pages;
         status = aux_vmo_dispatcher->vmo()->TakePages(aux_offset, size, &pages);
         if (status != ZX_OK) {
diff --git a/system/utest/core/pager/pager.cpp b/system/utest/core/pager/pager.cpp
index b9fa77a..8a03bcb 100644
--- a/system/utest/core/pager/pager.cpp
+++ b/system/utest/core/pager/pager.cpp
@@ -4,11 +4,20 @@
 
 #include <fbl/algorithm.h>
 #include <fbl/function.h>
+#include <lib/fzl/vmo-mapper.h>
+#include <lib/zx/bti.h>
+#include <lib/zx/iommu.h>
+#include <lib/zx/port.h>
 #include <unittest/unittest.h>
+#include <zircon/syscalls/iommu.h>
 
 #include "test_thread.h"
 #include "userpager.h"
 
+__BEGIN_CDECLS;
+extern zx_handle_t get_root_resource(void);
+__END_CDECLS;
+
 namespace pager_tests {
 
 static bool check_buffer_data(Vmo* vmo, uint64_t offset,
@@ -1297,6 +1306,272 @@
 }
 
 
+// Tests API violations for pager_create
+bool invalid_pager_create() {
+    BEGIN_TEST;
+    zx_handle_t handle;
+
+    // Bad options
+    ASSERT_EQ(zx_pager_create(1, &handle), ZX_ERR_INVALID_ARGS);
+
+    END_TEST;
+}
+
+// Tests API violations for pager_create_vmo
+bool invalid_pager_create_vmo() {
+    BEGIN_TEST;
+
+    zx_handle_t pager;
+    ASSERT_EQ(zx_pager_create(0, &pager), ZX_OK);
+
+    zx::port port;
+    ASSERT_EQ(zx::port::create(0, &port), ZX_OK);
+
+    zx_handle_t vmo;
+
+    // Bad options
+    ASSERT_EQ(zx_pager_create_vmo(pager, port.get(), 0, ZX_PAGE_SIZE, ZX_VMO_NON_RESIZABLE, &vmo),
+              ZX_ERR_INVALID_ARGS);
+
+    // Bad handles for pager and port
+    ASSERT_EQ(zx_pager_create_vmo(ZX_HANDLE_INVALID, port.get(), 0, ZX_PAGE_SIZE, 0, &vmo),
+              ZX_ERR_BAD_HANDLE);
+    ASSERT_EQ(zx_pager_create_vmo(pager, ZX_HANDLE_INVALID, 0, ZX_PAGE_SIZE, 0, &vmo),
+              ZX_ERR_BAD_HANDLE);
+
+    // Missing write right on port
+    zx::port ro_port;
+    ASSERT_EQ(port.duplicate(ZX_DEFAULT_PORT_RIGHTS & ~ZX_RIGHT_WRITE, &ro_port), ZX_OK);
+    ASSERT_EQ(zx_pager_create_vmo(pager, ro_port.get(), 0, ZX_PAGE_SIZE, 0, &vmo),
+              ZX_ERR_ACCESS_DENIED);
+
+    // Bad handle types for pager and port
+    ASSERT_EQ(zx_pager_create_vmo(port.get(), port.get(), 0, ZX_PAGE_SIZE, 0, &vmo),
+              ZX_ERR_WRONG_TYPE);
+    zx::vmo tmp_vmo; // handle 2 needs to be writable
+    ASSERT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &tmp_vmo), ZX_OK);
+    ASSERT_EQ(zx_pager_create_vmo(pager, tmp_vmo.get(), 0, ZX_PAGE_SIZE, 0, &vmo),
+              ZX_ERR_WRONG_TYPE);
+
+    // Invalid size
+    static constexpr uint64_t kBadSize = fbl::round_down(UINT64_MAX, ZX_PAGE_SIZE) + 1;
+    ASSERT_EQ(zx_pager_create_vmo(pager, port.get(), 0, kBadSize, 0, &vmo),
+              ZX_ERR_OUT_OF_RANGE);
+
+    zx_handle_close(pager);
+    END_TEST;
+}
+
+// Tests API violations for pager_detach_vmo
+bool invalid_pager_detach_vmo() {
+    BEGIN_TEST;
+    zx_handle_t pager;
+    ASSERT_EQ(zx_pager_create(0, &pager), ZX_OK);
+
+    zx::port port;
+    ASSERT_EQ(zx::port::create(0, &port), ZX_OK);
+
+    zx::vmo vmo;
+    ASSERT_EQ(zx_pager_create_vmo(pager, port.get(), 0,
+                                  ZX_PAGE_SIZE, 0, vmo.reset_and_get_address()), ZX_OK);
+
+    // Test bad handles
+    ASSERT_EQ(zx_pager_detach_vmo(ZX_HANDLE_INVALID, vmo.get()), ZX_ERR_BAD_HANDLE);
+    ASSERT_EQ(zx_pager_detach_vmo(pager, ZX_HANDLE_INVALID), ZX_ERR_BAD_HANDLE);
+
+    // Test wrong handle types
+    ASSERT_EQ(zx_pager_detach_vmo(vmo.get(), vmo.get()), ZX_ERR_WRONG_TYPE);
+    ASSERT_EQ(zx_pager_detach_vmo(pager, pager), ZX_ERR_WRONG_TYPE);
+
+    // Test detaching a non-paged vmo
+    zx::vmo tmp_vmo;
+    ASSERT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &tmp_vmo), ZX_OK);
+    ASSERT_EQ(zx_pager_detach_vmo(pager, tmp_vmo.get()), ZX_ERR_INVALID_ARGS);
+
+    // Test detaching with the wrong pager
+    zx_handle_t pager2;
+    ASSERT_EQ(zx_pager_create(0, &pager2), ZX_OK);
+    ASSERT_EQ(zx_pager_detach_vmo(pager2, vmo.get()), ZX_ERR_INVALID_ARGS);
+    zx_handle_close(pager2);
+
+    zx_handle_close(pager);
+    END_TEST;
+}
+
+// Tests op-agnostic API violations for pager_vmo_op
+bool invalid_pager_vmo_op() {
+    BEGIN_TEST;
+    zx_handle_t pager;
+    ASSERT_EQ(zx_pager_create(0, &pager), ZX_OK);
+
+    zx::port port;
+    ASSERT_EQ(zx::port::create(0, &port), ZX_OK);
+
+    zx::vmo vmo;
+    ASSERT_EQ(zx_pager_create_vmo(pager, port.get(), 0,
+                                  ZX_PAGE_SIZE, 0, vmo.reset_and_get_address()), ZX_OK);
+
+    // Test bad handles
+    ASSERT_EQ(zx_pager_vmo_op(ZX_HANDLE_INVALID, vmo.get(), 0, 0, 0, ZX_HANDLE_INVALID, 0),
+              ZX_ERR_BAD_HANDLE);
+    ASSERT_EQ(zx_pager_vmo_op(pager, ZX_HANDLE_INVALID, 0, 0, 0, ZX_HANDLE_INVALID, 0),
+              ZX_ERR_BAD_HANDLE);
+
+    // Test wrong handle types
+    ASSERT_EQ(zx_pager_vmo_op(vmo.get(), vmo.get(), 0, 0, 0, ZX_HANDLE_INVALID, 0),
+              ZX_ERR_WRONG_TYPE);
+    ASSERT_EQ(zx_pager_vmo_op(pager, pager, 0, 0, 0, ZX_HANDLE_INVALID, 0), ZX_ERR_WRONG_TYPE);
+
+    // Test using a non-paged vmo
+    zx::vmo tmp_vmo;
+    ASSERT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &tmp_vmo), ZX_OK);
+    ASSERT_EQ(zx_pager_vmo_op(pager, tmp_vmo.get(), 0, 0, 0, ZX_HANDLE_INVALID, 0),
+              ZX_ERR_INVALID_ARGS);
+
+    // Test detaching with the wrong pager
+    zx_handle_t pager2;
+    ASSERT_EQ(zx_pager_create(0, &pager2), ZX_OK);
+    ASSERT_EQ(zx_pager_vmo_op(pager2, vmo.get(), 0, 0, 0, ZX_HANDLE_INVALID, 0),
+              ZX_ERR_INVALID_ARGS);
+    zx_handle_close(pager2);
+
+    // Tests misaligned offset and size
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), 0, 1, 0, ZX_HANDLE_INVALID, 0),
+              ZX_ERR_INVALID_ARGS);
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), 0, 0, 1, ZX_HANDLE_INVALID, 0),
+              ZX_ERR_INVALID_ARGS);
+
+    // Tests bad opcode
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), UINT32_MAX, 0, 0, ZX_HANDLE_INVALID, 0),
+              ZX_ERR_INVALID_ARGS);
+
+    END_TEST;
+}
+
+// Tests API violations for pager_vmo_op's ZX_PAGER_SUPPLY_PAGES op
+bool invalid_pager_supply() {
+    BEGIN_TEST;
+    zx_handle_t pager;
+    ASSERT_EQ(zx_pager_create(0, &pager), ZX_OK);
+
+    zx::port port;
+    ASSERT_EQ(zx::port::create(0, &port), ZX_OK);
+
+    zx::vmo vmo;
+    ASSERT_EQ(zx_pager_create_vmo(pager, port.get(), 0,
+                                  ZX_PAGE_SIZE, 0, vmo.reset_and_get_address()), ZX_OK);
+
+    // Tests bad handle
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                              0, 0, ZX_HANDLE_INVALID, 0), ZX_ERR_BAD_HANDLE);
+
+    // Tests wrong handle type
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                              0, 0, port.get(), 0), ZX_ERR_WRONG_TYPE);
+
+
+    zx::vmo aux_vmo;
+    ASSERT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &aux_vmo), ZX_OK);
+
+    // Tests missing permissions on the aux vmo
+    zx::vmo ro_vmo;
+    ASSERT_EQ(vmo.duplicate(ZX_DEFAULT_VMO_RIGHTS & ~ZX_RIGHT_WRITE, &ro_vmo), ZX_OK);
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                              0, 0, ro_vmo.get(), 0), ZX_ERR_ACCESS_DENIED);
+    zx::vmo wo_vmo;
+    ASSERT_EQ(vmo.duplicate(ZX_DEFAULT_VMO_RIGHTS & ~ZX_RIGHT_READ, &wo_vmo), ZX_OK);
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                              0, 0, wo_vmo.get(), 0), ZX_ERR_ACCESS_DENIED);
+
+    // Test bad aux alignment
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                              0, 0, aux_vmo.get(), 1), ZX_ERR_INVALID_ARGS);
+
+    // Test violations of conditions for taking pages from a vmo
+    static uint32_t kIsClone = 0;
+    static uint32_t kFromPager = 1;
+    static uint32_t kHasMapping = 2;
+    static uint32_t kHasClone = 3;
+    static uint32_t kNotCommitted = 4;
+#ifdef BUILD_COMBINED_TESTS
+    static uint32_t kHasPinned = 5;
+    static uint32_t kNumViolationTypes = 6;
+#else
+    static uint32_t kNumViolationTypes = 5;
+#endif // BUILD_COMBINED_TESTS
+    for (uint32_t i = 0; i < kNumViolationTypes; i++) {
+        zx::vmo aux_vmo;
+        zx::vmo alt_vmo;
+        fzl::VmoMapper mapper;
+
+        if (i == kIsClone) {
+            ASSERT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &alt_vmo), ZX_OK);
+            ASSERT_EQ(alt_vmo.clone(ZX_VMO_CLONE_COPY_ON_WRITE, 0, ZX_PAGE_SIZE, &aux_vmo), ZX_OK);
+        } else if (i == kFromPager) {
+            ASSERT_EQ(zx_pager_create_vmo(pager, port.get(), 0, ZX_PAGE_SIZE, 0,
+                                          aux_vmo.reset_and_get_address()), ZX_OK);
+        } else {
+            ASSERT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &aux_vmo), ZX_OK);
+        }
+
+        if (i == kHasMapping) {
+            ASSERT_EQ(mapper.Map(aux_vmo, 0, ZX_PAGE_SIZE, ZX_VM_PERM_READ), ZX_OK);
+        }
+
+        if (i == kHasClone) {
+            ASSERT_EQ(aux_vmo.clone(ZX_VMO_CLONE_COPY_ON_WRITE, 0, ZX_PAGE_SIZE, &alt_vmo), ZX_OK);
+        }
+
+        if (i != kNotCommitted) {
+            if (i == kFromPager) {
+                ASSERT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &alt_vmo), ZX_OK);
+                ASSERT_EQ(alt_vmo.op_range(ZX_VMO_OP_COMMIT, 0, ZX_PAGE_SIZE, nullptr, 0), ZX_OK);
+                ASSERT_EQ(zx_pager_vmo_op(pager, aux_vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                                          0, ZX_PAGE_SIZE, alt_vmo.get(), 0), ZX_OK);
+            } else {
+                ASSERT_EQ(aux_vmo.op_range(ZX_VMO_OP_COMMIT, 0, ZX_PAGE_SIZE, nullptr, 0), ZX_OK);
+            }
+        }
+
+#ifdef BUILD_COMBINED_TESTS
+        zx::iommu iommu;
+        zx::bti bti;
+        zx::pmt pmt;
+        if (i == kHasPinned) {
+            zx::unowned_resource root_res(get_root_resource());
+            zx_iommu_desc_dummy_t desc;
+            ASSERT_EQ(zx_iommu_create(get_root_resource(), ZX_IOMMU_TYPE_DUMMY,
+                                      &desc, sizeof(desc), iommu.reset_and_get_address()), ZX_OK);
+            ASSERT_EQ(zx::bti::create(iommu, 0, 0xdeadbeef, &bti), ZX_OK);
+            zx_paddr_t addr;
+            ASSERT_EQ(bti.pin(ZX_BTI_PERM_READ, aux_vmo, 0, ZX_PAGE_SIZE, &addr, 1, &pmt), ZX_OK);
+        }
+#endif
+
+        ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                                  0, ZX_PAGE_SIZE, aux_vmo.get(), 0), ZX_ERR_BAD_STATE);
+
+#ifdef BUILD_COMBINED_TESTS
+        if (pmt) {
+            pmt.unpin();
+        }
+#endif
+    }
+
+    // Test out of range pager_vmo region
+    ASSERT_EQ(aux_vmo.op_range(ZX_VMO_OP_COMMIT, 0, ZX_PAGE_SIZE, nullptr, 0), ZX_OK);
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                              ZX_PAGE_SIZE, ZX_PAGE_SIZE, aux_vmo.get(), 0), ZX_ERR_OUT_OF_RANGE);
+
+    // Test out of range aux_vmo region
+    ASSERT_EQ(zx::vmo::create(ZX_PAGE_SIZE, 0, &aux_vmo), ZX_OK);
+    ASSERT_EQ(aux_vmo.op_range(ZX_VMO_OP_COMMIT, 0, ZX_PAGE_SIZE, nullptr, 0), ZX_OK);
+    ASSERT_EQ(zx_pager_vmo_op(pager, vmo.get(), ZX_PAGER_OP_SUPPLY_PAGES,
+                              0, ZX_PAGE_SIZE, aux_vmo.get(), ZX_PAGE_SIZE), ZX_ERR_OUT_OF_RANGE);
+
+    END_TEST;
+}
+
 #define DEFINE_VMO_VMAR_TEST(fn_name) \
 bool fn_name ##_vmar() { return fn_name(true); } \
 bool fn_name ##_vmo() { return fn_name(false); } \
@@ -1391,9 +1666,17 @@
 RUN_TEST(supply_decommit_test);
 END_TEST_CASE(commit_tests)
 
-} // namespace pager_tests
+// Tests focused on API violations
 
-//TODO: Test cases which violate various syscall invalid args
+BEGIN_TEST_CASE(api_violations)
+RUN_TEST(invalid_pager_create);
+RUN_TEST(invalid_pager_create_vmo);
+RUN_TEST(invalid_pager_detach_vmo);
+RUN_TEST(invalid_pager_vmo_op);
+RUN_TEST(invalid_pager_supply);
+END_TEST_CASE(api_violations)
+
+} // namespace pager_tests
 
 #ifndef BUILD_COMBINED_TESTS
 int main(int argc, char** argv) {
diff --git a/system/utest/core/pager/rules.mk b/system/utest/core/pager/rules.mk
index 875ddcf..63b3f0a 100644
--- a/system/utest/core/pager/rules.mk
+++ b/system/utest/core/pager/rules.mk
@@ -20,6 +20,7 @@
 MODULE_STATIC_LIBS := \
     system/ulib/elf-search \
     system/ulib/fbl \
+    system/ulib/fzl \
     system/ulib/inspector \
     system/ulib/sync \
     system/ulib/zx \
diff --git a/system/utest/core/rules.mk b/system/utest/core/rules.mk
index 4159e7e..42845eb 100644
--- a/system/utest/core/rules.mk
+++ b/system/utest/core/rules.mk
@@ -20,6 +20,7 @@
 MODULE_STATIC_LIBS := \
     system/ulib/ddk \
     system/ulib/fbl \
+    system/ulib/fzl \
     system/ulib/runtime \
     system/ulib/sync \
     system/ulib/zx \