Commit message: renderer: fix memory leak in vertex elements state create

Reported-by: Li Qiang
Free the vertex array in error path.
This was introduced by this commit:
renderer: fix heap overflow in vertex elements state create.

I rewrote the code to not require the allocation in the first
place if we have an error, seems nicer.

Signed-off-by: Dave Airlie <airlied@redhat.com>
CVE: CVE-2017-6386
CWE: CWE-772
Vulnerability description: Memory leak in the vrend_create_vertex_elements_state function in vrend_renderer.c in virglrenderer allows local guest OS users to cause a denial of service (host memory consumption) via a large number of VIRGL_OBJECT_VERTEX_ELEMENTS commands.

Project: virglrenderer
File: vrend_renderer.c

static inline int conv_shader_type(int type)
{
   switch (type) {
   case PIPE_SHADER_VERTEX: return GL_VERTEX_SHADER;
   case PIPE_SHADER_FRAGMENT: return GL_FRAGMENT_SHADER;
   case PIPE_SHADER_GEOMETRY: return GL_GEOMETRY_SHADER;
   default:
      return 0;
   };
}

Project: virglrenderer
File: vrend_renderer.c

static GLuint convert_wrap(int wrap)
{
   switch(wrap){
   case PIPE_TEX_WRAP_REPEAT: return GL_REPEAT;
   case PIPE_TEX_WRAP_CLAMP: if (vrend_state.use_core_profile == false) return GL_CLAMP; else return GL_CLAMP_TO_EDGE;

   case PIPE_TEX_WRAP_CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE;
   case PIPE_TEX_WRAP_CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER;

   case PIPE_TEX_WRAP_MIRROR_REPEAT: return GL_MIRRORED_REPEAT;
   case PIPE_TEX_WRAP_MIRROR_CLAMP: return GL_MIRROR_CLAMP_EXT;
   case PIPE_TEX_WRAP_MIRROR_CLAMP_TO_EDGE: return GL_MIRROR_CLAMP_TO_EDGE_EXT;
   case PIPE_TEX_WRAP_MIRROR_CLAMP_TO_BORDER: return GL_MIRROR_CLAMP_TO_BORDER_EXT;
   default:
      assert(0);
      return -1;
   }
}

Project: virglrenderer
File: vrend_renderer.c

static void free_fence_locked(struct vrend_fence *fence)
{
   list_del(&fence->fences);
   glDeleteSync(fence->syncobj);
   free(fence);
}

Project: virglrenderer
File: vrend_renderer.c

static char *get_skip_str(int *skip_val)
{
   char *start_skip = NULL;
   if (*skip_val < 0) {
      *skip_val = 0;
      return NULL;
   }

   if (*skip_val == 1) {
      start_skip = strdup("gl_SkipComponents1");
      *skip_val -= 1;
   } else if (*skip_val == 2) {
      start_skip = strdup("gl_SkipComponents2");
      *skip_val -= 2;
   } else if (*skip_val == 3) {
      start_skip = strdup("gl_SkipComponents3");
      *skip_val -= 3;
   } else if (*skip_val >= 4) {
      start_skip = strdup("gl_SkipComponents4");
      *skip_val -= 4;
   }
   return start_skip;
}

Project: virglrenderer
File: vrend_renderer.c

static void iov_buffer_upload(void *cookie, uint32_t doff, void *src, int len)
{
   struct virgl_sub_upload_data *d = cookie;
   glBufferSubData(d->target, d->box->x + doff, len, src);
}

Project: virglrenderer
File: vrend_renderer.c

void vrend_bind_sampler_states(struct vrend_context *ctx,
                               uint32_t shader_type,
                               uint32_t start_slot,
                               uint32_t num_states,
                               uint32_t *handles)
{
   int i;
   struct vrend_sampler_state *state;

   if (shader_type >= PIPE_SHADER_TYPES) {
      report_context_error(ctx, VIRGL_ERROR_CTX_ILLEGAL_CMD_BUFFER, shader_type);
      return;
   }

   if (num_states > PIPE_MAX_SAMPLERS ||
       start_slot > (PIPE_MAX_SAMPLERS - num_states)) {
      report_context_error(ctx, VIRGL_ERROR_CTX_ILLEGAL_CMD_BUFFER, num_states);
      return;
   }

   ctx->sub->num_sampler_states[shader_type] = num_states;

   for (i = 0; i < num_states; i++) {
      if (handles[i] == 0)
         state = NULL;
      else
         state = vrend_object_lookup(ctx->sub->object_hash, handles[i], VIRGL_OBJECT_SAMPLER_STATE);

      ctx->sub->sampler_state[shader_type][i + start_slot] = state;
   }
   ctx->sub->sampler_state_dirty = true;
}

