1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <gtest/gtest.h>
18
19 #include "../confirmationui_rate_limiting.h"
20 #include <keymaster/logger.h>
21
22 using std::vector;
23
24 namespace keystore {
25
26 namespace test {
27
28 namespace {
29
30 class StdoutLogger : public ::keymaster::Logger {
31 public:
StdoutLogger()32 StdoutLogger() { set_instance(this); }
33
log_msg(LogLevel level,const char * fmt,va_list args) const34 int log_msg(LogLevel level, const char* fmt, va_list args) const {
35 int output_len = 0;
36 switch (level) {
37 case DEBUG_LVL:
38 output_len = printf("DEBUG: ");
39 break;
40 case INFO_LVL:
41 output_len = printf("INFO: ");
42 break;
43 case WARNING_LVL:
44 output_len = printf("WARNING: ");
45 break;
46 case ERROR_LVL:
47 output_len = printf("ERROR: ");
48 break;
49 case SEVERE_LVL:
50 output_len = printf("SEVERE: ");
51 break;
52 }
53
54 output_len += vprintf(fmt, args);
55 output_len += printf("\n");
56 return output_len;
57 }
58 };
59
60 StdoutLogger logger;
61
62 class FakeClock : public std::chrono::steady_clock {
63 private:
64 static time_point sNow;
65
66 public:
setNow(time_point newNow)67 static void setNow(time_point newNow) { sNow = newNow; }
now()68 static time_point now() noexcept { return sNow; }
69 };
70
71 FakeClock::time_point FakeClock::sNow;
72
73 } // namespace
74
75 /*
76 * Test that there are no residual slots when various apps receive successful confirmations.
77 */
TEST(ConfirmationUIRateLimitingTest,noPenaltyTest)78 TEST(ConfirmationUIRateLimitingTest, noPenaltyTest) {
79 auto now = std::chrono::steady_clock::now();
80 RateLimiting<FakeClock> rateLimiting;
81 FakeClock::setNow(now);
82
83 for (int i = 0; i < 10000; ++i) {
84 ASSERT_TRUE(rateLimiting.tryPrompt(rand()));
85 rateLimiting.processResult(ConfirmationResponseCode::OK);
86 }
87
88 ASSERT_EQ(0U, rateLimiting.usedSlots());
89 }
90
TEST(ConfirmationUIRateLimitingTest,policyTest)91 TEST(ConfirmationUIRateLimitingTest, policyTest) {
92 using namespace std::chrono_literals;
93 auto now = std::chrono::steady_clock::now();
94 RateLimiting<FakeClock> rateLimiting;
95 FakeClock::setNow(now);
96
97 // first three tries are free
98 for (int i = 0; i < 3; ++i) {
99 ASSERT_TRUE(rateLimiting.tryPrompt(20));
100 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
101 }
102
103 // throw in a couple of successful confirmations by other apps to make sure there
104 // is not cross talk
105 for (int i = 0; i < 10000; ++i) {
106 uid_t id = rand();
107 if (id == 20) continue;
108 ASSERT_TRUE(rateLimiting.tryPrompt(id));
109 rateLimiting.processResult(ConfirmationResponseCode::OK);
110 }
111
112 // the next three tries get a 30s penalty
113 for (int i = 3; i < 6; ++i) {
114 FakeClock::setNow(FakeClock::now() + 29s);
115 ASSERT_FALSE(rateLimiting.tryPrompt(20));
116 FakeClock::setNow(FakeClock::now() + 1s);
117 ASSERT_TRUE(rateLimiting.tryPrompt(20));
118 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
119 }
120
121 // throw in a couple of successful confirmations by other apps to make sure there
122 // is not cross talk
123 for (int i = 0; i < 10000; ++i) {
124 uid_t id = rand();
125 if (id == 20) continue;
126 ASSERT_TRUE(rateLimiting.tryPrompt(id));
127 rateLimiting.processResult(ConfirmationResponseCode::OK);
128 }
129
130 // there after the penalty doubles with each cancellation
131 for (int i = 6; i < 17; ++i) {
132 FakeClock::setNow((FakeClock::now() + 60s * (1ULL << (i - 6))) - 1s);
133 ASSERT_FALSE(rateLimiting.tryPrompt(20));
134 FakeClock::setNow(FakeClock::now() + 1s);
135 ASSERT_TRUE(rateLimiting.tryPrompt(20));
136 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
137 }
138
139 // throw in a couple of successful confirmations by other apps to make sure there
140 // is not cross talk
141 for (int i = 0; i < 10000; ++i) {
142 uid_t id = rand();
143 if (id == 20) continue;
144 ASSERT_TRUE(rateLimiting.tryPrompt(id));
145 rateLimiting.processResult(ConfirmationResponseCode::OK);
146 }
147
148 ASSERT_EQ(1U, rateLimiting.usedSlots());
149
150 FakeClock::setNow(FakeClock::now() + 24h - 1s);
151 ASSERT_FALSE(rateLimiting.tryPrompt(20));
152
153 // after 24h the counter is forgotten
154 FakeClock::setNow(FakeClock::now() + 1s);
155 ASSERT_TRUE(rateLimiting.tryPrompt(20));
156 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
157
158 // throw in a couple of successful confirmations by other apps to make sure there
159 // is not cross talk
160 for (int i = 0; i < 10000; ++i) {
161 uid_t id = rand();
162 if (id == 20) continue;
163 ASSERT_TRUE(rateLimiting.tryPrompt(id));
164 rateLimiting.processResult(ConfirmationResponseCode::OK);
165 }
166
167 for (int i = 1; i < 3; ++i) {
168 ASSERT_TRUE(rateLimiting.tryPrompt(20));
169 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
170 }
171
172 // throw in a couple of successful confirmations by other apps to make sure there
173 // is not cross talk
174 for (int i = 0; i < 10000; ++i) {
175 uid_t id = rand();
176 if (id == 20) continue;
177 ASSERT_TRUE(rateLimiting.tryPrompt(id));
178 rateLimiting.processResult(ConfirmationResponseCode::OK);
179 }
180
181 for (int i = 3; i < 6; ++i) {
182 FakeClock::setNow(FakeClock::now() + 29s);
183 ASSERT_FALSE(rateLimiting.tryPrompt(20));
184 FakeClock::setNow(FakeClock::now() + 1s);
185 ASSERT_TRUE(rateLimiting.tryPrompt(20));
186 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
187 }
188
189 // throw in a couple of successful confirmations by other apps to make sure there
190 // is not cross talk
191 for (int i = 0; i < 10000; ++i) {
192 uid_t id = rand();
193 if (id == 20) continue;
194 ASSERT_TRUE(rateLimiting.tryPrompt(id));
195 rateLimiting.processResult(ConfirmationResponseCode::OK);
196 }
197
198 for (int i = 6; i < 17; ++i) {
199 FakeClock::setNow((FakeClock::now() + 60s * (1ULL << (i - 6))) - 1s);
200 ASSERT_FALSE(rateLimiting.tryPrompt(20));
201 FakeClock::setNow(FakeClock::now() + 1s);
202 ASSERT_TRUE(rateLimiting.tryPrompt(20));
203 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
204 }
205
206 // throw in a couple of successful confirmations by other apps to make sure there
207 // is not cross talk
208 for (int i = 0; i < 10000; ++i) {
209 uid_t id = rand();
210 if (id == 20) continue;
211 ASSERT_TRUE(rateLimiting.tryPrompt(id));
212 rateLimiting.processResult(ConfirmationResponseCode::OK);
213 }
214
215 ASSERT_EQ(1U, rateLimiting.usedSlots());
216 }
217
TEST(ConfirmationUIRateLimitingTest,rewindTest)218 TEST(ConfirmationUIRateLimitingTest, rewindTest) {
219 using namespace std::chrono_literals;
220 auto now = std::chrono::steady_clock::now();
221 RateLimiting<FakeClock> rateLimiting;
222
223 // first three tries are free
224 for (int i = 0; i < 3; ++i) {
225 FakeClock::setNow(now);
226 ASSERT_TRUE(rateLimiting.tryPrompt(20));
227 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
228 }
229
230 for (int i = 3; i < 6; ++i) {
231 FakeClock::setNow(FakeClock::now() + 29s);
232 ASSERT_FALSE(rateLimiting.tryPrompt(20));
233 FakeClock::setNow(FakeClock::now() + 1s);
234 ASSERT_TRUE(rateLimiting.tryPrompt(20));
235 rateLimiting.processResult(ConfirmationResponseCode::Canceled);
236 }
237
238 FakeClock::setNow(FakeClock::now() + 59s);
239 ASSERT_FALSE(rateLimiting.tryPrompt(20));
240 FakeClock::setNow(FakeClock::now() + 1s);
241 ASSERT_TRUE(rateLimiting.tryPrompt(20));
242 rateLimiting.processResult(ConfirmationResponseCode::Aborted);
243
244 FakeClock::setNow(FakeClock::now() - 1s);
245 ASSERT_FALSE(rateLimiting.tryPrompt(20));
246 FakeClock::setNow(FakeClock::now() + 1s);
247 ASSERT_TRUE(rateLimiting.tryPrompt(20));
248 rateLimiting.processResult(ConfirmationResponseCode::SystemError);
249
250 // throw in a couple of successful confirmations by other apps to make sure there
251 // is not cross talk
252 for (int i = 0; i < 10000; ++i) {
253 uid_t id = rand();
254 if (id == 20) continue;
255 ASSERT_TRUE(rateLimiting.tryPrompt(id));
256 rateLimiting.processResult(ConfirmationResponseCode::OK);
257 }
258
259 FakeClock::setNow(FakeClock::now() - 1s);
260 ASSERT_FALSE(rateLimiting.tryPrompt(20));
261 FakeClock::setNow(FakeClock::now() + 1s);
262 ASSERT_TRUE(rateLimiting.tryPrompt(20));
263 rateLimiting.processResult(ConfirmationResponseCode::UIError);
264
265 FakeClock::setNow(FakeClock::now() - 1s);
266 ASSERT_FALSE(rateLimiting.tryPrompt(20));
267 FakeClock::setNow(FakeClock::now() + 1s);
268 ASSERT_TRUE(rateLimiting.tryPrompt(20));
269
270 ASSERT_EQ(1U, rateLimiting.usedSlots());
271 }
272
273 } // namespace test
274 } // namespace keystore
275