1 /* 2 ** 3 ** Copyright 2018, The Android Open Source Project 4 ** 5 ** Licensed under the Apache License, Version 2.0 (the "License"); 6 ** you may not use this file except in compliance with the License. 7 ** You may obtain a copy of the License at 8 ** 9 ** http://www.apache.org/licenses/LICENSE-2.0 10 ** 11 ** Unless required by applicable law or agreed to in writing, software 12 ** distributed under the License is distributed on an "AS IS" BASIS, 13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 ** See the License for the specific language governing permissions and 15 ** limitations under the License. 16 */ 17 18 #ifndef KEYSTORE_CONFIRMATIONUI_RATE_LIMITING_H_ 19 #define KEYSTORE_CONFIRMATIONUI_RATE_LIMITING_H_ 20 21 #include <android/hardware/confirmationui/1.0/types.h> 22 #include <chrono> 23 #include <stdint.h> 24 #include <sys/types.h> 25 #include <tuple> 26 #include <unordered_map> 27 28 namespace keystore { 29 30 using ConfirmationResponseCode = android::hardware::confirmationui::V1_0::ResponseCode; 31 32 using std::chrono::duration; 33 using std::chrono::time_point; 34 35 template <typename Clock = std::chrono::steady_clock> class RateLimiting { 36 private: 37 struct Slot { SlotSlot38 Slot() : previous_start{}, prompt_start{}, counter(0) {} 39 typename Clock::time_point previous_start; 40 typename Clock::time_point prompt_start; 41 uint32_t counter; 42 }; 43 44 std::unordered_map<uid_t, Slot> slots_; 45 46 uint_t latest_requester_; 47 getBackoff(uint32_t counter)48 static std::chrono::seconds getBackoff(uint32_t counter) { 49 using namespace std::chrono_literals; 50 switch (counter) { 51 case 0: 52 case 1: 53 case 2: 54 return 0s; 55 case 3: 56 case 4: 57 case 5: 58 return 30s; 59 default: 60 return 60s * (1ULL << (counter - 6)); 61 } 62 } 63 64 public: 65 // Exposes the number of used slots. This is only used by the test to verify the assumption 66 // about used counter slots. usedSlots()67 size_t usedSlots() const { return slots_.size(); } doGC()68 void doGC() { 69 using namespace std::chrono_literals; 70 using std::chrono::system_clock; 71 using std::chrono::time_point_cast; 72 auto then = Clock::now() - 24h; 73 auto iter = slots_.begin(); 74 while (iter != slots_.end()) { 75 if (iter->second.prompt_start <= then) { 76 iter = slots_.erase(iter); 77 } else { 78 ++iter; 79 } 80 } 81 } 82 tryPrompt(uid_t id)83 bool tryPrompt(uid_t id) { 84 using namespace std::chrono_literals; 85 // remove slots that have not been touched in 24 hours 86 doGC(); 87 auto& slot = slots_[id]; 88 auto now = Clock::now(); 89 if (!slot.counter || slot.prompt_start <= now - getBackoff(slot.counter)) { 90 latest_requester_ = id; 91 slot.counter += 1; 92 slot.previous_start = slot.prompt_start; 93 slot.prompt_start = now; 94 return true; 95 } 96 return false; 97 } 98 99 // The app is penalized for cancelling a request. Request are rolled back only if 100 // the prompt was cancelled by the system: e.g. a system error or asynchronous event. 101 // When the user cancels the prompt, it is subject to rate limiting. 102 static constexpr const uint_t kInvalidRequester = -1; 103 cancelPrompt()104 void cancelPrompt() { latest_requester_ = kInvalidRequester; } 105 processResult(ConfirmationResponseCode rc)106 void processResult(ConfirmationResponseCode rc) { 107 if (latest_requester_ == kInvalidRequester) { 108 return; 109 } 110 switch (rc) { 111 case ConfirmationResponseCode::OK: 112 // reset the counter slot 113 slots_.erase(latest_requester_); 114 return; 115 case ConfirmationResponseCode::Canceled: 116 // nothing to do here 117 return; 118 default:; 119 } 120 121 // roll back latest request 122 auto& slot = slots_[latest_requester_]; 123 if (slot.counter <= 1) { 124 slots_.erase(latest_requester_); 125 return; 126 } 127 slot.counter -= 1; 128 slot.prompt_start = slot.previous_start; 129 } 130 }; 131 132 } // namespace keystore 133 134 #endif // KEYSTORE_CONFIRMATIONUI_RATE_LIMITING_H_ 135