[igt-dev] [PATCH i-g-t v5 5/6] kms_content_protection: srm and topology_info test

Ramalingam C
2019-04-18T08:46:59Z

Retrieve the topology info and use that to stitch a srm and
verify the revocation process.

Adds a connector properties called "HDCP Topology"

SRM stitched here is polluted. Kernel trusts the userspace to validate
the SRM integrity through DCP signature. Actually this test exploits
that trust to test SRM and downstream_info features.

HDCP1.4 BKSV and HDCP2.2 receiver id of a HDCP sink is different. So we
need to force the KMD to use a single HDCP version across
downstream_info gathering and SRM setting and verifying teh revocation
process. So Type 1 is used.

v2:
binary sysfs is used to write the srm into kernel [Daniel]
v3:
s/CP_downstream_info/HDCP Topology
v4:
srm sysfs is moved to /sys/class/drm as per kernel changes.
explicit padding is added for topology struct, in sync with kernel.
v5:
SRM is written into /lib/firmware/ instead of srm sysfs [Daniel]

Signed-off-by: Ramalingam C <ramalingam.c@intel.com>
---
lib/igt_kms.c | 1 +
lib/igt_kms.h | 1 +
tests/kms_content_protection.c | 230 ++++++++++++++++++++++++++++++---
3 files changed, 214 insertions(+), 18 deletions(-)

diff --git a/lib/igt_kms.c b/lib/igt_kms.c
index 18f0556687de..7d4080cb1b60 100644
--- a/lib/igt_kms.c
+++ b/lib/igt_kms.c
@@ -223,6 +223,7 @@ const char * const igt_connector_prop_names[IGT_NUM_CONNECTOR_PROPS] = {
[IGT_CONNECTOR_CONTENT_PROTECTION] = "Content Protection",
[IGT_CONNECTOR_VRR_CAPABLE] = "vrr_capable",
[IGT_CONNECTOR_HDCP_CONTENT_TYPE] = "HDCP Content Type",
+ [IGT_CONNECTOR_HDCP_TOPOLOGY_INFO] = "HDCP Topology",
};

