chromium/ppapi/proxy/nacl_message_scanner_unittest.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include "ipc/ipc_message.h"
#include "ppapi/proxy/nacl_message_scanner.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/proxy/ppapi_proxy_test.h"
#include "ppapi/proxy/serialized_handle.h"
#include "ppapi/shared_impl/host_resource.h"

namespace ppapi {
namespace proxy {

namespace {
const PP_Resource kInvalidResource = 0;
const PP_Resource kFileSystem = 1;
const PP_Resource kFileIO = 2;
const int64_t kQuotaReservationAmount = 100;
}

class NaClMessageScannerTest : public PluginProxyTest {
 public:
  NaClMessageScannerTest() {}

  NaClMessageScanner::FileSystem* FindFileSystem(
      const NaClMessageScanner& scanner,
      PP_Resource file_system) {
    NaClMessageScanner::FileSystemMap::const_iterator it =
        scanner.file_systems_.find(file_system);
    return (it != scanner.file_systems_.end()) ? it->second : NULL;
  }

  NaClMessageScanner::FileIO* FindFileIO(
      const NaClMessageScanner& scanner,
      PP_Resource file_io) {
    NaClMessageScanner::FileIOMap::const_iterator it =
        scanner.files_.find(file_io);
    return (it != scanner.files_.end()) ? it->second : NULL;
  }

