2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "SecdWatchdog.h"
25 #include "utilities/debugging.h"
26 #include <xpc/private.h>
27 #import <xpc/private.h>
30 #import <mach/mach_time.h>
31 #import <mach/message.h>
32 #import <os/assumes.h>
35 #import <CrashReporterSupport/CrashReporterSupport.h>
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)
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";
50 void SecdLoadWatchDog()
52 (void)[SecdWatchdog watchdog];
55 @implementation SecdWatchdog {
56 uint64_t _rusageBaseline;
57 CFTimeInterval _lastCheckTime;
58 dispatch_source_t _timer;
60 uint64_t _runtimeSecondsBeforeWatchdog;
63 long _checkPeriodLeeway;
64 long _gracefulExitLeeway;
65 uint64_t _diskUsageBaseLine;
66 uint64_t _diskUsageLimit;
71 @synthesize diskUsageHigh = _diskUsageHigh;
73 + (instancetype)watchdog
75 static SecdWatchdog* watchdog = nil;
76 static dispatch_once_t onceToken;
77 dispatch_once(&onceToken, ^{
78 watchdog = [[self alloc] init];
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;
101 - (uint64_t)secondsFromMachTime:(uint64_t)machTime
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;
114 return (machTime * ratio)/NSEC_PER_SEC;
117 + (bool)watchdogrusage:(rusage_info_current *)rusage
119 if (proc_pid_rusage(getpid(), RUSAGE_INFO_CURRENT, (rusage_info_t *)rusage) != 0) {
125 + (bool)triggerOSFaults
132 rusage_info_current currentRusage;
134 if (![[self class] watchdogrusage:¤tRusage]) {
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");
143 WriteStackshotReport(@"securityd watchdog triggered", __sec_exception_code_Watchdog);
145 xpc_transaction_exit_clean(); // we've used too much CPU - try to exit gracefully
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");
156 if (_diskUsageHigh == false &&
157 (currentRusage.ri_logical_writes > _diskUsageBaseLine + _diskUsageLimit))
159 if ([[self class] triggerOSFaults]) {
160 os_log_fault(OS_LOG_DEFAULT, "securityd have written more then %llu",
161 (unsigned long long)_diskUsageLimit);
163 _diskUsageHigh = true;
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;
179 - (void)activateTimer
181 @synchronized (self) {
183 rusage_info_current initialRusage;
184 [[self class] watchdogrusage:&initialRusage];
186 _rusageBaseline = [self secondsFromMachTime:initialRusage.ri_user_time];
187 _lastCheckTime = CFAbsoluteTimeGetCurrent();
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;
197 [strongSelf runWatchdog];
199 dispatch_resume(_timer);
203 - (NSDictionary*)watchdogParameters
205 @synchronized (self) {
206 return @{ SecdWatchdogAllowedRuntime : @(_runtimeSecondsBeforeWatchdog),
207 SecdWatchdogResetPeriod : @(_resetPeriod),
208 SecdWatchdogCheckPeriod : @(_checkPeriod),
209 SecdWatchdogGracefulExitTime : @(_gracefulExitLeeway) };
213 - (BOOL)setWatchdogParameters:(NSDictionary*)parameters error:(NSError**)error
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;
224 if ([parameter isEqualToString:SecdWatchdogAllowedRuntime] && [value isKindOfClass:[NSNumber class]]) {
225 strongSelf->_runtimeSecondsBeforeWatchdog = value.longValue;
227 else if ([parameter isEqualToString:SecdWatchdogResetPeriod] && [value isKindOfClass:[NSNumber class]]) {
228 strongSelf->_resetPeriod = value.longValue;
230 else if ([parameter isEqualToString:SecdWatchdogCheckPeriod] && [value isKindOfClass:[NSNumber class]]) {
231 strongSelf->_checkPeriod = value.longValue;
233 else if ([parameter isEqualToString:SecdWatchdogGracefulExitTime] && [value isKindOfClass:[NSNumber class]]) {
234 strongSelf->_gracefulExitLeeway = value.longValue;
237 [failedParameters addObject:parameter];
241 dispatch_source_cancel(_timer);
245 [self activateTimer];
247 if (failedParameters.count > 0) {
249 *error = [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:0 userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"failed to set parameters: %@", failedParameters]}];