Commit message: None
CVE: CVE-2017-5101
CWE: CWE-20
Vulnerability description: Inappropriate implementation in Omnibox in Google Chrome prior to 60.0.3112.78 for Linux, Windows, and Mac allowed a remote attacker to spoof the contents of the Omnibox via a crafted HTML page.

Project: Chrome

bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::CreateBuffers(
    gfx::Size coded_size,
    const VideoFrameLayout& input_layout,
    size_t output_buffer_size) {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());

  if (!SetOutputBufferFormat(coded_size, output_buffer_size)) {
    return false;
  }

  if (!SetInputBufferFormat(coded_size, input_layout)) {
    return false;
  }

  if (!RequestInputBuffers()) {
    return false;
  }

  if (!RequestOutputBuffers()) {
    return false;
  }

  return true;
}

Project: Chrome

void V4L2JpegEncodeAccelerator::EncodedInstance::DestroyInputBuffers() {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
  free_input_buffers_.clear();

  if (input_buffer_map_.empty())
    return;

  if (input_streamon_) {
    __u32 type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
    IOCTL_OR_ERROR_RETURN(VIDIOC_STREAMOFF, &type);
    input_streamon_ = false;
  }

  for (const auto& input_record : input_buffer_map_) {
    for (size_t i = 0; i < input_buffer_num_planes_; ++i) {
      device_->Munmap(input_record.address[i], input_record.length[i]);
    }
  }

  struct v4l2_requestbuffers reqbufs;
  memset(&reqbufs, 0, sizeof(reqbufs));
  reqbufs.count = 0;
  reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
  reqbufs.memory = V4L2_MEMORY_MMAP;
  IOCTL_OR_LOG_ERROR(VIDIOC_REQBUFS, &reqbufs);

  input_buffer_map_.clear();
  input_buffer_num_planes_ = 0;
}

Project: Chrome

bool V4L2JpegEncodeAccelerator::EncodedInstance::EnqueueInputRecord() {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
  DCHECK(!input_job_queue_.empty());
  DCHECK(!free_input_buffers_.empty());

  std::unique_ptr<JobRecord> job_record = std::move(input_job_queue_.front());
  input_job_queue_.pop();
  const int index = free_input_buffers_.back();
  I420BufferRecord& input_record = input_buffer_map_[index];
  DCHECK(!input_record.at_device);

  uint8_t* src_y = job_record->input_frame->data(VideoFrame::kYPlane);
  uint8_t* src_u = job_record->input_frame->data(VideoFrame::kUPlane);
  uint8_t* src_v = job_record->input_frame->data(VideoFrame::kVPlane);
  size_t src_y_stride = job_record->input_frame->stride(VideoFrame::kYPlane);
  size_t src_u_stride = job_record->input_frame->stride(VideoFrame::kUPlane);
  size_t src_v_stride = job_record->input_frame->stride(VideoFrame::kVPlane);

  size_t input_coded_width = job_record->input_frame->coded_size().width();
  size_t input_coded_height = job_record->input_frame->coded_size().height();

  size_t dst_y_stride = bytes_per_line_[0];
  size_t dst_u_stride;
  size_t dst_v_stride;

  uint8_t* dst_y = static_cast<uint8_t*>(input_record.address[0]);
  uint8_t* dst_u;
  uint8_t* dst_v;

  if (input_buffer_num_planes_ == 1) {
    dst_u_stride = dst_y_stride / 2;
    dst_v_stride = dst_y_stride / 2;
    dst_u = dst_y + dst_y_stride * input_buffer_height_;
    dst_v = dst_u + dst_u_stride * input_buffer_height_ / 2;
  } else {
    DCHECK(input_buffer_num_planes_ == 3);

    dst_u_stride = bytes_per_line_[1];
    dst_v_stride = bytes_per_line_[2];

    dst_u = static_cast<uint8_t*>(input_record.address[1]);
    dst_v = static_cast<uint8_t*>(input_record.address[2]);
  }

  if (libyuv::I420Copy(src_y, src_y_stride, src_u, src_u_stride, src_v,
                       src_v_stride, dst_y, dst_y_stride, dst_u, dst_u_stride,
                       dst_v, dst_v_stride, input_coded_width,
                       input_coded_height)) {
    VLOGF(1) << "I420Copy failed";
    return false;
  }

  struct v4l2_buffer qbuf;
  struct v4l2_plane planes[kMaxI420Plane];
  memset(&qbuf, 0, sizeof(qbuf));
  memset(planes, 0, sizeof(planes));
  qbuf.index = index;
  qbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
  qbuf.memory = V4L2_MEMORY_MMAP;
  qbuf.length = base::size(planes);
  for (size_t i = 0; i < input_buffer_num_planes_; i++) {
    planes[i].bytesused = 0;
  }
  qbuf.m.planes = planes;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
  input_record.at_device = true;
  running_job_queue_.push(std::move(job_record));
  free_input_buffers_.pop_back();

  DVLOGF(3) << "enqueued frame id=" << job_record->task_id << " to device.";
  return true;
}

