[graphics] Start on conformance test for ICDs
Validates
* That the JSON Manifest is valid
* That the ICD's DT_NEEDED only contains allowlisted shared libraries.
Change-Id: I7a2f76f706ed3aa445444290dc25920c56a66179
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/693829
Commit-Queue: John Bauman <jbauman@google.com>
Reviewed-by: Casey Dahlin <sadmac@google.com>
Reviewed-by: Craig Stout <cstout@google.com>
diff --git a/src/graphics/tests/BUILD.gn b/src/graphics/tests/BUILD.gn
index 19875b2..e2ad95f 100644
--- a/src/graphics/tests/BUILD.gn
+++ b/src/graphics/tests/BUILD.gn
@@ -45,6 +45,7 @@
testonly = true
deps = [
"common:vkcontext",
+ "icd_conformance",
"icd_load",
"libvulkan",
"mali_vulkan_test",
diff --git a/src/graphics/tests/icd_conformance/BUILD.gn b/src/graphics/tests/icd_conformance/BUILD.gn
new file mode 100644
index 0000000..79da61f
--- /dev/null
+++ b/src/graphics/tests/icd_conformance/BUILD.gn
@@ -0,0 +1,55 @@
+# Copyright 2022 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.
+
+import("//build/components.gni")
+import("//src/graphics/lib/magma/gnbuild/magma.gni")
+
+source_set("src") {
+ testonly = true
+
+ sources = [ "icd_conformance.cc" ]
+ public_deps = [
+ "//sdk/fidl/fuchsia.io:fuchsia.io_llcpp",
+ "//sdk/fidl/fuchsia.vulkan.loader:fuchsia.vulkan.loader_llcpp",
+ "//sdk/lib/fdio",
+ "//src/lib/elflib",
+ "//src/lib/files",
+ "//src/lib/fxl/test:gtest_main",
+ "//src/lib/fxl/test:test_settings",
+ "//src/lib/json_parser",
+ "//third_party/googletest:gtest",
+ "//zircon/system/ulib/fzl",
+ "//zircon/system/ulib/service:service-llcpp",
+ ]
+ public_deps += [ "//src/lib/vulkan" ]
+}
+
+executable("icd_conformance_bin") {
+ testonly = true
+ output_name = "icd_conformance"
+
+ deps = [ ":src" ]
+}
+
+fuchsia_component("icd_conformance_cmp") {
+ testonly = true
+ component_name = "icd_conformance"
+ deps = [ ":icd_conformance_bin" ]
+
+ manifest = "meta/icd_conformance.cml"
+}
+
+fuchsia_test_package("icd-conformance") {
+ package_name = "icd_conformance"
+ test_components = [ ":icd_conformance_cmp" ]
+ test_specs = {
+ # TODO(fxbug.dev/103295): Add aemu.
+ environments = magma_libvulkan_hardware_envs
+ }
+}
+
+group("icd_conformance") {
+ testonly = true
+ deps = [ ":icd-conformance" ]
+}
diff --git a/src/graphics/tests/icd_conformance/icd_conformance.cc b/src/graphics/tests/icd_conformance/icd_conformance.cc
new file mode 100644
index 0000000..c4b7461
--- /dev/null
+++ b/src/graphics/tests/icd_conformance/icd_conformance.cc
@@ -0,0 +1,148 @@
+// Copyright 2022 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 <fidl/fuchsia.io/cpp/wire.h>
+#include <fidl/fuchsia.vulkan.loader/cpp/wire.h>
+#include <lib/fdio/namespace.h>
+#include <lib/fidl/llcpp/connect_service.h>
+#include <lib/fit/defer.h>
+#include <lib/fzl/vmo-mapper.h>
+#include <lib/service/llcpp/service.h>
+
+#include <set>
+
+#include <gtest/gtest.h>
+
+#include "rapidjson/prettywriter.h"
+#include "rapidjson/schema.h"
+#include "src/lib/elflib/elflib.h"
+#include "src/lib/files/directory.h"
+#include "src/lib/json_parser/json_parser.h"
+#include "src/lib/json_parser/pretty_print.h"
+
+namespace {
+const char* kManifestFsPath = "/manifestfs";
+
+const char* kManifestSchema = R"(
+{
+ "$schema":"http://json-schema.org/schema#",
+ "type":"object",
+ "properties":{
+ "file_format_version":{
+ "type":"string"
+ },
+ "ICD":{
+ "type":"object",
+ "properties":{
+ "library_path":{
+ "type":"string"
+ },
+ "api_version":{
+ "type":"string"
+ }
+ },
+ "required":[
+ "library_path",
+ "api_version"
+ ]
+ }
+ },
+ "required":[
+ "file_format_version",
+ "ICD"
+ ]
+}
+)";
+
+void ValidateSharedObject(const zx::vmo& vmo) {
+ fzl::VmoMapper mapper;
+ ASSERT_EQ(ZX_OK, mapper.Map(vmo, 0, 0, ZX_VM_PERM_READ));
+
+ std::unique_ptr<elflib::ElfLib> lib =
+ elflib::ElfLib::Create(reinterpret_cast<uint8_t*>(mapper.start()), mapper.size());
+ ASSERT_TRUE(lib);
+
+ auto deps = lib->GetSharedObjectDependencies();
+
+ ASSERT_TRUE(deps);
+ const std::set<std::string> kSoAllowlist{"libzircon.so", "libc.so"};
+
+ // Validate all needed shared libraries against allowlist.
+ for (const std::string& dep : *deps) {
+ EXPECT_TRUE(kSoAllowlist.count(dep)) << "Disallowed library: " << dep;
+ }
+ EXPECT_LT(0u, deps->size());
+
+ auto warnings = lib->GetAndClearWarnings();
+
+ for (const std::string& warning : warnings) {
+ ADD_FAILURE() << warning;
+ }
+}
+
+bool ValidateManifestJson(const rapidjson::GenericDocument<rapidjson::UTF8<>>& doc) {
+ rapidjson::Document schema_doc;
+ schema_doc.Parse(kManifestSchema);
+ EXPECT_FALSE(schema_doc.HasParseError()) << schema_doc.GetParseError();
+
+ rapidjson::SchemaDocument schema(schema_doc);
+ rapidjson::SchemaValidator validator(schema);
+ if (!doc.Accept(validator)) {
+ rapidjson::StringBuffer sb;
+ rapidjson::PrettyWriter<rapidjson::StringBuffer> w(sb);
+ validator.GetError().Accept(w);
+ ADD_FAILURE() << "manifest.json failed validation " << sb.GetString();
+ return false;
+ }
+ return true;
+}
+
+void ValidateIcd(fidl::WireSyncClient<fuchsia_vulkan_loader::Loader>& loader,
+ const std::string& manifest_filename) {
+ json_parser::JSONParser manifest_parser;
+ auto manifest_doc =
+ manifest_parser.ParseFromFile(std::string(kManifestFsPath) + "/" + manifest_filename);
+
+ ASSERT_FALSE(manifest_parser.HasError()) << manifest_parser.error_str();
+
+ ASSERT_TRUE(ValidateManifestJson(manifest_doc));
+
+ std::string library_path = manifest_doc["ICD"].GetObject()["library_path"].GetString();
+
+ auto res = loader->Get(fidl::StringView::FromExternal(library_path));
+ ASSERT_EQ(res.status(), ZX_OK);
+ zx::vmo vmo = std::move(res->lib);
+ ValidateSharedObject(vmo);
+}
+
+TEST(IcdConformance, SharedLibraries) {
+ auto svc = service::OpenServiceRoot();
+ auto client_end = service::ConnectAt<fuchsia_vulkan_loader::Loader>(*svc);
+
+ auto client = fidl::BindSyncClient(std::move(*client_end));
+
+ auto manifest_fs_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
+ ASSERT_EQ(ZX_OK, manifest_fs_endpoints.status_value());
+ auto manifest_result =
+ client->ConnectToManifestFs(fuchsia_vulkan_loader::ConnectToManifestOptions::kWaitForIdle,
+ manifest_fs_endpoints->server.TakeChannel());
+ EXPECT_EQ(ZX_OK, manifest_result.status());
+
+ fdio_ns_t* ns;
+ EXPECT_EQ(ZX_OK, fdio_ns_get_installed(&ns));
+ EXPECT_EQ(ZX_OK, fdio_ns_bind(ns, kManifestFsPath,
+ manifest_fs_endpoints->client.TakeChannel().release()));
+ auto defer_unbind = fit::defer([&]() { fdio_ns_unbind(ns, kManifestFsPath); });
+
+ std::vector<std::string> manifests;
+ EXPECT_TRUE(files::ReadDirContents(kManifestFsPath, &manifests));
+
+ for (auto& manifest : manifests) {
+ if (manifest == ".")
+ continue;
+ SCOPED_TRACE(manifest);
+ ValidateIcd(client, manifest);
+ }
+}
+} // namespace
diff --git a/src/graphics/tests/icd_conformance/meta/icd_conformance.cml b/src/graphics/tests/icd_conformance/meta/icd_conformance.cml
new file mode 100644
index 0000000..7b927b5
--- /dev/null
+++ b/src/graphics/tests/icd_conformance/meta/icd_conformance.cml
@@ -0,0 +1,14 @@
+// Copyright 2022 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: [
+ "//src/lib/vulkan/vulkan-test.shard.cml",
+ "//src/sys/test_runners/gtest/default.shard.cml",
+ "syslog/client.shard.cml",
+ "vulkan/client.shard.cml",
+ ],
+ program: {
+ binary: "bin/icd_conformance",
+ },
+}
diff --git a/src/lib/elflib/elflib.cc b/src/lib/elflib/elflib.cc
index f0358ca..401f612 100644
--- a/src/lib/elflib/elflib.cc
+++ b/src/lib/elflib/elflib.cc
@@ -991,6 +991,40 @@
.size = *dynstr_.size});
}
+std::optional<std::vector<std::string>> ElfLib::GetSharedObjectDependencies() {
+ LoadProgramHeaders();
+
+ std::vector<std::string> output;
+
+ for (size_t idx = 0; idx < segments_.size(); idx++) {
+ if (segments_[idx].p_type != PT_DYNAMIC) {
+ continue;
+ }
+
+ auto data = GetSegmentData(idx);
+
+ if (!data.ptr) {
+ return std::nullopt;
+ }
+
+ const Elf64_Dyn* start = reinterpret_cast<const Elf64_Dyn*>(data.ptr);
+ const Elf64_Dyn* end = start + (data.size / sizeof(Elf64_Dyn));
+
+ for (auto dyn = start; dyn != end; dyn++) {
+ if (dyn->d_tag == DT_NEEDED) {
+ auto string = GetDynamicString(dyn->d_un.d_val);
+ if (!string) {
+ Warn("Invalid symbol table string");
+ continue;
+ }
+ output.push_back(std::move(*string));
+ }
+ }
+ }
+
+ return output;
+}
+
bool ElfLib::ProbeHasDebugInfo() {
if (!header_.e_shnum) {
// No sections, no debug info.
diff --git a/src/lib/elflib/elflib.h b/src/lib/elflib/elflib.h
index 97387b6..ffc957f 100644
--- a/src/lib/elflib/elflib.h
+++ b/src/lib/elflib/elflib.h
@@ -92,6 +92,8 @@
// ".dynsym" section.
std::optional<std::map<std::string, Elf64_Sym>> GetAllDynamicSymbols();
+ std::optional<std::vector<std::string>> GetSharedObjectDependencies();
+
// Attempt to discern whether this file has debug symbols (otherwise it is
// presumably stripped).
//
diff --git a/src/lib/elflib/elflib_fuzztest.cc b/src/lib/elflib/elflib_fuzztest.cc
index 49e71bc..6048dc2 100644
--- a/src/lib/elflib/elflib_fuzztest.cc
+++ b/src/lib/elflib/elflib_fuzztest.cc
@@ -17,6 +17,7 @@
elf->GetAllSymbols();
elf->GetAllDynamicSymbols();
elf->GetPLTOffsets();
+ elf->GetSharedObjectDependencies();
elf->GetAndClearWarnings();
}
return 0;
diff --git a/src/lib/elflib/elflib_unittest.cc b/src/lib/elflib/elflib_unittest.cc
index 9aa839e..f5fff7b 100644
--- a/src/lib/elflib/elflib_unittest.cc
+++ b/src/lib/elflib/elflib_unittest.cc
@@ -451,5 +451,19 @@
ASSERT_TRUE(warnings.empty());
}
+TEST(ElfLib, GetSharedObjectDependencies) {
+ std::unique_ptr<ElfLib> elf =
+ ElfLib::Create(GetTestBinaryPath(std::string(kStrippedExampleFile)));
+ ASSERT_NE(elf.get(), nullptr);
+
+ auto objects = elf->GetSharedObjectDependencies();
+ ASSERT_TRUE(objects);
+ auto& object_vec = objects.value();
+ ASSERT_EQ(object_vec.size(), 3u);
+ EXPECT_EQ(object_vec[0], "libclang_rt.scudo.so");
+ EXPECT_EQ(object_vec[1], "libfdio.so");
+ EXPECT_EQ(object_vec[2], "libc.so");
+}
+
} // namespace
} // namespace elflib