Skip to content

Add a test against PL takeovers #520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
};