Project: Chrome

bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::EnqueueOutputRecord() {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
  DCHECK(!free_output_buffers_.empty());

  const int index = free_output_buffers_.back();
  struct v4l2_buffer qbuf;
  struct v4l2_plane planes[kMaxJpegPlane];
  memset(&qbuf, 0, sizeof(qbuf));
  memset(planes, 0, sizeof(planes));
  qbuf.index = index;
  qbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
  qbuf.memory = V4L2_MEMORY_DMABUF;
  qbuf.length = base::size(planes);
  qbuf.m.planes = planes;

  auto& job_record = running_job_queue_.back();
  for (size_t i = 0; i < qbuf.length; i++) {
    planes[i].m.fd = job_record->output_frame->DmabufFds()[i].get();
  }
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QBUF, &qbuf);
  free_output_buffers_.pop_back();
  return true;
}

Project: Chrome

size_t V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::FinalizeJpegImage(
    scoped_refptr<VideoFrame> output_frame,
    size_t buffer_size,
    std::unique_ptr<UnalignedSharedMemory> exif_shm) {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
  size_t idx = 0;

  auto output_gmb_handle = CreateGpuMemoryBufferHandle(output_frame.get());
  if (output_gmb_handle.is_null()) {
    VLOGF(1) << "Failed to create GpuMemoryBufferHandle";
    return 0;
  }
  auto output_gmb_buffer =
      gpu_memory_buffer_support_->CreateGpuMemoryBufferImplFromHandle(
          std::move(output_gmb_handle), output_frame->coded_size(),
          gfx::BufferFormat::R_8, gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE,
          base::DoNothing());

  bool isMapped = output_gmb_buffer->Map();
  if (!isMapped) {
    VLOGF(1) << "Failed to map gmb buffer";
    return 0;
  }
  uint8_t* dst_ptr = static_cast<uint8_t*>(output_gmb_buffer->memory(0));

  static const uint8_t kJpegStart[] = {0xFF, JPEG_SOI};

  if (exif_shm) {
    uint8_t* exif_buffer = static_cast<uint8_t*>(exif_shm->memory());
    size_t exif_buffer_size = exif_shm->size();
    uint16_t exif_segment_size = static_cast<uint16_t>(exif_buffer_size + 2);
    const uint8_t kAppSegment[] = {
        0xFF, JPEG_APP1, static_cast<uint8_t>(exif_segment_size / 256),
        static_cast<uint8_t>(exif_segment_size % 256)};

    size_t compressed_data_offset = sizeof(kJpegStart) + sizeof(kAppSegment) +
                                    exif_buffer_size + jpeg_markers_.size();
    memmove(dst_ptr + compressed_data_offset, dst_ptr, buffer_size);

    memcpy(dst_ptr, kJpegStart, sizeof(kJpegStart));
    idx += sizeof(kJpegStart);
    memcpy(dst_ptr + idx, kAppSegment, sizeof(kAppSegment));
    idx += sizeof(kAppSegment);
    memcpy(dst_ptr + idx, exif_buffer, exif_buffer_size);
    idx += exif_buffer_size;
  } else {
    static const uint8_t kAppSegment[] = {
        0xFF, JPEG_APP0, 0x00,
        0x10,  // Segment length:16 (2-byte).
        0x4A,  // J
        0x46,  // F
        0x49,  // I
        0x46,  // F
        0x00,  // 0
        0x01,  // Major version.
        0x01,  // Minor version.
        0x01,  // Density units 0:no units, 1:pixels per inch,
        0x00,
        0x48,  // X density (2-byte).
        0x00,
        0x48,  // Y density (2-byte).
        0x00,  // Thumbnail width.
        0x00   // Thumbnail height.
    };

    size_t compressed_data_offset =
        sizeof(kJpegStart) + sizeof(kAppSegment) + jpeg_markers_.size();
    memmove(dst_ptr + compressed_data_offset, dst_ptr, buffer_size);

    memcpy(dst_ptr, kJpegStart, sizeof(kJpegStart));
    idx += sizeof(kJpegStart);

    memcpy(dst_ptr + idx, kAppSegment, sizeof(kAppSegment));
    idx += sizeof(kAppSegment);
  }

  switch (output_buffer_pixelformat_) {
    case V4L2_PIX_FMT_JPEG_RAW:
      memcpy(dst_ptr + idx, jpeg_markers_.data(), jpeg_markers_.size());
      idx += jpeg_markers_.size();
      idx += buffer_size;
      if (dst_ptr[idx - 2] != 0xFF && dst_ptr[idx - 1] != JPEG_EOI) {
        dst_ptr[idx] = 0xFF;
        dst_ptr[idx + 1] = JPEG_EOI;
        idx += 2;
      }
      break;

    default:
      NOTREACHED() << "Unsupported output pixel format";
  }

  output_gmb_buffer->Unmap();

  return idx;
}

