Commit 9e50b9d5 authored by Ville Syrjälä's avatar Ville Syrjälä Committed by Dave Airlie

drm: edid: Add some bounds checking

Make sure drm_detect_hdmi_monitor() and drm_detect_monitor_audio() don't
access beyond the extension block.
Signed-off-by: default avatarVille Syrjälä <ville.syrjala@linux.intel.com>
Reviewed-by: default avatarAdam Jackson <ajax@redhat.com>
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 4a1897d2
...@@ -1516,6 +1516,40 @@ do_cea_modes (struct drm_connector *connector, u8 *db, u8 len) ...@@ -1516,6 +1516,40 @@ do_cea_modes (struct drm_connector *connector, u8 *db, u8 len)
return modes; return modes;
} }
static int
cea_db_payload_len(const u8 *db)
{
return db[0] & 0x1f;
}
static int
cea_db_tag(const u8 *db)
{
return db[0] >> 5;
}
static int
cea_revision(const u8 *cea)
{
return cea[1];
}
static int
cea_db_offsets(const u8 *cea, int *start, int *end)
{
/* Data block offset in CEA extension block */
*start = 4;
*end = cea[2];
if (*end == 0)
*end = 127;
if (*end < 4 || *end > 127)
return -ERANGE;
return 0;
}
#define for_each_cea_db(cea, i, start, end) \
for ((i) = (start); (i) < (end) && (i) + cea_db_payload_len(&(cea)[(i)]) < (end); (i) += cea_db_payload_len(&(cea)[(i)]) + 1)
static int static int
add_cea_modes(struct drm_connector *connector, struct edid *edid) add_cea_modes(struct drm_connector *connector, struct edid *edid)
{ {
...@@ -1523,10 +1557,17 @@ add_cea_modes(struct drm_connector *connector, struct edid *edid) ...@@ -1523,10 +1557,17 @@ add_cea_modes(struct drm_connector *connector, struct edid *edid)
u8 * db, dbl; u8 * db, dbl;
int modes = 0; int modes = 0;
if (cea && cea[1] >= 3) { if (cea && cea_revision(cea) >= 3) {
for (db = cea + 4; db < cea + cea[2]; db += dbl + 1) { int i, start, end;
dbl = db[0] & 0x1f;
if (((db[0] & 0xe0) >> 5) == VIDEO_BLOCK) if (cea_db_offsets(cea, &start, &end))
return 0;
for_each_cea_db(cea, i, start, end) {
db = &cea[i];
dbl = cea_db_payload_len(db);
if (cea_db_tag(db) == VIDEO_BLOCK)
modes += do_cea_modes (connector, db+1, dbl); modes += do_cea_modes (connector, db+1, dbl);
} }
} }
...@@ -1617,18 +1658,28 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) ...@@ -1617,18 +1658,28 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid)
eld[18] = edid->prod_code[0]; eld[18] = edid->prod_code[0];
eld[19] = edid->prod_code[1]; eld[19] = edid->prod_code[1];
if (cea[1] >= 3) if (cea_revision(cea) >= 3) {
for (db = cea + 4; db < cea + cea[2]; db += dbl + 1) { int i, start, end;
dbl = db[0] & 0x1f;
switch ((db[0] & 0xe0) >> 5) { if (cea_db_offsets(cea, &start, &end)) {
start = 0;
end = 0;
}
for_each_cea_db(cea, i, start, end) {
db = &cea[i];
dbl = cea_db_payload_len(db);
switch (cea_db_tag(db)) {
case AUDIO_BLOCK: case AUDIO_BLOCK:
/* Audio Data Block, contains SADs */ /* Audio Data Block, contains SADs */
sad_count = dbl / 3; sad_count = dbl / 3;
if (dbl >= 1)
memcpy(eld + 20 + mnl, &db[1], dbl); memcpy(eld + 20 + mnl, &db[1], dbl);
break; break;
case SPEAKER_BLOCK: case SPEAKER_BLOCK:
/* Speaker Allocation Data Block */ /* Speaker Allocation Data Block */
if (dbl >= 1)
eld[7] = db[1]; eld[7] = db[1];
break; break;
case VENDOR_BLOCK: case VENDOR_BLOCK:
...@@ -1640,6 +1691,7 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) ...@@ -1640,6 +1691,7 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid)
break; break;
} }
} }
}
eld[5] |= sad_count << 4; eld[5] |= sad_count << 4;
eld[2] = (20 + mnl + sad_count * 3 + 3) / 4; eld[2] = (20 + mnl + sad_count * 3 + 3) / 4;
...@@ -1725,19 +1777,16 @@ bool drm_detect_hdmi_monitor(struct edid *edid) ...@@ -1725,19 +1777,16 @@ bool drm_detect_hdmi_monitor(struct edid *edid)
if (!edid_ext) if (!edid_ext)
goto end; goto end;
/* Data block offset in CEA extension block */ if (cea_db_offsets(edid_ext, &start_offset, &end_offset))
start_offset = 4; goto end;
end_offset = edid_ext[2];
/* /*
* Because HDMI identifier is in Vendor Specific Block, * Because HDMI identifier is in Vendor Specific Block,
* search it from all data blocks of CEA extension. * search it from all data blocks of CEA extension.
*/ */
for (i = start_offset; i < end_offset; for_each_cea_db(edid_ext, i, start_offset, end_offset) {
/* Increased by data block len */
i += ((edid_ext[i] & 0x1f) + 1)) {
/* Find vendor specific block */ /* Find vendor specific block */
if ((edid_ext[i] >> 5) == VENDOR_BLOCK) { if (cea_db_tag(&edid_ext[i]) == VENDOR_BLOCK) {
hdmi_id = edid_ext[i + 1] | (edid_ext[i + 2] << 8) | hdmi_id = edid_ext[i + 1] | (edid_ext[i + 2] << 8) |
edid_ext[i + 3] << 16; edid_ext[i + 3] << 16;
/* Find HDMI identifier */ /* Find HDMI identifier */
...@@ -1780,15 +1829,13 @@ bool drm_detect_monitor_audio(struct edid *edid) ...@@ -1780,15 +1829,13 @@ bool drm_detect_monitor_audio(struct edid *edid)
goto end; goto end;
} }
/* Data block offset in CEA extension block */ if (cea_db_offsets(edid_ext, &start_offset, &end_offset))
start_offset = 4; goto end;
end_offset = edid_ext[2];
for (i = start_offset; i < end_offset; for_each_cea_db(edid_ext, i, start_offset, end_offset) {
i += ((edid_ext[i] & 0x1f) + 1)) { if (cea_db_tag(&edid_ext[i]) == AUDIO_BLOCK) {
if ((edid_ext[i] >> 5) == AUDIO_BLOCK) {
has_audio = true; has_audio = true;
for (j = 1; j < (edid_ext[i] & 0x1f); j += 3) for (j = 1; j < cea_db_payload_len(&edid_ext[i]) + 1; j += 3)
DRM_DEBUG_KMS("CEA audio format %d\n", DRM_DEBUG_KMS("CEA audio format %d\n",
(edid_ext[i + j] >> 3) & 0xf); (edid_ext[i + j] >> 3) & 0xf);
goto end; goto end;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment