]> git.saurik.com Git - apple/security.git/blob - OSX/sec/ipc/SecdWatchdog.m
Security-59306.101.1.tar.gz
[apple/security.git] / OSX / sec / ipc / SecdWatchdog.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #import "SecdWatchdog.h"
25 #include "utilities/debugging.h"
26 #include <xpc/private.h>
27 #import <xpc/private.h>
28 #import <libproc.h>
29 #import <os/log.h>
30 #import <mach/mach_time.h>
31 #import <mach/message.h>
32 #import <os/assumes.h>
33
34 #if !TARGET_OS_MAC
35 #import <CrashReporterSupport/CrashReporterSupport.h>
36 #endif
37
38 #define CPU_RUNTIME_SECONDS_BEFORE_WATCHDOG (60 * 20)
39 #define WATCHDOG_RESET_PERIOD (60 * 60 * 24)
40 #define WATCHDOG_CHECK_PERIOD (60 * 60)
41 #define WATCHDOG_CHECK_PERIOD_LEEWAY (60 * 10)
42 #define WATCHDOG_GRACEFUL_EXIT_LEEWAY (60 * 5)
43 #define WATCHDOG_DISKUSAGE_LIMIT (1000 * 1024 * 1024) // (1GiBi)
44
45 NSString* const SecdWatchdogAllowedRuntime = @"allowed-runtime";
46 NSString* const SecdWatchdogResetPeriod = @"reset-period";
47 NSString* const SecdWatchdogCheckPeriod = @"check-period";
48 NSString* const SecdWatchdogGracefulExitTime = @"graceful-exit-time";
49
50 void SecdLoadWatchDog()
51 {
52 (void)[SecdWatchdog watchdog];
53 }
54
55 @implementation SecdWatchdog {
56 uint64_t _rusageBaseline;
57 CFTimeInterval _lastCheckTime;
58 dispatch_source_t _timer;
59
60 uint64_t _runtimeSecondsBeforeWatchdog;
61 long _resetPeriod;
62 long _checkPeriod;
63 long _checkPeriodLeeway;
64 long _gracefulExitLeeway;
65 uint64_t _diskUsageBaseLine;
66 uint64_t _diskUsageLimit;
67
68 bool _diskUsageHigh;
69 }
70
71 @synthesize diskUsageHigh = _diskUsageHigh;
72
73 + (instancetype)watchdog
74 {
75 static SecdWatchdog* watchdog = nil;
76 static dispatch_once_t onceToken;
77 dispatch_once(&onceToken, ^{
78 watchdog = [[self alloc] init];
79 });
80
81 return watchdog;
82 }
83
84 - (instancetype)init
85 {
86 if (self = [super init]) {
87 _runtimeSecondsBeforeWatchdog = CPU_RUNTIME_SECONDS_BEFORE_WATCHDOG;
88 _resetPeriod = WATCHDOG_RESET_PERIOD;
89 _checkPeriod = WATCHDOG_CHECK_PERIOD;
90 _checkPeriodLeeway = WATCHDOG_CHECK_PERIOD_LEEWAY;
91 _gracefulExitLeeway = WATCHDOG_GRACEFUL_EXIT_LEEWAY;
92 _diskUsageLimit = WATCHDOG_DISKUSAGE_LIMIT;
93 _diskUsageHigh = false;
94
95 [self activateTimer];
96 }
97
98 return self;
99 }
100
101 - (uint64_t)secondsFromMachTime:(uint64_t)machTime
102 {
103 static dispatch_once_t once;
104 static uint64_t ratio;
105 dispatch_once(&once, ^{
106 mach_timebase_info_data_t tbi;
107 if (os_assumes_zero(mach_timebase_info(&tbi)) == KERN_SUCCESS) {
108 ratio = tbi.numer / tbi.denom;
109 } else {
110 ratio = 1;
111 }
112 });
113
114 return (machTime * ratio)/NSEC_PER_SEC;
115 }
116
117 + (bool)watchdogrusage:(rusage_info_current *)rusage
118 {
119 if (proc_pid_rusage(getpid(), RUSAGE_INFO_CURRENT, (rusage_info_t *)rusage) != 0) {
120 return false;
121 }
122 return true;
123 }
124
125 + (bool)triggerOSFaults
126 {
127 return true;
128 }
129
130 - (void)runWatchdog
131 {
132 rusage_info_current currentRusage;
133
134 if (![[self class] watchdogrusage:&currentRusage]) {
135 return;
136 }
137
138 @synchronized (self) {
139 uint64_t spentUserTime = [self secondsFromMachTime:currentRusage.ri_user_time];
140 if (spentUserTime > _rusageBaseline + _runtimeSecondsBeforeWatchdog) {
141 seccritical("SecWatchdog: watchdog has detected securityd/secd is using too much CPU - attempting to exit gracefully");
142 #if !TARGET_OS_MAC
143 WriteStackshotReport(@"securityd watchdog triggered", __sec_exception_code_Watchdog);
144 #endif
145 xpc_transaction_exit_clean(); // we've used too much CPU - try to exit gracefully
146
147 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_gracefulExitLeeway * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
148 // if we still haven't exited gracefully after 5 minutes, time to die unceremoniously
149 seccritical("SecWatchdog: watchdog has failed to exit securityd/secd gracefully - exiting ungracefully");
150 exit(EXIT_FAILURE);
151 });
152
153 return;
154 }
155
156 if (_diskUsageHigh == false &&
157 (currentRusage.ri_logical_writes > _diskUsageBaseLine + _diskUsageLimit))
158 {
159 if ([[self class] triggerOSFaults]) {
160 os_log_fault(OS_LOG_DEFAULT, "securityd have written more then %llu",
161 (unsigned long long)_diskUsageLimit);
162 }
163 _diskUsageHigh = true;
164 }
165
166 CFTimeInterval currentTime = CFAbsoluteTimeGetCurrent();
167 if (currentTime > _lastCheckTime + _resetPeriod) {
168 // we made it through a 24 hour period - reset our timeout to 24 hours from now, and our cpu usage threshold to another 20 minutes
169 secinfo("SecWatchdog", "resetting watchdog monitoring interval ahead another 24 hours");
170 _lastCheckTime = currentTime;
171 _rusageBaseline = spentUserTime;
172 _diskUsageHigh = false;
173 _diskUsageBaseLine = currentRusage.ri_logical_writes;
174 }
175 }
176
177 }
178
179 - (void)activateTimer
180 {
181 @synchronized (self) {
182
183 rusage_info_current initialRusage;
184 [[self class] watchdogrusage:&initialRusage];
185
186 _rusageBaseline = [self secondsFromMachTime:initialRusage.ri_user_time];
187 _lastCheckTime = CFAbsoluteTimeGetCurrent();
188
189 __weak __typeof(self) weakSelf = self;
190 _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
191 dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, _checkPeriod * NSEC_PER_SEC, _checkPeriodLeeway * NSEC_PER_SEC); // run once every hour, but give the timer lots of leeway to be power friendly
192 dispatch_source_set_event_handler(_timer, ^{
193 __strong __typeof(self) strongSelf = weakSelf;
194 if (!strongSelf) {
195 return;
196 }
197 [strongSelf runWatchdog];
198 });
199 dispatch_resume(_timer);
200 }
201 }
202
203 - (NSDictionary*)watchdogParameters
204 {
205 @synchronized (self) {
206 return @{ SecdWatchdogAllowedRuntime : @(_runtimeSecondsBeforeWatchdog),
207 SecdWatchdogResetPeriod : @(_resetPeriod),
208 SecdWatchdogCheckPeriod : @(_checkPeriod),
209 SecdWatchdogGracefulExitTime : @(_gracefulExitLeeway) };
210 }
211 }
212
213 - (BOOL)setWatchdogParameters:(NSDictionary*)parameters error:(NSError**)error
214 {
215 NSMutableArray* failedParameters = [NSMutableArray array];
216 @synchronized (self) {
217 __weak __typeof(self) weakSelf = self;
218 [parameters enumerateKeysAndObjectsUsingBlock:^(NSString* parameter, NSNumber* value, BOOL* stop) {
219 __strong __typeof(self) strongSelf = weakSelf;
220 if (!strongSelf) {
221 return;
222 }
223
224 if ([parameter isEqualToString:SecdWatchdogAllowedRuntime] && [value isKindOfClass:[NSNumber class]]) {
225 strongSelf->_runtimeSecondsBeforeWatchdog = value.longValue;
226 }
227 else if ([parameter isEqualToString:SecdWatchdogResetPeriod] && [value isKindOfClass:[NSNumber class]]) {
228 strongSelf->_resetPeriod = value.longValue;
229 }
230 else if ([parameter isEqualToString:SecdWatchdogCheckPeriod] && [value isKindOfClass:[NSNumber class]]) {
231 strongSelf->_checkPeriod = value.longValue;
232 }
233 else if ([parameter isEqualToString:SecdWatchdogGracefulExitTime] && [value isKindOfClass:[NSNumber class]]) {
234 strongSelf->_gracefulExitLeeway = value.longValue;
235 }
236 else {
237 [failedParameters addObject:parameter];
238 }
239 }];
240
241 dispatch_source_cancel(_timer);
242 _timer = NULL;
243 }
244
245 [self activateTimer];
246
247 if (failedParameters.count > 0) {
248 if (error) {
249 *error = [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:0 userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"failed to set parameters: %@", failedParameters]}];
250 }
251
252 return NO;
253 }
254 else {
255 return YES;
256 }
257 }
258
259 @end