Project: Chrome

bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::Initialize() {
  device_ = V4L2Device::Create();
  gpu_memory_buffer_support_ = std::make_unique<gpu::GpuMemoryBufferSupport>();

  if (!device_) {
    VLOGF(1) << "Failed to Create V4L2Device";
    return false;
  }

  output_buffer_pixelformat_ = V4L2_PIX_FMT_JPEG_RAW;
  if (!device_->Open(V4L2Device::Type::kJpegEncoder,
                     output_buffer_pixelformat_)) {
    VLOGF(1) << "Failed to open device";
    return false;
  }

  struct v4l2_capability caps;
  const __u32 kCapsRequired = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
  memset(&caps, 0, sizeof(caps));
  if (device_->Ioctl(VIDIOC_QUERYCAP, &caps) != 0) {
    VPLOGF(1) << "ioctl() failed: VIDIOC_QUERYCAP";
    return false;
  }
  if ((caps.capabilities & kCapsRequired) != kCapsRequired) {
    VLOGF(1) << "VIDIOC_QUERYCAP, caps check failed: 0x" << std::hex
             << caps.capabilities;
    return false;
  }

  return true;
}

Project: Chrome

void V4L2JpegEncodeAccelerator::EncodedInstance::NotifyError(int32_t task_id,
                                                             Status status) {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
  parent_->NotifyError(task_id, status);
}

Project: Chrome

