Exploit-DB #39961
Description: Google Chrome - GPU Process MailboxManagerImpl Double-Read
Type: dos
Platform: linux
Date: 2016-06-15
Author: Google Security Research

Exploit:
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=780

Several functions in the GPU command buffer service interact with the GPU 
mailbox manager (gpu/command_buffer/service/mailbox_manager_impl.cc), passing a reference to shared memory as the mailbox argument.

MailboxManagerImpl does not expect this mailbox argument to be malleable in this way, and it is in several places copied and passed to various stl functions, resulting in unexpected behaviour from double-reads when an attacker modifies the mailbox name mid function.

The attached POC uses the GPU command 'ProduceTextureDirectCHROMIUMImmediate' to trigger a use-after-free but other commands that interact with the mailbox manager should also be vulnerable in a similar way.

error::Error GLES2DecoderImpl::HandleProduceTextureDirectCHROMIUMImmediate(
    uint32_t immediate_data_size,
    const void* cmd_data) {
  const gles2::cmds::ProduceTextureDirectCHROMIUMImmediate& c =
      *static_cast<const gles2::cmds::ProduceTextureDirectCHROMIUMImmediate*>(
          cmd_data);
  (void)c;
  GLuint texture = c.texture;
  GLenum target = static_cast<GLenum>(c.target);
  uint32_t data_size;
  if (!ComputeDataSize(1, sizeof(GLbyte), 64, &data_size)) {
    return error::kOutOfBounds;
  }
  if (data_size > immediate_data_size) {
    return error::kOutOfBounds;
  }
  
  // ** mailbox is a pointer into our shared memory buffer **
  const GLbyte* mailbox =
      GetImmediateDataAs<const GLbyte*>(c, data_size, immediate_data_size);
  if (!validators_->texture_bind_target.IsValid(target)) {
    LOCAL_SET_GL_ERROR_INVALID_ENUM("glProduceTextureDirectCHROMIUM", target,
                                    "target");
    return error::kNoError;
  }
  if (mailbox == NULL) {
    return error::kOutOfBounds;
  }
  DoProduceTextureDirectCHROMIUM(texture, target, mailbox);
  return error::kNoError;
}

void GLES2DecoderImpl::DoProduceTextureDirectCHROMIUM(GLuint client_id,
    GLenum target, const GLbyte* data) {
  TRACE_EVENT2("gpu", "GLES2DecoderImpl::DoProduceTextureDirectCHROMIUM",
      "context", logger_.GetLogPrefix(),
      "mailbox[0]", static_cast<unsigned char>(data[0]));

  ProduceTextureRef("glProduceTextureDirectCHROMIUM", GetTexture(client_id),
      target, data);
}

void GLES2DecoderImpl::ProduceTextureRef(const char* func_name,
                                         TextureRef* texture_ref,
                                         GLenum target,
                                         const GLbyte* data) {
  // ** mailbox is still a pointer to shared memory **
  const Mailbox& mailbox = *reinterpret_cast<const Mailbox*>(data);
  DLOG_IF(ERROR, !mailbox.Verify()) << func_name << " was passed a "
                                       "mailbox that was not generated by "
                                       "GenMailboxCHROMIUM.";

  if (!texture_ref) {
    LOCAL_SET_GL_ERROR(
        GL_INVALID_OPERATION, func_name, "unknown texture for target");
    return;
  }

  Texture* produced = texture_manager()->Produce(texture_ref);
  if (!produced) {
    LOCAL_SET_GL_ERROR(
        GL_INVALID_OPERATION, func_name, "invalid texture");
    return;
  }

  if (produced->target() != target) {
    LOCAL_SET_GL_ERROR(
        GL_INVALID_OPERATION, func_name, "invalid target");
    return;
  }

  group_->mailbox_manager()->ProduceTexture(mailbox, produced);
}

void MailboxManagerImpl::ProduceTexture(const Mailbox& mailbox,
                                        Texture* texture) {
  MailboxToTextureMap::iterator it = mailbox_to_textures_.find(mailbox);
  if (it != mailbox_to_textures_.end()) {
    if (it->second->first == texture)
      return;
    TextureToMailboxMap::iterator texture_it = it->second;
    mailbox_to_textures_.erase(it);
    textures_to_mailboxes_.erase(texture_it);
  }
  InsertTexture(mailbox, texture);
}

void MailboxManagerImpl::InsertTexture(const Mailbox& mailbox,
                                       Texture* texture) {
  texture->SetMailboxManager(this);
  TextureToMailboxMap::iterator texture_it =
      textures_to_mailboxes_.insert(std::make_pair(texture, mailbox));
  // ** modify mailbox at this point **
  mailbox_to_textures_.insert(std::make_pair(mailbox, texture_it));
  DCHECK_EQ(mailbox_to_textures_.size(), textures_to_mailboxes_.size());
}

We can leverage this for memory corruption in the GPU process by a slightly roundabout route; since each entry in the mailbox_to_textures_ has an iterator to an entry in textures_to_mailboxes_, and it is an expected invariant that both of these entries have the same mailbox name. By starting to create a mailbox named aaaa...aaaa and waiting until the first insert is complete before changing the mailbox name to AAAA...aaaa, then adding a second entry with the same texture reference but with the actual name aaaa...aaaa we get the following situation (example trace through the code from ProduceTexture with InsertTexture call inlined):

// first entry - mailbox.name = 'aaaa...aaaa', texture = t1
MailboxToTextureMap::iterator it = mailbox_to_textures_.find(mailbox);
// if body not executed
if (it != mailbox_to_textures_.end()) {} 
texture->SetMailboxManager(this);
TextureToMailboxMap::iterator texture_it = 
  textures_to_mailboxes_.insert(std::make_pair(texture, mailbox));
// modify      - mailbox.name = 'AAAA...aaaa'
mailbox_to_textures_.insert(std::make_pair(mailbox, texture_it));
DCHECK_EQ(mailbox_to_textures_.size(), textures_to_mailboxes_.size());

// mailbox_to_textures_                            textures_to_mailboxes_
// AAAA...aaaa, it ------------------------------> aaaa...aaaa, t1

// second entry - mailbox.name = 'aaaa...aaaa', texture = t1
MailboxToTextureMap::iterator it = mailbox_to_textures_.find(mailbox);
// if body not executed (since 'AAAA...aaaa' != 'aaaa...aaaa')
if (it != mailbox_to_textures_.end()) {}
texture->SetMailboxManager(