/*
diff --git a/lib/igt_kms.h b/lib/igt_kms.h
index b1430a517305..90154e85b699 100644
--- a/lib/igt_kms.h
+++ b/lib/igt_kms.h
@@ -124,6 +124,7 @@ enum igt_atomic_connector_properties {
IGT_CONNECTOR_CONTENT_PROTECTION,
IGT_CONNECTOR_VRR_CAPABLE,
IGT_CONNECTOR_HDCP_CONTENT_TYPE,
+ IGT_CONNECTOR_HDCP_TOPOLOGY_INFO,
IGT_NUM_CONNECTOR_PROPS
};

diff --git a/tests/kms_content_protection.c b/tests/kms_content_protection.c
index 48f89571dcff..b48915eb3d73 100644
--- a/tests/kms_content_protection.c
+++ b/tests/kms_content_protection.c
@@ -58,6 +58,118 @@ struct data {
#define FLIP_EVENT_POLLING_TIMEOUT_MSEC 1000

+#define DRM_MODE_HDCP_KSV_LEN 5
+#define DRM_MODE_HDCP_MAX_DEVICE_CNT 127
+
+#define DRM_MODE_HDCP14_IN_FORCE (1<<0)
+#define DRM_MODE_HDCP22_IN_FORCE (1<<1)
+
+struct hdcp_topology_info {
+ /* Version of HDCP authenticated (1.4/2.2) */
+ __u32 ver_in_force;
+
+ /* Applicable only for HDCP2.2 */
+ __u32 content_type;
+
+ /* KSV of immediate HDCP Sink. In Little-Endian Format. */
+ __u8 bksv[DRM_MODE_HDCP_KSV_LEN];
+
+ /* Whether Immediate HDCP sink is a repeater? */
+ __u8 is_repeater;
+
+ /* Depth received from immediate downstream repeater */
+ __u8 depth;
+ __u8 pad1;
+
+ /* Device count received from immediate downstream repeater */
+ __u32 device_count;
+
+ /*
+ * Max buffer required to hold ksv list received from immediate
+ * repeater. In this array first device_count * DRM_MODE_HDCP_KSV_LEN
+ * will hold the valid ksv bytes.
+ * If authentication specification is
+ * HDCP1.4 - each KSV's Bytes will be in Little-Endian format.
+ * HDCP2.2 - each KSV's Bytes will be in Big-Endian format.
+ */
+ __u8 ksv_list[DRM_MODE_HDCP_KSV_LEN * DRM_MODE_HDCP_MAX_DEVICE_CNT];
+ __u8 pad2[5];
+} __packed;
+
+__u8 facsimile_srm[] = {
+ 0x80, 0x0, 0x0, 0x05, 0x01, 0x0, 0x0, 0x36, 0x02, 0x51, 0x1E, 0xF2,
+ 0x1A, 0xCD, 0xE7, 0x26, 0x97, 0xF4, 0x01, 0x97, 0x10, 0x19, 0x92, 0x53,
+ 0xE9, 0xF0, 0x59, 0x95, 0xA3, 0x7A, 0x3B, 0xFE, 0xE0, 0x9C, 0x76, 0xDD,
+ 0x83, 0xAA, 0xC2, 0x5B, 0x24, 0xB3, 0x36, 0x84, 0x94, 0x75, 0x34, 0xDB,
+ 0x10, 0x9E, 0x3B, 0x23, 0x13, 0xD8, 0x7A, 0xC2, 0x30, 0x79, 0x84};
+
+static void parse_topology_info(struct hdcp_topology_info *ds_info)
+{
+ __u8 *ksvs;
+ int i;
+
+ if (ds_info->ver_in_force & DRM_MODE_HDCP14_IN_FORCE)
+ igt_info("HDCP1.4 is Enabled\n");
+ else if (ds_info->ver_in_force & DRM_MODE_HDCP22_IN_FORCE)
+ igt_info("HDCP2.2 is Enabled. Type%d\n",
+ ds_info->content_type & HDCP_CONTENT_TYPE_1 ?
+ 1 : 0);
+ else
+ return;
+
+ igt_info("\tReceiver ID: %#04x %#04x %#04x %#04x %#04x\n",
+ ds_info->bksv[0] & 0xFF, ds_info->bksv[1] & 0xFF,
+ ds_info->bksv[2] & 0xFF, ds_info->bksv[3] & 0xFF,
+ ds_info->bksv[4] & 0xFF);
+
+ if (ds_info->is_repeater) {
+ igt_info("\tHDCP sink is a Repeater\n");
+
+ igt_info("\tDepth: %d, Device count: %d\n", ds_info->depth,
+ ds_info->device_count);
+ ksvs = ds_info->ksv_list;
+
+ for (i = 0; i < ds_info->device_count; i++) {
+ igt_info("\tksv-%d: %#04x %#04x %#04x %#04x %#04x\n", i,
+ ksvs[0] & 0xFF, ksvs[1] & 0xFF,
+ ksvs[2] & 0xFF, ksvs[3] & 0xFF,
+ ksvs[4] & 0xFF);
+ ksvs += DRM_MODE_HDCP_KSV_LEN;
+ }
+ } else {
+ igt_info("\tHDCP sink is a Receiver\n");
+ }
+}
+
+static void retrieve_topology_info_prepare_srm(igt_output_t *output)
+{
+ drmModePropertyBlobRes *ds_info_prop = NULL;
+ uint64_t topology_blob_id;
+ struct hdcp_topology_info *ds_info;
+ int i;
+
+ igt_info("HDCP topology property is attached\n");
+
+ topology_blob_id = igt_output_get_prop(output,
+ IGT_CONNECTOR_HDCP_TOPOLOGY_INFO);
+
+ igt_assert_f(topology_blob_id,
+ "Invalid topology blob id\n");
+
+ ds_info_prop = drmModeGetPropertyBlob(data.drm_fd,
+ topology_blob_id);
+
+ igt_assert(ds_info_prop);
+ igt_assert_eq(ds_info_prop->length,
+ sizeof(struct hdcp_topology_info));
+ ds_info = ds_info_prop->data;
+
+ parse_topology_info(ds_info);
+
+ for (i = 0; i < 5; i++)
+ facsimile_srm[i + 9] = ds_info->bksv[i];
+}
+
static void flip_handler(int fd, unsigned int sequence, unsigned int tv_sec,
unsigned int tv_usec, void *_data)
{
@@ -355,7 +467,7 @@ static void test_cp_disable(igt_output_t *output, enum igt_commit_style s)
static void test_cp_enable_with_retry(igt_output_t *output,
enum igt_commit_style s, int retry,
int content_type, bool expect_failure,
- bool test_type_change)
+ bool test_type_change, bool test_srm)
{
int retry_orig = retry;
bool ret;
@@ -376,6 +488,9 @@ static void test_cp_enable_with_retry(igt_output_t *output,
if (expect_failure)
igt_assert_f(!ret,
"CP Enabled. Though it is expected to fail\n");
+ else if (test_srm)
+ igt_assert_f(!ret,
+ "CP Enabled. Though ID is revoked through SRM\n");
else
igt_assert_f(ret, "Content Protection not enabled\n");
}
@@ -400,15 +515,37 @@ static void test_cp_lic(igt_output_t *output)
igt_assert_f(!ret, "Content Protection LIC Failed\n");
}