void V4L2JpegEncodeAccelerator::EncodedInstance::PrepareJpegMarkers(
    gfx::Size coded_size) {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
  const int kNumDQT = 2;
  for (size_t i = 0; i < kNumDQT; ++i) {
    const uint8_t kQuantSegment[] = {
        0xFF, JPEG_DQT, 0x00,
        0x03 + kDctSize,         // Segment length:67 (2-byte).
        static_cast<uint8_t>(i)  // Precision (4-bit high) = 0,
    };
    for (size_t j = 0; j < sizeof(kQuantSegment); ++j) {
      jpeg_markers_.push_back(kQuantSegment[j]);
    }

    for (size_t j = 0; j < kDctSize; ++j) {
      jpeg_markers_.push_back(quantization_table_[i].value[j]);
    }
  }

  const int kNumOfComponents = 3;
  const uint8_t kStartOfFrame[] = {
      0xFF,
      JPEG_SOF0,  // Baseline.
      0x00,
      0x11,  // Segment length:17 (2-byte).
      8,     // Data precision.
      static_cast<uint8_t>((coded_size.height() >> 8) & 0xFF),
      static_cast<uint8_t>(coded_size.height() & 0xFF),
      static_cast<uint8_t>((coded_size.width() >> 8) & 0xFF),
      static_cast<uint8_t>(coded_size.width() & 0xFF),
      kNumOfComponents,
  };
  for (size_t i = 0; i < sizeof(kStartOfFrame); ++i) {
    jpeg_markers_.push_back(kStartOfFrame[i]);
  }
  for (size_t i = 0; i < kNumOfComponents; ++i) {
    uint8_t h_sample_factor = 1;
    uint8_t v_sample_factor = 1;
    uint8_t quant_table_number = 1;
    if (!i) {
      h_sample_factor = 2;
      v_sample_factor = 2;
      quant_table_number = 0;
    }

    jpeg_markers_.push_back(i + 1);
    jpeg_markers_.push_back((h_sample_factor << 4) | v_sample_factor);
    jpeg_markers_.push_back(quant_table_number);
  }

  static const uint8_t kDcSegment[] = {
      0xFF, JPEG_DHT, 0x00,
      0x1F,  // Segment length:31 (2-byte).
  };
  static const uint8_t kAcSegment[] = {
      0xFF, JPEG_DHT, 0x00,
      0xB5,  // Segment length:181 (2-byte).
  };

  const int kNumHuffmanTables = 2;
  for (size_t i = 0; i < kNumHuffmanTables; ++i) {
    for (size_t j = 0; j < sizeof(kDcSegment); ++j) {
      jpeg_markers_.push_back(kDcSegment[j]);
    }

    jpeg_markers_.push_back(static_cast<uint8_t>(i));

    const JpegHuffmanTable& dcTable = kDefaultDcTable[i];
    for (size_t j = 0; j < kNumDcRunSizeBits; ++j)
      jpeg_markers_.push_back(dcTable.code_length[j]);
    for (size_t j = 0; j < kNumDcCodeWordsHuffVal; ++j)
      jpeg_markers_.push_back(dcTable.code_value[j]);

    for (size_t j = 0; j < sizeof(kAcSegment); ++j) {
      jpeg_markers_.push_back(kAcSegment[j]);
    }

    jpeg_markers_.push_back(0x10 | static_cast<uint8_t>(i));

    const JpegHuffmanTable& acTable = kDefaultAcTable[i];
    for (size_t j = 0; j < kNumAcRunSizeBits; ++j)
      jpeg_markers_.push_back(acTable.code_length[j]);
    for (size_t j = 0; j < kNumAcCodeWordsHuffVal; ++j)
      jpeg_markers_.push_back(acTable.code_value[j]);
  }

  static const uint8_t kStartOfScan[] = {
      0xFF, JPEG_SOS, 0x00,
      0x0C,  // Segment Length:12 (2-byte).
      0x03   // Number of components in scan.
  };
  for (size_t i = 0; i < sizeof(kStartOfScan); ++i) {
    jpeg_markers_.push_back(kStartOfScan[i]);
  }

  for (uint8_t i = 0; i < kNumOfComponents; ++i) {
    uint8_t dc_table_number = 1;
    uint8_t ac_table_number = 1;
    if (!i) {
      dc_table_number = 0;
      ac_table_number = 0;
    }

    jpeg_markers_.push_back(i + 1);
    jpeg_markers_.push_back((dc_table_number << 4) | ac_table_number);
  }
  jpeg_markers_.push_back(0x00);  // 0 for Baseline.
  jpeg_markers_.push_back(0x3F);  // 63 for Baseline.
  jpeg_markers_.push_back(0x00);  // 0 for Baseline.
}

