Skip to content

Commit

Permalink
Add a test against PL takeovers
Browse files Browse the repository at this point in the history
This (belatedly) adds a test that the attack fixed in
matrix-org/synapse#3397 is fixed.
  • Loading branch information
richvdh committed Oct 31, 2018
1 parent d8792a8 commit 4283634
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 1 deletion.
4 changes: 4 additions & 0 deletions lib/SyTest/Federation/Server.pm
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@ __PACKAGE__->mk_await_request_pair(
get_missing_events => [qw( :room_id )],
);

__PACKAGE__->mk_await_request_pair(
event_auth => [qw( :room_id :event_id )],
);

__PACKAGE__->mk_await_request_pair(
backfill => [qw( :room_id )],
);
Expand Down
191 changes: 190 additions & 1 deletion tests/50federation/36-state.pl
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ sub get_state_ids_from_server {
# /
# B (Y)
# / \ /
# | X
# | X
# \ /
# C
#
Expand Down Expand Up @@ -685,3 +685,192 @@ sub get_state_ids_from_server {
)->main::expect_m_not_found;
});
};

test "Should not be able to take over the room by pretending there is no PL event",
# this test checks the situation fixed by
# https://github.com/matrix-org/synapse/pull/3397: in short, it used to be
# possible to take over a room by pretending that there was no power-levels
# event in the room.
#
# We're going to create a DAG that looks like this:
#
# A
# |
# B
# .
# .
# C
# |
# D
# |
# E
#
# Starting with a regular room, we send a message E, whose prev_event is D.
# We expect the remote server to request the missing events, so we send D,
# whose prev_event is C.
#
# The server will then request C, and the state at C. We give it a bogus
# state, which includes a PL event X which we make up and gives us all the
# power.
#
# The end state *should* be that X is rejected and the room state is
# unaffected.

requires => [
$main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, $main::HOMESERVER_INFO[0],
# Create user and a publicly joinable room on the synapse.
local_user_and_room_fixtures(),
# Pick a user_id for our evil federation user.
federation_user_id_fixture()
],

do => sub {
my ( $outbound_client, $inbound_server, $info, $creator, $room_id, $evil_user_id ) = @_;
my $first_home_server = $info->server_name;
my $local_server_name = $outbound_client->server_name;

# Join our evil user to the room.
$outbound_client->join_room(
server_name => $first_home_server,
room_id => $room_id,
user_id => $evil_user_id,
)->then( sub {
my ( $room ) = @_;

# Fetch the create event and our evil user's join event.
my $create = $room->get_current_state_event("m.room.create");
my $join = $room->get_current_state_event("m.room.member", $evil_user_id);

my $evil_power_level_event_x = $room->create_event(
event_id_suffix => "pl_x",

# Pick a depth of 0 so that this event apears before the
# real m.room.power_levels event when doing state resolution.
# https://github.com/matrix-org/synapse/blob/v0.33.7/synapse/state/v1.py#L256
# https://github.com/matrix-org/synapse/blob/v0.33.7/synapse/state/v1.py#L301
depth => 0,
# Refer to an event that doesn't exist so that synapse has to rely
# on the auth_events we supply to auth this event.
prev_events => [["\$this:event.does.not.exist", {}]],
type => "m.room.power_levels",
state_key => "",
sender => $evil_user_id,
content => {
users => {
# Give ourselves all the power in the room.
$evil_user_id => 100,
# Set the creator's power level to 0 so that the real
# m.room.power_levels event fails auth checks when compared
# to our power_level event.
$creator->user_id => 0,
},
},
);

my $evil_message_event_c = $room->create_event(
event_id_suffix => 'msg_c',

# Pick a depth high enough to avoid the min_depth check.
# https://github.com/matrix-org/synapse/blob/v0.33.7/synapse/handlers/federation.py#L245
depth => 10,
# Reference an event that doesn't exist so that we can pick the
# state at this event.
prev_events => [["\$this:event.does.not.exist", {}]],
sender => $evil_user_id,
type => "m.room.message",
content => {
# Suitably evil laughter.
body => "hehehe...",
},
);

my $msg_d = $room->create_event(
event_id_suffix => 'msg_d',

depth => 11,
prev_events => [[$evil_message_event_c->{event_id}, {}]],
sender => $evil_user_id,
type => "m.room.message",
content => {
body => "totes legit",
},
);

my $msg_e = $room->create_event(
event_id_suffix => 'msg_e',
depth => 11,
prev_events => [[$msg_d->{event_id}, {}]],
sender => $evil_user_id,
type => "m.room.message",
content => {
body => "nothing to see",
},
);

Future->needs_all(
# Send the event using the federation send API.
$outbound_client->send_event(
event => $msg_e,
destination => $first_home_server,
),

# Synapse will request the missing events between the most recent
# event and the event we gave it.
# https://github.com/matrix-org/synapse/blob/v0.33.7/synapse/handlers/federation.py#L266
# https://github.com/matrix-org/synapse/blob/v0.33.7/synapse/handlers/federation.py#L507
$inbound_server->await_request_get_missing_events( $room_id )->then( sub {
my ( $req ) = @_;

my $body = $req->body_from_json;
log_if_fail "/get_missing_events request", $body;

assert_deeply_eq(
$body->{latest_events},
[ $msg_e->{event_id } ],
"latest_events in /get_missing_events request",
);

# just return D
$req->respond_json( {
events => [ $msg_d ],
} );

Future->done(1);
}),

# Synapse will ask us for the state at C.
# https://github.com/matrix-org/synapse/blob/v0.33.7/synapse/handlers/federation.py#L355
$inbound_server->await_request_state_ids( $room_id, $evil_message_event_c->{event_id} )->then( sub {
my ( $req ) = @_;
$req->respond_json( {
# We tell it that the state is only our join event, the
# create event, and our evil power level event.
pdu_ids => [
$create->{event_id},
$join->{event_id},
$evil_power_level_event_x->{event_id},
],
# We need to give it our join event so that the evil power
# level event passes the auth checks.
auth_chain_ids => [
$create->{event_id},
$join->{event_id},
],
});
Future->done(1);
}),
);
})->then( sub {
# Now check that our our evil power_level hasn't won the state resolution.
matrix_get_room_state( $creator, $room_id,
type => "m.room.power_levels",
state_key => "",
);
})->then( sub {
my ( $content ) = @_;

assert_eq( $content->{users}{$creator->user_id}, 100 );

Future->done(1);
});
};

0 comments on commit 4283634

Please sign in to comment.