+static bool write_srm_as_fw(const __u8 *srm, int len)
+{
+ int fd, ret, total = 0;
+
+ fd = open("/lib/firmware/display_hdcp_srm.bin", O_WRONLY | O_CREAT);
+ do {
+ ret = write(fd, srm + total, len - total);
+ if (ret < 0)
+ ret = -errno;
+ if (ret == -EINTR || ret == -EAGAIN)
+ continue;
+ if (ret <= 0)
+ break;
+ total += ret;
+ } while (total != len);
+ close(fd);
+
+ return total < len ? false : true;
+}
+
static void
test_content_protection_on_output(igt_output_t *output, enum igt_commit_style s,
bool dpms_test, int content_type,
- bool mei_reload_test, bool test_type_change)
+ bool mei_reload_test, bool test_type_change,
+ bool test_srm)
{
igt_display_t *display = &data.display;
igt_plane_t *primary;
enum pipe pipe;
- bool ret;
+ bool ret, srm_modified = false;
+ int i;

for_each_pipe(display, pipe) {
if (!igt_pipe_connector_valid(pipe, output))
@@ -423,13 +560,15 @@ test_content_protection_on_output(igt_output_t *output, enum igt_commit_style s,
if (!igt_pipe_is_free(display, pipe))
continue;

+ srm_modified = false;
modeset_with_fb(pipe, output, s);
test_cp_enable_with_retry(output, s, 3, content_type, false,
- false);
+ false, false);
+
if (test_type_change && content_type == HDCP_CONTENT_TYPE_1)
test_cp_enable_with_retry(output, s, 3,
HDCP_CONTENT_TYPE_0, false,
- test_type_change);
+ test_type_change, false);

if (mei_reload_test) {
igt_assert_f(!igt_kmod_unload("mei_hdcp", 0),
@@ -437,18 +576,47 @@ test_content_protection_on_output(igt_output_t *output, enum igt_commit_style s,

/* Expected to fail */
test_cp_enable_with_retry(output, s, 3,
- content_type, true, false);
+ content_type, true, false,
+ false);

igt_assert_f(!igt_kmod_load("mei_hdcp", NULL),
"mei_hdcp load failed");

/* Expected to pass */
test_cp_enable_with_retry(output, s, 3,
- content_type, false, false);
+ content_type, false, false,
+ false);
}

test_cp_lic(output);

+ if (output->props[IGT_CONNECTOR_HDCP_TOPOLOGY_INFO] &&
+ test_srm) {
+ retrieve_topology_info_prepare_srm(output);
+ srm_modified =
+ write_srm_as_fw((const __u8 *)facsimile_srm,
+ sizeof(facsimile_srm));
+ igt_assert_f(srm_modified, "SRM update failed");
+ }
+
+ if (test_srm && srm_modified) {
+ test_cp_disable(output, s);
+ test_cp_enable_with_retry(output, s, 3, content_type,
+ false, false, test_srm);
+
+ /* Removing the sink's Receiver ID from SRM Blob */
+ for (i = 0; i < 5; i++)
+ facsimile_srm[i + 9] = 0;
+
+ srm_modified =
+ write_srm_as_fw((const __u8 *)facsimile_srm,
+ sizeof(facsimile_srm));
+ igt_assert_f(srm_modified, "SRM update failed");
+
+ test_cp_enable_with_retry(output, s, 1, content_type,
+ false, false, false);
+ }
+
if (dpms_test) {
igt_pipe_set_prop_value(display, pipe,
IGT_CRTC_ACTIVE, 0);
@@ -463,7 +631,7 @@ test_content_protection_on_output(igt_output_t *output, enum igt_commit_style s,
if (!ret)
test_cp_enable_with_retry(output, s, 2,
content_type, false,
- false);
+ false, false);
}

test_cp_disable(output, s);
@@ -529,7 +697,7 @@ static bool sink_hdcp2_capable(igt_output_t *output)
static void
test_content_protection(enum igt_commit_style s, bool dpms_test,
int content_type, bool mei_reload_test,
- bool test_type_change)
+ bool test_type_change, bool test_srm)
{
igt_display_t *display = &data.display;
igt_output_t *output;
@@ -560,9 +728,8 @@ test_content_protection(enum igt_commit_style s, bool dpms_test,
}

test_content_protection_on_output(output, s, dpms_test,
- content_type,
- mei_reload_test,
- test_type_change);
+ content_type, mei_reload_test,
+ test_type_change, test_srm);
valid_tests++;
}