Project: Chrome

bool V4L2JpegEncodeAccelerator::EncodedInstance::RequestOutputBuffers() {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
  struct v4l2_requestbuffers reqbufs;
  memset(&reqbufs, 0, sizeof(reqbufs));
  reqbufs.count = kBufferCount;
  reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
  reqbufs.memory = V4L2_MEMORY_MMAP;
  IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_REQBUFS, &reqbufs);

  DCHECK(output_buffer_map_.empty());
  output_buffer_map_.resize(reqbufs.count);

  for (size_t i = 0; i < output_buffer_map_.size(); ++i) {
    free_output_buffers_.push_back(i);

    struct v4l2_buffer buffer;
    struct v4l2_plane planes[kMaxJpegPlane];
    memset(&buffer, 0, sizeof(buffer));
    memset(planes, 0, sizeof(planes));
    buffer.index = i;
    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    buffer.m.planes = planes;
    buffer.length = base::size(planes);
    buffer.memory = V4L2_MEMORY_MMAP;
    IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_QUERYBUF, &buffer);
    if (buffer.length != kMaxJpegPlane) {
      return false;
    }
    void* address =
        device_->Mmap(NULL, planes[0].length, PROT_READ | PROT_WRITE,
                      MAP_SHARED, planes[0].m.mem_offset);
    if (address == MAP_FAILED) {
      VPLOGF(1) << "mmap() failed";
      return false;
    }
    output_buffer_map_[i].address[0] = address;
    output_buffer_map_[i].length[0] = planes[0].length;
  }

  return true;
}

Project: Chrome

void V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::ServiceDevice() {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());

  if (!running_job_queue_.empty()) {
    Dequeue();
  }

  EnqueueInput();
  EnqueueOutput();

  DVLOGF(3) << "buffer counts: INPUT[" << input_job_queue_.size()
            << "] => DEVICE[" << free_input_buffers_.size() << "/"
            << "->" << free_output_buffers_.size() << "]";
}

Project: Chrome

bool V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::SetUpJpegParameters(
    int quality,
    gfx::Size coded_size) {
  DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());

  struct v4l2_ext_controls ctrls;
  struct v4l2_ext_control ctrl;

  memset(&ctrls, 0, sizeof(ctrls));
  memset(&ctrl, 0, sizeof(ctrl));

  ctrls.ctrl_class = V4L2_CTRL_CLASS_JPEG;
  ctrls.controls = &ctrl;
  ctrls.count = 1;

  switch (output_buffer_pixelformat_) {
    case V4L2_PIX_FMT_JPEG_RAW:
      FillQuantizationTable(quality, kDefaultQuantTable[0].value,
                            quantization_table_[0].value);
      FillQuantizationTable(quality, kDefaultQuantTable[1].value,
                            quantization_table_[1].value);

      ctrl.id = V4L2_CID_JPEG_LUMA_QUANTIZATION;
      ctrl.size = kDctSize;
      ctrl.ptr = quantization_table_[0].value;
      IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_EXT_CTRLS, &ctrls);

      ctrl.id = V4L2_CID_JPEG_CHROMA_QUANTIZATION;
      ctrl.size = kDctSize;
      ctrl.ptr = quantization_table_[1].value;
      IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_EXT_CTRLS, &ctrls);

      PrepareJpegMarkers(coded_size);
      break;

    default:
      NOTREACHED();
  }

  return true;
}