Project: virglrenderer
File: vrend_renderer.c

static void vrend_destroy_query(struct vrend_query *query)
{
   vrend_resource_reference(&query->res, NULL);
   list_del(&query->waiting_queries);
   glDeleteQueries(1, &query->id);
   free(query);
}

Project: virglrenderer
File: vrend_renderer.c

static inline bool vrend_format_can_sample(enum virgl_formats format)
{
   return tex_conv_table[format].bindings & VREND_BIND_SAMPLER;
}

Project: virglrenderer
File: vrend_renderer.c

vrend_insert_format(struct vrend_format_table *entry, uint32_t bindings)
{
   tex_conv_table[entry->format] = *entry;
   tex_conv_table[entry->format].bindings = bindings;
}

Project: virglrenderer
File: vrend_renderer.c

void vrend_render_condition(struct vrend_context *ctx,
                            uint32_t handle,
                            bool condition,
                            uint mode)
{
   struct vrend_query *q;
   GLenum glmode = 0;

   if (handle == 0) {
      glEndConditionalRenderNV();
      ctx->sub->cond_render_q_id = 0;
      ctx->sub->cond_render_gl_mode = 0;
      return;
   }

   q = vrend_object_lookup(ctx->sub->object_hash, handle, VIRGL_OBJECT_QUERY);
   if (!q)
      return;

   switch (mode) {
   case PIPE_RENDER_COND_WAIT:
      glmode = GL_QUERY_WAIT;
      break;
   case PIPE_RENDER_COND_NO_WAIT:
      glmode = GL_QUERY_NO_WAIT;
      break;
   case PIPE_RENDER_COND_BY_REGION_WAIT:
      glmode = GL_QUERY_BY_REGION_WAIT;
      break;
   case PIPE_RENDER_COND_BY_REGION_NO_WAIT:
      glmode = GL_QUERY_BY_REGION_NO_WAIT;
      break;
   default:
      fprintf(stderr, "unhandled condition %x\n", mode);
   }

   ctx->sub->cond_render_q_id = q->id;
   ctx->sub->cond_render_gl_mode = glmode;
   glBeginConditionalRender(q->id, glmode);

}

Project: virglrenderer
File: vrend_renderer.c

void vrend_renderer_check_fences(void)
{
   struct vrend_fence *fence, *stor;
   uint32_t latest_id = 0;
   GLenum glret;

   if (!vrend_state.inited)
      return;

   if (vrend_state.sync_thread) {
      flush_eventfd(vrend_state.eventfd);
      pipe_mutex_lock(vrend_state.fence_mutex);
      LIST_FOR_EACH_ENTRY_SAFE(fence, stor, &vrend_state.fence_list, fences) {
         if (fence->fence_id > latest_id)
            latest_id = fence->fence_id;
         free_fence_locked(fence);
      }
      pipe_mutex_unlock(vrend_state.fence_mutex);
   } else {
      vrend_renderer_force_ctx_0();

      LIST_FOR_EACH_ENTRY_SAFE(fence, stor, &vrend_state.fence_list, fences) {
         glret = glClientWaitSync(fence->syncobj, 0, 0);
         if (glret == GL_ALREADY_SIGNALED){
            latest_id = fence->fence_id;
            free_fence_locked(fence);
         }
         /* don't bother checking any subsequent ones */
         else if (glret == GL_TIMEOUT_EXPIRED) {
            break;
         }
      }
   }

   if (latest_id == 0)
      return;
   vrend_clicbs->write_fence(latest_id);
}

Project: virglrenderer
File: vrend_renderer.c

int vrend_renderer_resource_get_info(int res_handle,
                                     struct vrend_renderer_resource_info *info)
{
   struct vrend_resource *res;
   int elsize;

   if (!info)
      return EINVAL;
   res = vrend_resource_lookup(res_handle, 0);
   if (!res)
      return EINVAL;

   elsize = util_format_get_blocksize(res->base.format);

   info->handle = res_handle;
   info->tex_id = res->id;
   info->width = res->base.width0;
   info->height = res->base.height0;
   info->depth = res->base.depth0;
   info->format = res->base.format;
   info->flags = res->y_0_top ? VIRGL_RESOURCE_Y_0_TOP : 0;
   info->stride = util_format_get_nblocksx(res->base.format, u_minify(res->base.width0, 0)) * elsize;