@@ -582,33 +749,33 @@ igt_main
igt_subtest("legacy")
test_content_protection(COMMIT_LEGACY, false,
HDCP_CONTENT_TYPE_0, false,
- false);
+ false, false);

igt_subtest("atomic") {
igt_require(data.display.is_atomic);
test_content_protection(COMMIT_ATOMIC, false,
- HDCP_CONTENT_TYPE_0, false,
+ HDCP_CONTENT_TYPE_0, false, false,
false);
}

igt_subtest("atomic-dpms") {
igt_require(data.display.is_atomic);
test_content_protection(COMMIT_ATOMIC, true,
- HDCP_CONTENT_TYPE_0, false,
+ HDCP_CONTENT_TYPE_0, false, false,
false);
}

igt_subtest("type1") {
igt_require(data.display.is_atomic);
test_content_protection(COMMIT_ATOMIC, false,
- HDCP_CONTENT_TYPE_1, false,
+ HDCP_CONTENT_TYPE_1, false, false,
false);
}

igt_subtest("type1_mei_interface") {
igt_require(data.display.is_atomic);
test_content_protection(COMMIT_ATOMIC, false,
- HDCP_CONTENT_TYPE_1, true,
+ HDCP_CONTENT_TYPE_1, true, false,
false);
}

@@ -616,9 +783,36 @@ igt_main
igt_require(data.display.is_atomic);
test_content_protection(COMMIT_ATOMIC, false,
HDCP_CONTENT_TYPE_1, false,
- true);
+ true, false);
}

+ /*
+ * SRM subtest perform the HDCP authentication, and then retrive the
+ * receiver id through topology info.
+ *
+ * Using the receiver ID, facsimile SRM table is modified with
+ * receiver ID retrieved from teh topology info. After modification
+ * SRM table is not intact as per DCP Signature.
+ *
+ * But Kernel believes userspace and doesn't verify the DCP signature.
+ * So we can exploite that trust to test the SRM and topology info
+ * features.
+ *
+ * So when modified SRM is applied Authentication will fail due to
+ * receiver ID revocation.
+ *
+ * And Kernel attempts HDCP2.2 always and on failure of it HDCP1.4
+ * will be attempted. But their ID of the sink varies between 1.4 and
+ * 2.2 versions. So we need to stick to one version. Hence HDCP2.2 is
+ * choosen using Type 1.
+ */
+ igt_subtest("srm") {
+ igt_require(data.display.is_atomic);
+ test_content_protection(COMMIT_ATOMIC, false,
+ HDCP_CONTENT_TYPE_1, false, false, true);
+ }
+
+
igt_fixture
igt_display_fini(&data.display);
}
--
2.19.1