Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ set(PROJECT_SOURCES
"src/detectors/metadata.cc"
"src/detectors/result.cc"
"src/detectors/virtualbox_detector.cc"
"src/detectors/vmware_detector.cc"
"src/sources/cpuid_source.cc"
"src/sources/dmi_source.cc")

Expand Down
19 changes: 19 additions & 0 deletions lib/inc/internal/detectors/vmware_detector.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <internal/detectors/result.hpp>
#include <internal/sources/cpuid_source.hpp>
#include <internal/sources/dmi_source.hpp>
#include <string>

namespace whereami { namespace detectors {

/**
* VMware detector function
* @param cpuid_source An instance of a CPUID data source
* @param dmi_source An instance of a DMI data source
* @return The VMware detection result
*/
result vmware(const sources::cpuid_base& cpuid_source,
const sources::dmi_base& dmi_source);

}} // namespace whereami::detectors
10 changes: 10 additions & 0 deletions lib/inc/internal/sources/dmi_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ namespace whereami { namespace sources {
*/
struct dmi_data
{
/**
* The BIOS address
* Only available via dmidecode section 0 (requires root)
*/
std::string bios_address;
/**
* The name of the BIOS vendor
* via /sys/class/dmi/id/bios_vendor or dmidecode section 0 vendor
Expand Down Expand Up @@ -51,6 +56,11 @@ namespace whereami { namespace sources {
public:
dmi_base() {}
virtual ~dmi_base() {}
/**
* Retrieve the BIOS address
* @return The BIOS address
*/
std::string bios_address() const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this always available in the data from dmidecode, but just not always needed/useful for what we're trying to do? Or only under VMWare?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BIOS address is always available from dmidecode (as long as the dmidecode output isn't totally empty), but as far as I've seen, VMware is the only place it's actually useful.

The DMI source object is getting pretty crowded as more single-case values like this are collected - I feel like there's probably a better way to organize this.

/**
* Retrieve the BIOS vendor
* @return The BIOS vendor's name
Expand Down
62 changes: 62 additions & 0 deletions lib/src/detectors/vmware_detector.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#include <internal/detectors/vmware_detector.hpp>
#include <internal/vm.hpp>
#include <leatherman/execution/execution.hpp>
#include <leatherman/util/regex.hpp>

using namespace whereami;
using namespace leatherman::util;
using namespace std;

/**
* Retrieve the VMware version based on a DMI BIOS address result, if possible
* @param address The value of the BIOS address from DMI
* @return The VMware version
*/
static string vmware_bios_address_to_version(int address)
{
const std::unordered_map<int, std::string> version_map {
{ 0xe8480, "ESXi 2.5" },
{ 0xe7c70, "ESXi 3.0" },
{ 0xe66c0, "ESXi 3.5" },
{ 0xe7910, "ESXi 3.5" },
{ 0xea550, "ESXi 4.0" },
{ 0xea6c0, "ESXi 4.0" },
{ 0xea2e0, "ESXi 4.1" },
{ 0xe72c0, "ESXi 5.0" },
{ 0xea0c0, "ESXi 5.1" },
{ 0xea050, "ESXi 5.5" },
{ 0xe99e0, "ESXi 6.0" },
{ 0xea580, "ESXi 6.5" },
{ 0xea5e0, "Fusion 8.5" },
};

auto it = version_map.find(address);

if (it != version_map.end()) {
return it->second;
}
return {};
}

namespace whereami { namespace detectors {

result vmware(const sources::cpuid_base& cpuid_source,
const sources::dmi_base& dmi_source)
{
result res {vm::vmware};

if (dmi_source.manufacturer() == "VMware, Inc." ||
cpuid_source.vendor() == "VMwareVMware") {
res.validate();

auto bios_address = dmi_source.bios_address();
if (!bios_address.empty()) {
auto value = stoi(bios_address, nullptr, 16);
res.set("version", vmware_bios_address_to_version(value));
}
}

return res;
}

}} // namespace whereami::detectors
9 changes: 9 additions & 0 deletions lib/src/sources/dmi_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ using namespace leatherman::util;

namespace whereami { namespace sources {

std::string dmi_base::bios_address() const
{
return data_ ? data_->bios_address : "";
}

std::string dmi_base::bios_vendor() const
{
return data_ ? data_->bios_vendor : "";
Expand Down Expand Up @@ -138,6 +143,7 @@ namespace whereami { namespace sources {
static const unordered_map<int, vector<string>> sections {
{ 0, { // BIOS
"vendor:",
"address:",
}},
{ 1, { // System
"manufacturer:",
Expand Down Expand Up @@ -194,6 +200,9 @@ namespace whereami { namespace sources {
if (index == 0) {
data_->bios_vendor = move(value);
}
if (index == 1) {
data_->bios_address = move(value);
}
break;
}

Expand Down
7 changes: 7 additions & 0 deletions lib/src/whereami.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <internal/sources/dmi_source.hpp>
#include <internal/sources/cpuid_source.hpp>
#include <internal/detectors/virtualbox_detector.hpp>
#include <internal/detectors/vmware_detector.hpp>
#include <leatherman/logging/logging.hpp>

using namespace std;
Expand All @@ -30,6 +31,12 @@ namespace whereami {
results.emplace_back(virtualbox_result);
}

auto vmware_result = detectors::vmware(cpuid_source, dmi_source);

if (vmware_result.valid()) {
results.emplace_back(vmware_result);
}

return results;
}
} // namespace whereami
1 change: 1 addition & 0 deletions lib/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ set(TEST_CASES
"detectors/metadata.cc"
"detectors/result.cc"
"detectors/virtualbox_detector.cc"
"detectors/vmware_detector.cc"
"fixtures.cc"
"fixtures/cpuid_fixtures.cc"
"fixtures/dmi_fixtures.cc"
Expand Down
3 changes: 3 additions & 0 deletions lib/tests/detectors/virtualbox_detector.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ SCENARIO("Using the VirtualBox detector") {
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT},
});
dmi_fixture_values dmi_source({
"0x30000",
"VirtualBox",
"VirtualBox",
"Oracle Corporation",
Expand All @@ -42,6 +43,7 @@ SCENARIO("Using the VirtualBox detector") {
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT},
});
dmi_fixture_values dmi_source({
"",
"VirtualBox",
"VirtualBox",
"Oracle Corporation",
Expand Down Expand Up @@ -81,6 +83,7 @@ SCENARIO("Using the VirtualBox detector") {
"Other",
"Other",
"Other",
"Other",
{}});
THEN("the result should not be valid") {
auto res = virtualbox(cpuid_source, dmi_source);
Expand Down
90 changes: 90 additions & 0 deletions lib/tests/detectors/vmware_detector.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include <catch.hpp>
#include <internal/detectors/vmware_detector.hpp>
#include "../fixtures/cpuid_fixtures.hpp"
#include "../fixtures/dmi_fixtures.hpp"

using namespace std;
using namespace whereami::detectors;
using namespace whereami::sources;
using namespace whereami::testing::cpuid;
using namespace whereami::testing::dmi;

SCENARIO("Using the VMware detector") {
WHEN("running as root on a Linux VMware guest") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_VMwareVMware},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT},
});
dmi_fixture_values dmi_source({
"0xea580",
"Phoenix Technologies LTD",
"Intel Corporation",
"440BX Desktop Reference Platform",
"VMware, Inc.",
"VMware Virtual Platform",
{
"[MS_VM_CERT/SHA1/27d66596a61c48dd3dc7216fd715126e33f59ae7]",
"Welcome to the Virtual Machine",
}});
THEN("the result should be valid") {
auto res = vmware(cpuid_source, dmi_source);
REQUIRE(res.valid());
}
THEN("the result should contain version metdata") {
auto res = vmware(cpuid_source, dmi_source);
REQUIRE(res.get<string>("version") == "ESXi 6.5");
}
}

WHEN("running as a normal user on a Linux VMware guest") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_VMwareVMware},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT},
});
dmi_fixture_values dmi_source({
"",
"Phoenix Technologies LTD",
"Intel Corporation",
"440BX Desktop Reference Platform",
"VMware, Inc.",
"VMware Virtual Platform",
{}});
THEN("it should return true") {
auto res = vmware(cpuid_source, dmi_source);
REQUIRE(res.valid());
}
THEN("it should not contain version metdata") {
auto res = vmware(cpuid_source, dmi_source);
REQUIRE(res.get<string>("version") == "");
}
}

WHEN("running in a Windows VMware guest") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_VMwareVMware},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT},
});
dmi_fixture_empty dmi_source;
THEN("it should return true") {
REQUIRE(vmware(cpuid_source, dmi_source).valid());
}
}