  void OpenQuotaFile(NaClMessageScanner* scanner,
                     PP_Resource file_io,
                     PP_Resource file_system) {
    std::vector<SerializedHandle> unused_handles;
    ResourceMessageReplyParams fio_reply_params(file_io, 0);
    std::unique_ptr<IPC::Message> new_msg_ptr;
    scanner->ScanMessage(
        PpapiPluginMsg_ResourceReply(
            fio_reply_params,
            PpapiPluginMsg_FileIO_OpenReply(file_system, 0)),
        PpapiPluginMsg_ResourceReply::ID,
        &unused_handles,
        &new_msg_ptr);
    EXPECT_FALSE(new_msg_ptr);
  }
};

TEST_F(NaClMessageScannerTest, FileOpenClose) {
  NaClMessageScanner test;
  std::vector<SerializedHandle> unused_handles;
  ResourceMessageCallParams fio_call_params(kFileIO, 0);
  ResourceMessageCallParams fs_call_params(kFileSystem, 0);
  ResourceMessageReplyParams fio_reply_params(kFileIO, 0);
  ResourceMessageReplyParams fs_reply_params(kFileSystem, 0);
  std::unique_ptr<IPC::Message> new_msg_ptr;

  EXPECT_EQ(NULL, FindFileSystem(test, kFileSystem));
  EXPECT_EQ(NULL, FindFileIO(test, kFileIO));

  // Open a file, not in a quota file system.
  test.ScanMessage(
      PpapiPluginMsg_ResourceReply(
          fio_reply_params,
          PpapiPluginMsg_FileIO_OpenReply(kInvalidResource, 0)),
      PpapiPluginMsg_ResourceReply::ID,
      &unused_handles,
      &new_msg_ptr);
  EXPECT_FALSE(new_msg_ptr);
  EXPECT_FALSE(FindFileSystem(test, kFileSystem));
  EXPECT_FALSE(FindFileIO(test, kFileIO));

  // Open a file in a quota file system; info objects for it and its file system
  // should be created.
  OpenQuotaFile(&test, kFileIO, kFileSystem);
  NaClMessageScanner::FileSystem* fs = FindFileSystem(test, kFileSystem);
  NaClMessageScanner::FileIO* fio = FindFileIO(test, kFileIO);
  EXPECT_TRUE(fs);
  EXPECT_EQ(0, fs->reserved_quota());
  EXPECT_TRUE(fio);
  EXPECT_EQ(0, fio->max_written_offset());

  const int64_t kNewFileSize = 10;
  fio->SetMaxWrittenOffset(kNewFileSize);

  // We should not be able to under-report max_written_offset when closing.
  test.ScanUntrustedMessage(
      PpapiHostMsg_ResourceCall(
          fio_call_params,
          PpapiHostMsg_FileIO_Close(FileGrowth(0, 0))),
      &new_msg_ptr);
  EXPECT_TRUE(new_msg_ptr);
  ResourceMessageCallParams call_params;
  IPC::Message nested_msg;
  FileGrowth file_growth;
  EXPECT_TRUE(UnpackMessage<PpapiHostMsg_ResourceCall>(
                  *new_msg_ptr, &call_params, &nested_msg) &&
              UnpackMessage<PpapiHostMsg_FileIO_Close>(
                  nested_msg, &file_growth));
  new_msg_ptr.reset();
  EXPECT_EQ(kNewFileSize, file_growth.max_written_offset);
  EXPECT_FALSE(FindFileIO(test, kFileIO));

  // Reopen the file.
  OpenQuotaFile(&test, kFileIO, kFileSystem);
  fio = FindFileIO(test, kFileIO);
  fio->SetMaxWrittenOffset(kNewFileSize);

  // Close with correct max_written_offset.
  test.ScanUntrustedMessage(
      PpapiHostMsg_ResourceCall(
          fio_call_params,
          PpapiHostMsg_FileIO_Close(FileGrowth(kNewFileSize, 0))),
      &new_msg_ptr);
  EXPECT_FALSE(new_msg_ptr);
  EXPECT_FALSE(FindFileIO(test, kFileIO));

  // Destroy file system.
  test.ScanUntrustedMessage(
      PpapiHostMsg_ResourceCall(
          fs_call_params,
          PpapiHostMsg_ResourceDestroyed(kFileSystem)),
      &new_msg_ptr);
  EXPECT_FALSE(FindFileSystem(test, kFileSystem));
}

TEST_F(NaClMessageScannerTest, QuotaAuditing) {
  NaClMessageScanner test;
  std::vector<SerializedHandle> unused_handles;
  ResourceMessageCallParams fio_call_params(kFileIO, 0);
  ResourceMessageCallParams fs_call_params(kFileSystem, 0);
  ResourceMessageReplyParams fio_reply_params(kFileIO, 0);
  ResourceMessageReplyParams fs_reply_params(kFileSystem, 0);
  std::unique_ptr<IPC::Message> new_msg_ptr;

  OpenQuotaFile(&test, kFileIO, kFileSystem);
  NaClMessageScanner::FileSystem* fs = FindFileSystem(test, kFileSystem);
  NaClMessageScanner::FileIO* fio = FindFileIO(test, kFileIO);
  EXPECT_TRUE(fs);
  EXPECT_EQ(0, fs->reserved_quota());
  EXPECT_TRUE(fio);
  EXPECT_EQ(0, fio->max_written_offset());

  // Without reserving quota, we should not be able to grow the file.
  EXPECT_FALSE(fio->Grow(1));
  EXPECT_EQ(0, fs->reserved_quota());
  EXPECT_EQ(0, fio->max_written_offset());

  // Receive reserved quota, and updated file sizes.
  const int64_t kNewFileSize = 10;
  FileSizeMap file_sizes;
  file_sizes[kFileIO] = kNewFileSize;
  test.ScanMessage(
      PpapiPluginMsg_ResourceReply(
          fs_reply_params,
          PpapiPluginMsg_FileSystem_ReserveQuotaReply(
              kQuotaReservationAmount,
              file_sizes)),
      PpapiPluginMsg_ResourceReply::ID,
      &unused_handles,
      &new_msg_ptr);
  EXPECT_FALSE(new_msg_ptr);
  EXPECT_EQ(kQuotaReservationAmount, fs->reserved_quota());
  EXPECT_EQ(kNewFileSize, fio->max_written_offset());

  // We should be able to grow the file within quota.
  EXPECT_TRUE(fio->Grow(1));
  EXPECT_EQ(kQuotaReservationAmount - 1, fs->reserved_quota());
  EXPECT_EQ(kNewFileSize + 1, fio->max_written_offset());

  // We should not be able to grow the file over quota.
  EXPECT_FALSE(fio->Grow(kQuotaReservationAmount));
  EXPECT_EQ(kQuotaReservationAmount - 1, fs->reserved_quota());
  EXPECT_EQ(kNewFileSize + 1, fio->max_written_offset());

  // Plugin should not under-report max written offsets when reserving quota.
  file_sizes[kFileIO] = 0;  // should be kNewFileSize + 1.
  test.ScanUntrustedMessage(
      PpapiHostMsg_ResourceCall(
          fio_call_params,
          PpapiHostMsg_FileSystem_ReserveQuota(
              kQuotaReservationAmount,
              FileSizeMapToFileGrowthMapForTesting(file_sizes))),
      &new_msg_ptr);
  EXPECT_TRUE(new_msg_ptr);
  ResourceMessageCallParams call_params;
  IPC::Message nested_msg;
  int64_t amount = 0;
  FileGrowthMap new_file_growths;
  EXPECT_TRUE(UnpackMessage<PpapiHostMsg_ResourceCall>(
                  *new_msg_ptr, &call_params, &nested_msg) &&
              UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>(
                  nested_msg, &amount, &new_file_growths));
  new_msg_ptr.reset();
  EXPECT_EQ(kQuotaReservationAmount, amount);
  EXPECT_EQ(kNewFileSize + 1, new_file_growths[kFileIO].max_written_offset);
}

TEST_F(NaClMessageScannerTest, SetLength) {
  NaClMessageScanner test;
  std::vector<SerializedHandle> unused_handles;
  ResourceMessageCallParams fio_call_params(kFileIO, 0);
  ResourceMessageCallParams fs_call_params(kFileSystem, 0);
  ResourceMessageReplyParams fio_reply_params(kFileIO, 0);
  ResourceMessageReplyParams fs_reply_params(kFileSystem, 0);
  std::unique_ptr<IPC::Message> new_msg_ptr;

  OpenQuotaFile(&test, kFileIO, kFileSystem);
  NaClMessageScanner::FileSystem* fs = FindFileSystem(test, kFileSystem);
  NaClMessageScanner::FileIO* fio = FindFileIO(test, kFileIO);

  // Receive reserved quota, and updated file sizes.
  const int64_t kNewFileSize = 10;
  FileSizeMap file_sizes;
  file_sizes[kFileIO] = 0;
  test.ScanMessage(
      PpapiPluginMsg_ResourceReply(
          fs_reply_params,
          PpapiPluginMsg_FileSystem_ReserveQuotaReply(
              kQuotaReservationAmount,
              file_sizes)),
      PpapiPluginMsg_ResourceReply::ID,
      &unused_handles,
      &new_msg_ptr);

  // We should be able to SetLength within quota.
  test.ScanUntrustedMessage(
      PpapiHostMsg_ResourceCall(
          fio_call_params,
          PpapiHostMsg_FileIO_SetLength(kNewFileSize)),
      &new_msg_ptr);
  EXPECT_FALSE(new_msg_ptr);
  EXPECT_EQ(kQuotaReservationAmount - kNewFileSize, fs->reserved_quota());
  EXPECT_EQ(kNewFileSize, fio->max_written_offset());

  // We shouldn't be able to SetLength beyond quota. The message should be
  // rewritten to fail with length == -1.
  test.ScanUntrustedMessage(
      PpapiHostMsg_ResourceCall(
          fio_call_params,
          PpapiHostMsg_FileIO_SetLength(kQuotaReservationAmount + 1)),
      &new_msg_ptr);
  EXPECT_TRUE(new_msg_ptr);
  ResourceMessageCallParams call_params;
  IPC::Message nested_msg;
  int64_t length = 0;
  EXPECT_TRUE(UnpackMessage<PpapiHostMsg_ResourceCall>(
                  *new_msg_ptr, &call_params, &nested_msg) &&
              UnpackMessage<PpapiHostMsg_FileIO_SetLength>(
                  nested_msg, &length));
  new_msg_ptr.reset();
  EXPECT_EQ(-1, length);
  EXPECT_EQ(kQuotaReservationAmount - kNewFileSize, fs->reserved_quota());
  EXPECT_EQ(kNewFileSize, fio->max_written_offset());
}

}  // namespace proxy
}  // namespace ppapi