diff mbox series

[v2,2/3] drm/nouveau: Check framebuffer size against bo

Message ID 20191217004520.2404-3-jajones@nvidia.com (mailing list archive)
State New, archived
Headers show
Series drm/nouveau: Support NVIDIA format modifiers | expand

Commit Message

James Jones Dec. 17, 2019, 12:45 a.m. UTC
Make sure framebuffer dimensions and tiling
parameters will not result in accesses beyond the
end of the GEM buffer they are bound to.

Signed-off-by: James Jones <jajones@nvidia.com>
---
 drivers/gpu/drm/nouveau/nouveau_display.c | 93 +++++++++++++++++++++++
 1 file changed, 93 insertions(+)

Comments

Ben Skeggs Jan. 6, 2020, 1:25 a.m. UTC | #1
On Tue, 17 Dec 2019 at 10:45, James Jones <jajones@nvidia.com> wrote:
>
> Make sure framebuffer dimensions and tiling
> parameters will not result in accesses beyond the
> end of the GEM buffer they are bound to.
>
> Signed-off-by: James Jones <jajones@nvidia.com>
> ---
>  drivers/gpu/drm/nouveau/nouveau_display.c | 93 +++++++++++++++++++++++
>  1 file changed, 93 insertions(+)
>
> diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
> index 6f038511a03a..f1509392d7b7 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_display.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_display.c
> @@ -224,6 +224,72 @@ static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = {
>         .create_handle = nouveau_user_framebuffer_create_handle,
>  };
>
> +static inline uint32_t
> +nouveau_get_width_in_blocks(uint32_t stride)
> +{
> +       /* GOBs per block in the x direction is always one, and GOBs are
> +        * 64 bytes wide
> +        */
> +       static const uint32_t log_block_width = 6;
> +
> +       return (stride + (1 << log_block_width) - 1) >> log_block_width;
> +}
> +
> +static inline uint32_t
> +nouveau_get_height_in_blocks(struct nouveau_drm *drm,
> +                            uint32_t height,
> +                            uint32_t log_block_height_in_gobs)
> +{
> +       uint32_t log_gob_height;
> +       uint32_t log_block_height;
> +
> +       BUG_ON(drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA);
> +
> +       if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
> +               log_gob_height = 2;
> +       else
> +               log_gob_height = 3;
> +
> +       log_block_height = log_block_height_in_gobs + log_gob_height;
> +
> +       return (height + (1 << log_block_height) - 1) >> log_block_height;
> +}
> +
> +static int
> +nouveau_check_bl_size(struct nouveau_drm *drm, struct nouveau_bo *nvbo,
> +                     uint32_t offset, uint32_t stride, uint32_t h,
> +                     uint32_t tile_mode)
> +{
> +       uint32_t gob_size, bw, bh;
> +       uint64_t bl_size;
> +
> +       BUG_ON(drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA);
> +
> +       if (drm->client.device.info.chipset >= 0xc0)
> +               tile_mode >>= 4;
> +
> +       BUG_ON(tile_mode & 0xFFFFFFF0);
As far as I can tell, tile_mode can be fed into this function
unsanitised from userspace, so we probably want something different to
a BUG_ON() here.

> +
> +       if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
> +               gob_size = 256;
> +       else
> +               gob_size = 512;
> +
> +       bw = nouveau_get_width_in_blocks(stride);
> +       bh = nouveau_get_height_in_blocks(drm, h, tile_mode);
> +
> +       bl_size = bw * bh * (1 << tile_mode) * gob_size;
> +
> +       DRM_DEBUG_KMS("offset=%u stride=%u h=%u tile_mode=0x%02x bw=%u bh=%u gob_size=%u bl_size=%llu size=%lu\n",
> +                     offset, stride, h, tile_mode, bw, bh, gob_size, bl_size,
> +                     nvbo->bo.mem.size);
> +
> +       if (bl_size + offset > nvbo->bo.mem.size)
> +               return -ERANGE;
> +
> +       return 0;
> +}
> +
>  int
>  nouveau_framebuffer_new(struct drm_device *dev,
>                         const struct drm_mode_fb_cmd2 *mode_cmd,
> @@ -232,6 +298,8 @@ nouveau_framebuffer_new(struct drm_device *dev,
>  {
>         struct nouveau_drm *drm = nouveau_drm(dev);
>         struct nouveau_framebuffer *fb;
> +       const struct drm_format_info *info;
> +       unsigned int width, height, i;
>         int ret;
>
>          /* YUV overlays have special requirements pre-NV50 */
> @@ -254,6 +322,31 @@ nouveau_framebuffer_new(struct drm_device *dev,
>                 return -EINVAL;
>         }
>
> +       info = drm_get_format_info(dev, mode_cmd);
> +
> +       for (i = 0; i < info->num_planes; i++) {
> +               width = drm_format_info_plane_width(info,
> +                                                   mode_cmd->width,
> +                                                   i);
> +               height = drm_format_info_plane_height(info,
> +                                                     mode_cmd->height,
> +                                                     i);
> +
> +               if (nvbo->kind) {
> +                       ret = nouveau_check_bl_size(drm, nvbo,
> +                                                   mode_cmd->offsets[i],
> +                                                   mode_cmd->pitches[i],
> +                                                   height, nvbo->mode);
> +                       if (ret)
> +                               return ret;
> +               } else {
> +                       uint32_t size = mode_cmd->pitches[i] * height;
> +
> +                       if (size + mode_cmd->offsets[i] > nvbo->bo.mem.size)
> +                               return -ERANGE;
> +               }
> +       }
> +
>         if (!(fb = *pfb = kzalloc(sizeof(*fb), GFP_KERNEL)))
>                 return -ENOMEM;
>
> --
> 2.17.1
>
> _______________________________________________
> Nouveau mailing list
> Nouveau@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/nouveau
James Jones Jan. 6, 2020, 7:06 p.m. UTC | #2
On 1/5/20 5:25 PM, Ben Skeggs wrote:
> On Tue, 17 Dec 2019 at 10:45, James Jones <jajones@nvidia.com> wrote:
>>
>> Make sure framebuffer dimensions and tiling
>> parameters will not result in accesses beyond the
>> end of the GEM buffer they are bound to.
>>
>> Signed-off-by: James Jones <jajones@nvidia.com>
>> ---
>>   drivers/gpu/drm/nouveau/nouveau_display.c | 93 +++++++++++++++++++++++
>>   1 file changed, 93 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
>> index 6f038511a03a..f1509392d7b7 100644
>> --- a/drivers/gpu/drm/nouveau/nouveau_display.c
>> +++ b/drivers/gpu/drm/nouveau/nouveau_display.c
>> @@ -224,6 +224,72 @@ static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = {
>>          .create_handle = nouveau_user_framebuffer_create_handle,
>>   };
>>
>> +static inline uint32_t
>> +nouveau_get_width_in_blocks(uint32_t stride)
>> +{
>> +       /* GOBs per block in the x direction is always one, and GOBs are
>> +        * 64 bytes wide
>> +        */
>> +       static const uint32_t log_block_width = 6;
>> +
>> +       return (stride + (1 << log_block_width) - 1) >> log_block_width;
>> +}
>> +
>> +static inline uint32_t
>> +nouveau_get_height_in_blocks(struct nouveau_drm *drm,
>> +                            uint32_t height,
>> +                            uint32_t log_block_height_in_gobs)
>> +{
>> +       uint32_t log_gob_height;
>> +       uint32_t log_block_height;
>> +
>> +       BUG_ON(drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA);
>> +
>> +       if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
>> +               log_gob_height = 2;
>> +       else
>> +               log_gob_height = 3;
>> +
>> +       log_block_height = log_block_height_in_gobs + log_gob_height;
>> +
>> +       return (height + (1 << log_block_height) - 1) >> log_block_height;
>> +}
>> +
>> +static int
>> +nouveau_check_bl_size(struct nouveau_drm *drm, struct nouveau_bo *nvbo,
>> +                     uint32_t offset, uint32_t stride, uint32_t h,
>> +                     uint32_t tile_mode)
>> +{
>> +       uint32_t gob_size, bw, bh;
>> +       uint64_t bl_size;
>> +
>> +       BUG_ON(drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA);
>> +
>> +       if (drm->client.device.info.chipset >= 0xc0)
>> +               tile_mode >>= 4;
>> +
>> +       BUG_ON(tile_mode & 0xFFFFFFF0);
> As far as I can tell, tile_mode can be fed into this function
> unsanitised from userspace, so we probably want something different to
> a BUG_ON() here.

Good catch.  I had assumed nouveau_bo::mode was validated at creation 
time.  I'll get this fixed up.

Thanks,
-James

>> +
>> +       if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
>> +               gob_size = 256;
>> +       else
>> +               gob_size = 512;
>> +
>> +       bw = nouveau_get_width_in_blocks(stride);
>> +       bh = nouveau_get_height_in_blocks(drm, h, tile_mode);
>> +
>> +       bl_size = bw * bh * (1 << tile_mode) * gob_size;
>> +
>> +       DRM_DEBUG_KMS("offset=%u stride=%u h=%u tile_mode=0x%02x bw=%u bh=%u gob_size=%u bl_size=%llu size=%lu\n",
>> +                     offset, stride, h, tile_mode, bw, bh, gob_size, bl_size,
>> +                     nvbo->bo.mem.size);
>> +
>> +       if (bl_size + offset > nvbo->bo.mem.size)
>> +               return -ERANGE;
>> +
>> +       return 0;
>> +}
>> +
>>   int
>>   nouveau_framebuffer_new(struct drm_device *dev,
>>                          const struct drm_mode_fb_cmd2 *mode_cmd,
>> @@ -232,6 +298,8 @@ nouveau_framebuffer_new(struct drm_device *dev,
>>   {
>>          struct nouveau_drm *drm = nouveau_drm(dev);
>>          struct nouveau_framebuffer *fb;
>> +       const struct drm_format_info *info;
>> +       unsigned int width, height, i;
>>          int ret;
>>
>>           /* YUV overlays have special requirements pre-NV50 */
>> @@ -254,6 +322,31 @@ nouveau_framebuffer_new(struct drm_device *dev,
>>                  return -EINVAL;
>>          }
>>
>> +       info = drm_get_format_info(dev, mode_cmd);
>> +
>> +       for (i = 0; i < info->num_planes; i++) {
>> +               width = drm_format_info_plane_width(info,
>> +                                                   mode_cmd->width,
>> +                                                   i);
>> +               height = drm_format_info_plane_height(info,
>> +                                                     mode_cmd->height,
>> +                                                     i);
>> +
>> +               if (nvbo->kind) {
>> +                       ret = nouveau_check_bl_size(drm, nvbo,
>> +                                                   mode_cmd->offsets[i],
>> +                                                   mode_cmd->pitches[i],
>> +                                                   height, nvbo->mode);
>> +                       if (ret)
>> +                               return ret;
>> +               } else {
>> +                       uint32_t size = mode_cmd->pitches[i] * height;
>> +
>> +                       if (size + mode_cmd->offsets[i] > nvbo->bo.mem.size)
>> +                               return -ERANGE;
>> +               }
>> +       }
>> +
>>          if (!(fb = *pfb = kzalloc(sizeof(*fb), GFP_KERNEL)))
>>                  return -ENOMEM;
>>
>> --
>> 2.17.1
>>
>> _______________________________________________
>> Nouveau mailing list
>> Nouveau@lists.freedesktop.org
>> https://lists.freedesktop.org/mailman/listinfo/nouveau
diff mbox series

Patch

diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c
index 6f038511a03a..f1509392d7b7 100644
--- a/drivers/gpu/drm/nouveau/nouveau_display.c
+++ b/drivers/gpu/drm/nouveau/nouveau_display.c
@@ -224,6 +224,72 @@  static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = {
 	.create_handle = nouveau_user_framebuffer_create_handle,
 };
 
+static inline uint32_t
+nouveau_get_width_in_blocks(uint32_t stride)
+{
+	/* GOBs per block in the x direction is always one, and GOBs are
+	 * 64 bytes wide
+	 */
+	static const uint32_t log_block_width = 6;
+
+	return (stride + (1 << log_block_width) - 1) >> log_block_width;
+}
+
+static inline uint32_t
+nouveau_get_height_in_blocks(struct nouveau_drm *drm,
+			     uint32_t height,
+			     uint32_t log_block_height_in_gobs)
+{
+	uint32_t log_gob_height;
+	uint32_t log_block_height;
+
+	BUG_ON(drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA);
+
+	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
+		log_gob_height = 2;
+	else
+		log_gob_height = 3;
+
+	log_block_height = log_block_height_in_gobs + log_gob_height;
+
+	return (height + (1 << log_block_height) - 1) >> log_block_height;
+}
+
+static int
+nouveau_check_bl_size(struct nouveau_drm *drm, struct nouveau_bo *nvbo,
+		      uint32_t offset, uint32_t stride, uint32_t h,
+		      uint32_t tile_mode)
+{
+	uint32_t gob_size, bw, bh;
+	uint64_t bl_size;
+
+	BUG_ON(drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA);
+
+	if (drm->client.device.info.chipset >= 0xc0)
+		tile_mode >>= 4;
+
+	BUG_ON(tile_mode & 0xFFFFFFF0);
+
+	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
+		gob_size = 256;
+	else
+		gob_size = 512;
+
+	bw = nouveau_get_width_in_blocks(stride);
+	bh = nouveau_get_height_in_blocks(drm, h, tile_mode);
+
+	bl_size = bw * bh * (1 << tile_mode) * gob_size;
+
+	DRM_DEBUG_KMS("offset=%u stride=%u h=%u tile_mode=0x%02x bw=%u bh=%u gob_size=%u bl_size=%llu size=%lu\n",
+		      offset, stride, h, tile_mode, bw, bh, gob_size, bl_size,
+		      nvbo->bo.mem.size);
+
+	if (bl_size + offset > nvbo->bo.mem.size)
+		return -ERANGE;
+
+	return 0;
+}
+
 int
 nouveau_framebuffer_new(struct drm_device *dev,
 			const struct drm_mode_fb_cmd2 *mode_cmd,
@@ -232,6 +298,8 @@  nouveau_framebuffer_new(struct drm_device *dev,
 {
 	struct nouveau_drm *drm = nouveau_drm(dev);
 	struct nouveau_framebuffer *fb;
+	const struct drm_format_info *info;
+	unsigned int width, height, i;
 	int ret;
 
         /* YUV overlays have special requirements pre-NV50 */
@@ -254,6 +322,31 @@  nouveau_framebuffer_new(struct drm_device *dev,
 		return -EINVAL;
 	}
 
+	info = drm_get_format_info(dev, mode_cmd);
+
+	for (i = 0; i < info->num_planes; i++) {
+		width = drm_format_info_plane_width(info,
+						    mode_cmd->width,
+						    i);
+		height = drm_format_info_plane_height(info,
+						      mode_cmd->height,
+						      i);
+
+		if (nvbo->kind) {
+			ret = nouveau_check_bl_size(drm, nvbo,
+						    mode_cmd->offsets[i],
+						    mode_cmd->pitches[i],
+						    height, nvbo->mode);
+			if (ret)
+				return ret;
+		} else {
+			uint32_t size = mode_cmd->pitches[i] * height;
+
+			if (size + mode_cmd->offsets[i] > nvbo->bo.mem.size)
+				return -ERANGE;
+		}
+	}
+
 	if (!(fb = *pfb = kzalloc(sizeof(*fb), GFP_KERNEL)))
 		return -ENOMEM;