Commit bdb8846a authored by Mikel Astiz's avatar Mikel Astiz Committed by Commit Bot

USS: Always commit valid session specifics

A recent bug surfaced the fact that old clients (prior to USS) don't
handle well the case where invalid tab specifics are received over the
sync protocol (namely, with an invalid tab ID).

In the particular case of orphaned foreign tabs, i.e. tabs that have
been overriden -same tab ID- by a different sync entity -different tab
node ID-, the situation is problematic. AFAIK, these entities would only
be submitted by a USS client when custom passphrase is enabled, leading
to crashes in all devices a user is syncing with.

Fortunately, the USS implementation is behind a feature toggle and has
never been enabled or experimented with.

Bug: 840876
Change-Id: I20c9272b735f0515f4bf8c0e9fd15ca31c2113a0
Reviewed-on: https://chromium-review.googlesource.com/1051234
Commit-Queue: Mikel Astiz <mastiz@chromium.org>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Cr-Commit-Position: refs/heads/master@{#557168}
parent 759fa952
...@@ -573,11 +573,9 @@ std::unique_ptr<syncer::DataBatch> SessionStore::GetSessionDataForKeys( ...@@ -573,11 +573,9 @@ std::unique_ptr<syncer::DataBatch> SessionStore::GetSessionDataForKeys(
base::BindRepeating( base::BindRepeating(
[](syncer::MutableDataBatch* batch, const std::string& session_name, [](syncer::MutableDataBatch* batch, const std::string& session_name,
sync_pb::SessionSpecifics* specifics) { sync_pb::SessionSpecifics* specifics) {
DCHECK(AreValidSpecifics(*specifics));
// Local variable used to avoid assuming argument evaluation order. // Local variable used to avoid assuming argument evaluation order.
// We also avoid GetStorageKey() because specifics might not be const std::string storage_key = GetStorageKey(*specifics);
// valid according to AreValidSpecifics().
const std::string storage_key = EncodeStorageKey(
specifics->session_tag(), specifics->tab_node_id());
batch->Put(storage_key, MoveToEntityData(session_name, specifics)); batch->Put(storage_key, MoveToEntityData(session_name, specifics));
}, },
batch.get())); batch.get()));
...@@ -591,11 +589,9 @@ std::unique_ptr<syncer::DataBatch> SessionStore::GetAllSessionData() const { ...@@ -591,11 +589,9 @@ std::unique_ptr<syncer::DataBatch> SessionStore::GetAllSessionData() const {
base::BindRepeating( base::BindRepeating(
[](syncer::MutableDataBatch* batch, const std::string& session_name, [](syncer::MutableDataBatch* batch, const std::string& session_name,
sync_pb::SessionSpecifics* specifics) { sync_pb::SessionSpecifics* specifics) {
DCHECK(AreValidSpecifics(*specifics));
// Local variable used to avoid assuming argument evaluation order. // Local variable used to avoid assuming argument evaluation order.
// We also avoid GetStorageKey() because specifics might not be const std::string storage_key = GetStorageKey(*specifics);
// valid according to AreValidSpecifics().
const std::string storage_key = EncodeStorageKey(
specifics->session_tag(), specifics->tab_node_id());
batch->Put(storage_key, MoveToEntityData(session_name, specifics)); batch->Put(storage_key, MoveToEntityData(session_name, specifics));
}, },
batch.get())); batch.get()));
......
...@@ -575,11 +575,11 @@ TEST_F(SessionStoreTest, ShouldReturnForeignUnmappedTabs) { ...@@ -575,11 +575,11 @@ TEST_F(SessionStoreTest, ShouldReturnForeignUnmappedTabs) {
/*urls=*/_))))); /*urls=*/_)))));
} }
TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) { TEST_F(SessionStoreTest, ShouldIgnoreForeignOrphanTabs) {
const std::string kForeignSessionTag = "SomeForeignTag"; const std::string kForeignSessionTag = "SomeForeignTag";
const int kWindowId = 5; const int kWindowId = 5;
const int kTabId = 7; const int kTabId = 7;
// Both tab nodes point to the same tab ID. // Both tab nodes point to the same tab ID, so the second one should prevail.
const int kTabNodeId1 = 2; const int kTabNodeId1 = 2;
const int kTabNodeId2 = 3; const int kTabNodeId2 = 3;
...@@ -587,8 +587,6 @@ TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) { ...@@ -587,8 +587,6 @@ TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) {
SessionStore::GetHeaderStorageKey(kLocalSessionTag); SessionStore::GetHeaderStorageKey(kLocalSessionTag);
const std::string foreign_header_storage_key = const std::string foreign_header_storage_key =
SessionStore::GetHeaderStorageKey(kForeignSessionTag); SessionStore::GetHeaderStorageKey(kForeignSessionTag);
const std::string foreign_tab_storage_key1 =
SessionStore::GetTabStorageKey(kForeignSessionTag, kTabNodeId1);
const std::string foreign_tab_storage_key2 = const std::string foreign_tab_storage_key2 =
SessionStore::GetTabStorageKey(kForeignSessionTag, kTabNodeId2); SessionStore::GetTabStorageKey(kForeignSessionTag, kTabNodeId2);
...@@ -610,6 +608,7 @@ TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) { ...@@ -610,6 +608,7 @@ TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) {
tab2.mutable_tab()->set_tab_id(kTabId); tab2.mutable_tab()->set_tab_id(kTabId);
ASSERT_TRUE(SessionStore::AreValidSpecifics(tab2)); ASSERT_TRUE(SessionStore::AreValidSpecifics(tab2));
// Store the two foreign tabs, in order.
std::unique_ptr<SessionStore::WriteBatch> batch = std::unique_ptr<SessionStore::WriteBatch> batch =
session_store()->CreateWriteBatch(/*error_handler=*/base::DoNothing()); session_store()->CreateWriteBatch(/*error_handler=*/base::DoNothing());
ASSERT_THAT(batch, NotNull()); ASSERT_THAT(batch, NotNull());
...@@ -617,6 +616,8 @@ TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) { ...@@ -617,6 +616,8 @@ TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) {
batch->PutAndUpdateTracker(tab2, base::Time::Now()); batch->PutAndUpdateTracker(tab2, base::Time::Now());
SessionStore::WriteBatch::Commit(std::move(batch)); SessionStore::WriteBatch::Commit(std::move(batch));
// The first foreign tab should have been overwritten by the second one,
// because they shared a tab ID.
EXPECT_THAT(BatchToEntityDataMap(session_store()->GetAllSessionData()), EXPECT_THAT(BatchToEntityDataMap(session_store()->GetAllSessionData()),
UnorderedElementsAre( UnorderedElementsAre(
Pair(local_header_storage_key, _), Pair(local_header_storage_key, _),
...@@ -624,11 +625,6 @@ TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) { ...@@ -624,11 +625,6 @@ TEST_F(SessionStoreTest, ShouldReturnForeignOrphanTabs) {
EntityDataHasSpecifics(MatchesHeader(kForeignSessionTag, EntityDataHasSpecifics(MatchesHeader(kForeignSessionTag,
/*window_ids=*/{}, /*window_ids=*/{},
/*tab_ids=*/{}))), /*tab_ids=*/{}))),
Pair(foreign_tab_storage_key1,
EntityDataHasSpecifics(
MatchesTab(kForeignSessionTag, /*window_id=*/0,
/*tab_id=*/-1, kTabNodeId1,
/*urls=*/_))),
Pair(foreign_tab_storage_key2, Pair(foreign_tab_storage_key2,
EntityDataHasSpecifics(MatchesTab( EntityDataHasSpecifics(MatchesTab(
kForeignSessionTag, kWindowId, kTabId, kTabNodeId2, kForeignSessionTag, kWindowId, kTabId, kTabNodeId2,
......
...@@ -776,6 +776,16 @@ void SerializePartialTrackerToSpecifics( ...@@ -776,6 +776,16 @@ void SerializePartialTrackerToSpecifics(
// Tab entities. // Tab entities.
const SessionID tab_id = const SessionID tab_id =
tracker.LookupTabIdFromTabNodeId(session_tag, tab_node_id); tracker.LookupTabIdFromTabNodeId(session_tag, tab_node_id);
if (!tab_id.is_valid()) {
// This can be the case for tabs that were unmapped because we received
// a new foreign tab with the same tab ID (the last one wins), so we
// don't remember the tab ID for the original |tab_node_id|. Instead of
// returning partially populated SessionSpecifics (without tab ID), we
// simply drop them, because older clients don't handle well such
// invalid specifics.
continue;
}
const sessions::SessionTab* tab = const sessions::SessionTab* tab =
tracker.LookupSessionTab(session_tag, tab_id); tracker.LookupSessionTab(session_tag, tab_id);
if (tab) { if (tab) {
...@@ -788,12 +798,11 @@ void SerializePartialTrackerToSpecifics( ...@@ -788,12 +798,11 @@ void SerializePartialTrackerToSpecifics(
continue; continue;
} }
// Create entities for unmapped tabs nodes (possibly not even associated // Create entities for unmapped tabs nodes.
// to a tab ID).
sync_pb::SessionSpecifics tab_pb; sync_pb::SessionSpecifics tab_pb;
tab_pb.set_tab_node_id(tab_node_id); tab_pb.set_tab_node_id(tab_node_id);
tab_pb.set_session_tag(session_tag); tab_pb.set_session_tag(session_tag);
tab_pb.mutable_tab()->set_tab_id(tab_id.id()); // Might be invalid. tab_pb.mutable_tab()->set_tab_id(tab_id.id());
output_cb.Run(session->session_name, &tab_pb); output_cb.Run(session->session_name, &tab_pb);
} }
} }
......
...@@ -1018,10 +1018,6 @@ TEST_F(SyncedSessionTrackerTest, SerializeTrackerToSpecifics) { ...@@ -1018,10 +1018,6 @@ TEST_F(SyncedSessionTrackerTest, SerializeTrackerToSpecifics) {
callback, callback,
Run(kSessionName, Pointee(MatchesTab(kTag, Ne(kWindow1.id()), kTab3.id(), Run(kSessionName, Pointee(MatchesTab(kTag, Ne(kWindow1.id()), kTab3.id(),
kTabNode3, /*urls=*/_)))); kTabNode3, /*urls=*/_))));
EXPECT_CALL(
callback,
Run(kSessionName, Pointee(MatchesTab(kTag, /*window_id=*/0, /*tab_id=*/-1,
kTabNode4, /*urls=*/_))));
SerializeTrackerToSpecifics(*GetTracker(), callback.Get()); SerializeTrackerToSpecifics(*GetTracker(), callback.Get());
EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&callback)); EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&callback));
......
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