WHEN("running outside of VMware") {
cpuid_fixture_values cpuid_source({
{VENDOR_LEAF, register_fixtures::VENDOR_KVMKVMKVM},
{HYPERVISOR_PRESENT, register_fixtures::HYPERVISOR_PRESENT},
});
dmi_fixture_values dmi_source({
"Other",
"Other",
"Other",
"Other",
"Other",
"Other",
{}});
THEN("it should return false") {
REQUIRE_FALSE(vmware(cpuid_source, dmi_source).valid());
}
}
}
1 change: 1 addition & 0 deletions lib/tests/fixtures/cpuid_fixtures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace whereami { namespace testing { namespace cpuid {
static const sources::cpuid_registers VENDOR_NONE{0, 0, 0, 0};
static const sources::cpuid_registers VENDOR_KVMKVMKVM{0, 1263359563, 1447775574, 77};
static const sources::cpuid_registers VENDOR_VBoxVBoxVBox{0, 2020557398, 2020557398, 2020557398};
static const sources::cpuid_registers VENDOR_VMwareVMware{1073741840, 1635208534, 1297507698, 1701994871};
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/tests/fixtures/dmi_fixtures.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ using namespace std;

namespace whereami { namespace testing { namespace dmi {

dmi_fixture::dmi_fixture(std::string dmidecode_path, std::string sys_path)
dmi_fixture::dmi_fixture(std::string const& dmidecode_path, std::string const& sys_path)
: dmidecode_fixture_path_(dmidecode_path), sys_fixture_path_(sys_path)
{
data_.reset(nullptr);
Expand Down
4 changes: 2 additions & 2 deletions lib/tests/fixtures/dmi_fixtures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ namespace whereami { namespace testing { namespace dmi {
*/
class dmi_fixture : public sources::dmi {
public:
dmi_fixture(std::string dmidecode_path = dmi_fixtures::DMIDECODE_NONE,
std::string sys_path = dmi_fixtures::SYS_NONE);
dmi_fixture(std::string const& dmidecode_path = dmi_fixtures::DMIDECODE_NONE,
std::string const& sys_path = dmi_fixtures::SYS_NONE);
protected:
/**
* Read /sys/ data from a fixture base path (instead of from /sys/)
Expand Down
7 changes: 5 additions & 2 deletions lib/tests/sources/dmi_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ SCENARIO("Using the DMI data source") {
dmi_fixtures::DMIDECODE_NONE,
dmi_fixtures::SYS_VIRTUALBOX
};
THEN("string fields are populated via /sys/") {
THEN("accessible string fields are populated via /sys/") {
REQUIRE(dmi_source.bios_vendor() == "innotek GmbH");
REQUIRE(dmi_source.board_manufacturer() == "Oracle Corporation");
REQUIRE(dmi_source.board_product_name() == "VirtualBox");
REQUIRE(dmi_source.manufacturer() == "innotek GmbH");
REQUIRE(dmi_source.product_name() == "VirtualBox");
}
THEN("OEM strings are unavailable without dmidecode") {
THEN("privileged data is unavailable without dmidecode") {
REQUIRE(dmi_source.bios_address().empty());
REQUIRE(dmi_source.oem_strings().size() == 0);
}
}
Expand All @@ -36,6 +37,7 @@ SCENARIO("Using the DMI data source") {
dmi_fixtures::SYS_NONE
};
THEN("nothing is found") {
REQUIRE(dmi_source.bios_address().empty());
REQUIRE(dmi_source.bios_vendor().empty());
REQUIRE(dmi_source.board_manufacturer().empty());
REQUIRE(dmi_source.board_product_name().empty());
Expand All @@ -51,6 +53,7 @@ SCENARIO("Using the DMI data source") {
dmi_fixtures::SYS_NONE
};
THEN("all fields are populated via dmidecode") {
REQUIRE(dmi_source.bios_address() == "0xE0000");
REQUIRE(dmi_source.bios_vendor() == "innotek GmbH");
REQUIRE(dmi_source.board_manufacturer() == "Oracle Corporation");
REQUIRE(dmi_source.board_product_name() == "VirtualBox");
Expand Down