   return 0;
}

Project: virglrenderer
File: vrend_renderer.c

int vrend_renderer_transfer_iov(const struct vrend_transfer_info *info,
                                int transfer_mode)
{
   struct vrend_resource *res;
   struct vrend_context *ctx;
   struct iovec *iov;
   int num_iovs;

   if (!info->box)
      return EINVAL;

   ctx = vrend_lookup_renderer_ctx(info->ctx_id);
   if (!ctx)
      return EINVAL;

   if (info->ctx_id == 0)
      res = vrend_resource_lookup(info->handle, 0);
   else
      res = vrend_renderer_ctx_res_lookup(ctx, info->handle);

   if (!res) {
      if (info->ctx_id)
         report_context_error(ctx, VIRGL_ERROR_CTX_ILLEGAL_RESOURCE, info->handle);
      return EINVAL;
   }

   iov = info->iovec;
   num_iovs = info->iovec_cnt;

   if (res->iov && (!iov || num_iovs == 0)) {
      iov = res->iov;
      num_iovs = res->num_iovs;
   }

   if (!iov) {
      if (info->ctx_id)
         report_context_error(ctx, VIRGL_ERROR_CTX_ILLEGAL_RESOURCE, info->handle);
      return EINVAL;
   }

   if (!check_transfer_bounds(res, info))
      return EINVAL;

   if (!check_iov_bounds(res, info, iov, num_iovs))
      return EINVAL;

   vrend_hw_switch_context(vrend_lookup_renderer_ctx(0), true);

   if (transfer_mode == VREND_TRANSFER_WRITE)
      return vrend_renderer_transfer_write_iov(ctx, res, iov, num_iovs,
                                               info);
   else
      return vrend_renderer_transfer_send_iov(ctx, res, iov, num_iovs,
                                              info);
}

Project: virglrenderer
File: vrend_renderer.c

void vrend_set_viewport_states(struct vrend_context *ctx,
                               uint32_t start_slot,
                               uint32_t num_viewports,
                               const struct pipe_viewport_state *state)
{
   /* convert back to glViewport */
   GLint x, y;
   GLsizei width, height;
   GLclampd near_val, far_val;
   bool viewport_is_negative = (state[0].scale[1] < 0) ? true : false;
   int i, idx;

   if (num_viewports > PIPE_MAX_VIEWPORTS ||
       start_slot > (PIPE_MAX_VIEWPORTS - num_viewports)) {
      report_context_error(ctx, VIRGL_ERROR_CTX_ILLEGAL_CMD_BUFFER, num_viewports);
      return;
   }

   for (i = 0; i < num_viewports; i++) {
      GLfloat abs_s1 = fabsf(state[i].scale[1]);

      idx = start_slot + i;
      width = state[i].scale[0] * 2.0f;
      height = abs_s1 * 2.0f;
      x = state[i].translate[0] - state[i].scale[0];
      y = state[i].translate[1] - state[i].scale[1];

      near_val = state[i].translate[2] - state[i].scale[2];
      far_val = near_val + (state[i].scale[2] * 2.0);

      if (ctx->sub->vps[idx].cur_x != x ||
          ctx->sub->vps[idx].cur_y != y ||
          ctx->sub->vps[idx].width != width ||
          ctx->sub->vps[idx].height != height) {
         ctx->sub->viewport_state_dirty |= (1 << idx);
         ctx->sub->vps[idx].cur_x = x;
         ctx->sub->vps[idx].cur_y = y;
         ctx->sub->vps[idx].width = width;
         ctx->sub->vps[idx].height = height;
      }

      if (idx == 0) {
         if (ctx->sub->viewport_is_negative != viewport_is_negative)
            ctx->sub->viewport_is_negative = viewport_is_negative;

         ctx->sub->depth_scale = fabsf(far_val - near_val);
         ctx->sub->depth_transform = near_val;
      }

      if (ctx->sub->vps[idx].near_val != near_val ||
          ctx->sub->vps[idx].far_val != far_val) {
         ctx->sub->vps[idx].near_val = near_val;
         ctx->sub->vps[idx].far_val = far_val;
         if (idx)
            glDepthRangeIndexed(idx, ctx->sub->vps[idx].near_val, ctx->sub->vps[idx].far_val);
         else
            glDepthRange(ctx->sub->vps[idx].near_val, ctx->sub->vps[idx].far_val);
      }
   }
}