Commit bdd2f9cd authored by Thierry Reding's avatar Thierry Reding

drm/tegra: Don't leak kernel pointer to userspace

Each open file descriptor can have any number of contexts associated
with it. To differentiate between these contexts a unique ID is required
and back when these userspace interfaces were introduced, in commit
d43f81cb ("drm/tegra: Add gr2d device"), the pointer to the context
structure was deemed adequate. However, this leaks information about
kernel internal memory to userspace, which can potentially be exploited.

Switch the context parameter to be allocated from an IDR, which has the
added benefit of providing an easy way to look up a context from its ID.
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent 347ad49d
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
*/ */
#include <linux/host1x.h> #include <linux/host1x.h>
#include <linux/idr.h>
#include <linux/iommu.h> #include <linux/iommu.h>
#include <drm/drm_atomic.h> #include <drm/drm_atomic.h>
...@@ -24,7 +25,8 @@ ...@@ -24,7 +25,8 @@
#define DRIVER_PATCHLEVEL 0 #define DRIVER_PATCHLEVEL 0
struct tegra_drm_file { struct tegra_drm_file {
struct list_head contexts; struct idr contexts;
struct mutex lock;
}; };
static void tegra_atomic_schedule(struct tegra_drm *tegra, static void tegra_atomic_schedule(struct tegra_drm *tegra,
...@@ -248,7 +250,8 @@ static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp) ...@@ -248,7 +250,8 @@ static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp)
if (!fpriv) if (!fpriv)
return -ENOMEM; return -ENOMEM;
INIT_LIST_HEAD(&fpriv->contexts); idr_init(&fpriv->contexts);
mutex_init(&fpriv->lock);
filp->driver_priv = fpriv; filp->driver_priv = fpriv;
return 0; return 0;
...@@ -427,21 +430,16 @@ int tegra_drm_submit(struct tegra_drm_context *context, ...@@ -427,21 +430,16 @@ int tegra_drm_submit(struct tegra_drm_context *context,
#ifdef CONFIG_DRM_TEGRA_STAGING #ifdef CONFIG_DRM_TEGRA_STAGING
static struct tegra_drm_context *tegra_drm_get_context(__u64 context) static struct tegra_drm_context *
tegra_drm_file_get_context(struct tegra_drm_file *file, u32 id)
{ {
return (struct tegra_drm_context *)(uintptr_t)context; struct tegra_drm_context *context;
}
static bool tegra_drm_file_owns_context(struct tegra_drm_file *file,
struct tegra_drm_context *context)
{
struct tegra_drm_context *ctx;
list_for_each_entry(ctx, &file->contexts, list) mutex_lock(&file->lock);
if (ctx == context) context = idr_find(&file->contexts, id);
return true; mutex_unlock(&file->lock);
return false; return context;
} }
static int tegra_gem_create(struct drm_device *drm, void *data, static int tegra_gem_create(struct drm_device *drm, void *data,
...@@ -522,6 +520,28 @@ static int tegra_syncpt_wait(struct drm_device *drm, void *data, ...@@ -522,6 +520,28 @@ static int tegra_syncpt_wait(struct drm_device *drm, void *data,
&args->value); &args->value);
} }
static int tegra_client_open(struct tegra_drm_file *fpriv,
struct tegra_drm_client *client,
struct tegra_drm_context *context)
{
int err;
err = client->ops->open_channel(client, context);
if (err < 0)
return err;
err = idr_alloc(&fpriv->contexts, context, 0, 0, GFP_KERNEL);
if (err < 0) {
client->ops->close_channel(context);
return err;
}
context->client = client;
context->id = err;
return 0;
}
static int tegra_open_channel(struct drm_device *drm, void *data, static int tegra_open_channel(struct drm_device *drm, void *data,
struct drm_file *file) struct drm_file *file)
{ {
...@@ -536,19 +556,22 @@ static int tegra_open_channel(struct drm_device *drm, void *data, ...@@ -536,19 +556,22 @@ static int tegra_open_channel(struct drm_device *drm, void *data,
if (!context) if (!context)
return -ENOMEM; return -ENOMEM;
mutex_lock(&fpriv->lock);
list_for_each_entry(client, &tegra->clients, list) list_for_each_entry(client, &tegra->clients, list)
if (client->base.class == args->client) { if (client->base.class == args->client) {
err = client->ops->open_channel(client, context); err = tegra_client_open(fpriv, client, context);
if (err) if (err < 0)
break; break;
list_add(&context->list, &fpriv->contexts); args->context = context->id;
args->context = (uintptr_t)context; break;
context->client = client;
return 0;
} }
kfree(context); if (err < 0)
kfree(context);
mutex_unlock(&fpriv->lock);
return err; return err;
} }
...@@ -558,16 +581,22 @@ static int tegra_close_channel(struct drm_device *drm, void *data, ...@@ -558,16 +581,22 @@ static int tegra_close_channel(struct drm_device *drm, void *data,
struct tegra_drm_file *fpriv = file->driver_priv; struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_close_channel *args = data; struct drm_tegra_close_channel *args = data;
struct tegra_drm_context *context; struct tegra_drm_context *context;
int err = 0;
context = tegra_drm_get_context(args->context); mutex_lock(&fpriv->lock);
if (!tegra_drm_file_owns_context(fpriv, context)) context = tegra_drm_file_get_context(fpriv, args->context);
return -EINVAL; if (!context) {
err = -EINVAL;
goto unlock;
}
list_del(&context->list); idr_remove(&fpriv->contexts, context->id);
tegra_drm_context_free(context); tegra_drm_context_free(context);
return 0; unlock:
mutex_unlock(&fpriv->lock);
return err;
} }
static int tegra_get_syncpt(struct drm_device *drm, void *data, static int tegra_get_syncpt(struct drm_device *drm, void *data,
...@@ -577,19 +606,27 @@ static int tegra_get_syncpt(struct drm_device *drm, void *data, ...@@ -577,19 +606,27 @@ static int tegra_get_syncpt(struct drm_device *drm, void *data,
struct drm_tegra_get_syncpt *args = data; struct drm_tegra_get_syncpt *args = data;
struct tegra_drm_context *context; struct tegra_drm_context *context;
struct host1x_syncpt *syncpt; struct host1x_syncpt *syncpt;
int err = 0;
context = tegra_drm_get_context(args->context); mutex_lock(&fpriv->lock);
if (!tegra_drm_file_owns_context(fpriv, context)) context = tegra_drm_file_get_context(fpriv, args->context);
return -ENODEV; if (!context) {
err = -ENODEV;
goto unlock;
}
if (args->index >= context->client->base.num_syncpts) if (args->index >= context->client->base.num_syncpts) {
return -EINVAL; err = -EINVAL;
goto unlock;
}
syncpt = context->client->base.syncpts[args->index]; syncpt = context->client->base.syncpts[args->index];
args->id = host1x_syncpt_id(syncpt); args->id = host1x_syncpt_id(syncpt);
return 0; unlock:
mutex_unlock(&fpriv->lock);
return err;
} }
static int tegra_submit(struct drm_device *drm, void *data, static int tegra_submit(struct drm_device *drm, void *data,
...@@ -598,13 +635,21 @@ static int tegra_submit(struct drm_device *drm, void *data, ...@@ -598,13 +635,21 @@ static int tegra_submit(struct drm_device *drm, void *data,
struct tegra_drm_file *fpriv = file->driver_priv; struct tegra_drm_file *fpriv = file->driver_priv;
struct drm_tegra_submit *args = data; struct drm_tegra_submit *args = data;
struct tegra_drm_context *context; struct tegra_drm_context *context;
int err;
context = tegra_drm_get_context(args->context); mutex_lock(&fpriv->lock);
if (!tegra_drm_file_owns_context(fpriv, context)) context = tegra_drm_file_get_context(fpriv, args->context);
return -ENODEV; if (!context) {
err = -ENODEV;
goto unlock;
}
err = context->client->ops->submit(context, args, drm, file);
return context->client->ops->submit(context, args, drm, file); unlock:
mutex_unlock(&fpriv->lock);
return err;
} }
static int tegra_get_syncpt_base(struct drm_device *drm, void *data, static int tegra_get_syncpt_base(struct drm_device *drm, void *data,
...@@ -615,24 +660,34 @@ static int tegra_get_syncpt_base(struct drm_device *drm, void *data, ...@@ -615,24 +660,34 @@ static int tegra_get_syncpt_base(struct drm_device *drm, void *data,
struct tegra_drm_context *context; struct tegra_drm_context *context;
struct host1x_syncpt_base *base; struct host1x_syncpt_base *base;
struct host1x_syncpt *syncpt; struct host1x_syncpt *syncpt;
int err = 0;
context = tegra_drm_get_context(args->context); mutex_lock(&fpriv->lock);
if (!tegra_drm_file_owns_context(fpriv, context)) context = tegra_drm_file_get_context(fpriv, args->context);
return -ENODEV; if (!context) {
err = -ENODEV;
goto unlock;
}
if (args->syncpt >= context->client->base.num_syncpts) if (args->syncpt >= context->client->base.num_syncpts) {
return -EINVAL; err = -EINVAL;
goto unlock;
}
syncpt = context->client->base.syncpts[args->syncpt]; syncpt = context->client->base.syncpts[args->syncpt];
base = host1x_syncpt_get_base(syncpt); base = host1x_syncpt_get_base(syncpt);
if (!base) if (!base) {
return -ENXIO; err = -ENXIO;
goto unlock;
}
args->id = host1x_syncpt_base_id(base); args->id = host1x_syncpt_base_id(base);
return 0; unlock:
mutex_unlock(&fpriv->lock);
return err;
} }
static int tegra_gem_set_tiling(struct drm_device *drm, void *data, static int tegra_gem_set_tiling(struct drm_device *drm, void *data,
...@@ -841,14 +896,25 @@ static void tegra_drm_disable_vblank(struct drm_device *drm, unsigned int pipe) ...@@ -841,14 +896,25 @@ static void tegra_drm_disable_vblank(struct drm_device *drm, unsigned int pipe)
tegra_dc_disable_vblank(dc); tegra_dc_disable_vblank(dc);
} }
static int tegra_drm_context_cleanup(int id, void *p, void *data)
{
struct tegra_drm_context *context = p;
tegra_drm_context_free(context);
return 0;
}
static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file) static void tegra_drm_preclose(struct drm_device *drm, struct drm_file *file)
{ {
struct tegra_drm_file *fpriv = file->driver_priv; struct tegra_drm_file *fpriv = file->driver_priv;
struct tegra_drm_context *context, *tmp;
list_for_each_entry_safe(context, tmp, &fpriv->contexts, list) mutex_lock(&fpriv->lock);
tegra_drm_context_free(context); idr_for_each(&fpriv->contexts, tegra_drm_context_cleanup, NULL);
mutex_unlock(&fpriv->lock);
idr_destroy(&fpriv->contexts);
mutex_destroy(&fpriv->lock);
kfree(fpriv); kfree(fpriv);
} }
......
...@@ -68,7 +68,7 @@ struct tegra_drm_client; ...@@ -68,7 +68,7 @@ struct tegra_drm_client;
struct tegra_drm_context { struct tegra_drm_context {
struct tegra_drm_client *client; struct tegra_drm_client *client;
struct host1x_channel *channel; struct host1x_channel *channel;
struct list_head list; unsigned int id;
}; };
struct tegra_drm_client_ops { struct tegra_drm_client_ops {
......
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