[igt-dev] [PATCH i-g-t v5 5/6] kms_content_protection: srm and topology_info test
Ramalingam C
ramalingam.c at intel.com
Tue Apr 30 12:33:16 UTC 2019
On 2019-04-29 at 17:24:29 +0200, Daniel Vetter wrote:
> On Thu, Apr 18, 2019 at 02:18:41PM +0530, Ramalingam C wrote:
> > 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 at 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.
>
> Oops, just realized that this is a bug in the kernel. Here's the slightly
> nasty attack:
>
> - create a fake SRM with no revocations but really high revision number
> - kernel will reject updates from here on.
No. Kernel reads the SRM from /lib/firmware everytime written by
userspace. And SRM Signature is validated by userspace and written there
for kernel consumption.
>
> Or do we just shrug and say userspace needs to validate the SRM fully
> before it drops it into /lib/firmware?
Yes we heavely rely on userspace to provide the latest signature validated SRM.
Kernel just checks the validness of SRM table structure and consumes it.
>
> Plan B for SRM testing would be that we simply drop an SRM into
> /lib/firmware (a valid one) and make sure the kernel doesn't blow up. Not
> a great testcase, but better than nothing.
>
> I guess proper SRM testing needs some kind of hdcp validation (special
> dongle that can optionally use a blacklisted ksv), but we don't have that
> stuff in igt or our CI.
>
> There's also the issue that I'm not sure we can merge the topolgoy info
> prop, so for now I'd split out a really simple subtest that checks the
> kernel doesn't blow up when loading the facsimile srm.
Yes without topology patch in upstream we can't upstream this test. As
you mentioned we can just right SRM into kernel and validate the success
of write as a subtest.
-Ram
> -Daniel
>
> > + */
> > + 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
> >
> > _______________________________________________
> > igt-dev mailing list
> > igt-dev at lists.freedesktop.org
> > https://lists.freedesktop.org/mailman/listinfo/igt-dev
>
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
More information about the igt-dev
mailing list