Line data Source code
1 : /**
2 : * @file test_wrong_session_id.c
3 : * @brief TEST-29 — functional test: mock server injects wrong session_id;
4 : * client rejects the frame.
5 : *
6 : * The unit tests in tests/unit/test_rpc.c (test_recv_encrypted_wrong_session_id)
7 : * exercise rpc_recv_encrypted() directly with fake frames and mock crypto.
8 : * This functional test goes one layer higher: it seeds a real MTProto session,
9 : * arms the mock server to emit a reply with a deliberately wrong session_id,
10 : * issues a real api_call(), and asserts that the call returns -1.
11 : *
12 : * Flow:
13 : * 1. mt_server_seed_session() → real auth key on disk and in mock server.
14 : * 2. mt_server_expect() → register a responder that queues the (tampered)
15 : * reply; the client rejects the frame during rpc_recv_encrypted().
16 : * 3. mt_server_set_wrong_session_id_once() → next reply will have
17 : * session_id XOR 0xFFFFFFFFFFFFFFFF in the plaintext.
18 : * 4. api_call() sends users.getUsers, receives the tampered frame, and
19 : * must return -1.
20 : */
21 :
22 : #include "test_helpers.h"
23 :
24 : #include "mock_socket.h"
25 : #include "mock_tel_server.h"
26 :
27 : #include "api_call.h"
28 : #include "mtproto_session.h"
29 : #include "transport.h"
30 : #include "app/session_store.h"
31 : #include "tl_serial.h"
32 : #include "tl_registry.h"
33 :
34 : #include <stdio.h>
35 : #include <stdlib.h>
36 : #include <string.h>
37 : #include <unistd.h>
38 :
39 : /* ---- helpers ---- */
40 :
41 2 : static void with_tmp_home_wsi(const char *tag) {
42 : char tmp[256];
43 2 : snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-ft-wsi-%s", tag);
44 : char bin[512];
45 2 : snprintf(bin, sizeof(bin), "%s/.config/tg-cli/session.bin", tmp);
46 2 : (void)unlink(bin);
47 2 : setenv("HOME", tmp, 1);
48 2 : }
49 :
50 2 : static void connect_mock_wsi(Transport *t) {
51 2 : transport_init(t);
52 2 : ASSERT(transport_connect(t, "127.0.0.1", 443) == 0,
53 : "transport connects for wrong-session-id test");
54 : }
55 :
56 2 : static void init_cfg_wsi(ApiConfig *cfg) {
57 2 : api_config_init(cfg);
58 2 : cfg->api_id = 12345;
59 2 : cfg->api_hash = "deadbeefcafebabef00dbaadfeedc0de";
60 2 : }
61 :
62 : /* ---- responder ---- */
63 :
64 : /**
65 : * Emit a minimal reply. The mock server calls this when the client's
66 : * request arrives, before the (tampered) reply is enqueued. So this
67 : * function WILL run — we rely on it to actually queue the bad frame.
68 : */
69 2 : static void on_get_users_bad_reply(MtRpcContext *ctx) {
70 : TlWriter w;
71 2 : tl_writer_init(&w);
72 2 : tl_write_uint32(&w, TL_vector);
73 2 : tl_write_uint32(&w, 0);
74 2 : mt_server_reply_result(ctx, w.data, w.len);
75 2 : tl_writer_free(&w);
76 2 : }
77 :
78 : /* ================================================================ */
79 : /* Test case */
80 : /* ================================================================ */
81 :
82 : /**
83 : * @brief TEST-29 — wrong session_id in server reply must be rejected.
84 : *
85 : * The mock server is armed to flip all bits of the session_id in its next
86 : * reply. rpc_recv_encrypted() verifies the decrypted session_id against
87 : * s->session_id and must return -1, causing api_call() to propagate -1 to
88 : * the caller.
89 : *
90 : * Note on sequencing: the mock server dispatches the client request
91 : * synchronously inside api_call() before the client attempts to read.
92 : * The responder fires (server-side) and enqueues the tampered reply.
93 : * The client then calls rpc_recv_encrypted(), detects the wrong session_id,
94 : * and returns -1. mt_server_rpc_call_count() confirms the server did
95 : * receive and process the request.
96 : */
97 2 : static void test_wrong_session_id_rejected(void) {
98 2 : with_tmp_home_wsi("reject");
99 :
100 2 : mt_server_init();
101 2 : mt_server_reset();
102 2 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed session");
103 :
104 : /* users.getUsers CRC — a simple query the mock server can handle. */
105 : #define CRC_users_getUsers 0x0d91a548U
106 2 : mt_server_expect(CRC_users_getUsers, on_get_users_bad_reply, NULL);
107 :
108 : /* Arm the one-shot: next reply frame will have a wrong session_id. */
109 2 : mt_server_set_wrong_session_id_once();
110 :
111 2 : ApiConfig cfg; init_cfg_wsi(&cfg);
112 2 : MtProtoSession s; mtproto_session_init(&s);
113 2 : int dc = 0;
114 2 : ASSERT(session_store_load(&s, &dc) == 0, "session loaded");
115 :
116 2 : Transport t; connect_mock_wsi(&t);
117 :
118 : /* Build users.getUsers(inputUserSelf) query. */
119 : #define CRC_inputUserSelf 0xf7c1b13fU
120 : TlWriter q;
121 2 : tl_writer_init(&q);
122 2 : tl_write_uint32(&q, CRC_users_getUsers);
123 2 : tl_write_uint32(&q, TL_vector);
124 2 : tl_write_uint32(&q, 1);
125 2 : tl_write_uint32(&q, CRC_inputUserSelf);
126 2 : tl_write_uint64(&q, 0ULL); /* access_hash */
127 :
128 : uint8_t resp[4096];
129 2 : size_t resp_len = 0;
130 2 : int rc = api_call(&cfg, &s, &t, q.data, q.len, resp, sizeof(resp), &resp_len);
131 2 : tl_writer_free(&q);
132 :
133 2 : ASSERT(rc == -1, "api_call must return -1 when session_id is wrong");
134 :
135 : /* The mock server dispatched one RPC (used to queue the tampered reply). */
136 2 : ASSERT(mt_server_rpc_call_count() == 1,
137 : "mock server must have received exactly one RPC call");
138 :
139 2 : transport_close(&t);
140 2 : mt_server_reset();
141 : }
142 :
143 : /* ================================================================ */
144 : /* Suite entry point */
145 : /* ================================================================ */
146 :
147 2 : void run_wrong_session_id_tests(void) {
148 2 : RUN_TEST(test_wrong_session_id_rejected);
149 2 : }
|