]> git.saurik.com Git - apple/security.git/blob - OSX/sec/ipc/SecdWatchdog.m
Security-58286.200.222.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
28 #if !TARGET_OS_MAC
29 #import <CrashReporterSupport/CrashReporterSupport.h>
30 #endif
31
32 #define CPU_RUNTIME_SECONDS_BEFORE_WATCHDOG (60 * 20)
33 #define WATCHDOG_RESET_PERIOD (60 * 60 * 24)
34 #define WATCHDOG_CHECK_PERIOD (60 * 60)
35 #define WATCHDOG_CHECK_PERIOD_LEEWAY (60 * 10)
36 #define WATCHDOG_GRACEFUL_EXIT_LEEWAY (60 * 5)
37
38 NSString* const SecdWatchdogAllowedRuntime = @"allowed-runtime";
39 NSString* const SecdWatchdogResetPeriod = @"reset-period";
40 NSString* const SecdWatchdogCheckPeriod = @"check-period";
41 NSString* const SecdWatchdogGracefulExitTime = @"graceful-exit-time";
42
43 void SecdLoadWatchDog()
44 {
45 (void)[SecdWatchdog watchdog];
46 }
47
48 @implementation SecdWatchdog {
49 long _rusageBaseline;
50 CFTimeInterval _lastCheckTime;
51 dispatch_source_t _timer;
52
53 long _runtimeSecondsBeforeWatchdog;
54 long _resetPeriod;
55 long _checkPeriod;
56 long _checkPeriodLeeway;
57 long _gracefulExitLeeway;
58 }
59
60 + (instancetype)watchdog
61 {
62 static SecdWatchdog* watchdog = nil;
63 static dispatch_once_t onceToken;
64 dispatch_once(&onceToken, ^{
65 watchdog = [[self alloc] init];
66 });
67
68 return watchdog;
69 }
70
71 - (instancetype)init
72 {
73 if (self = [super init]) {
74 _runtimeSecondsBeforeWatchdog = CPU_RUNTIME_SECONDS_BEFORE_WATCHDOG;
75 _resetPeriod = WATCHDOG_RESET_PERIOD;
76 _checkPeriod = WATCHDOG_CHECK_PERIOD;
77 _checkPeriodLeeway = WATCHDOG_CHECK_PERIOD_LEEWAY;
78 _gracefulExitLeeway = WATCHDOG_GRACEFUL_EXIT_LEEWAY;
79
80 [self activateTimer];
81 }
82
83 return self;
84 }
85
86 - (void)activateTimer
87 {
88 @synchronized (self) {
89 struct rusage initialRusage;
90 getrusage(RUSAGE_SELF, &initialRusage);
91 _rusageBaseline = initialRusage.ru_utime.tv_sec;
92 _lastCheckTime = CFAbsoluteTimeGetCurrent();
93
94 __weak __typeof(self) weakSelf = self;
95 _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
96 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
97 dispatch_source_set_event_handler(_timer, ^{
98 __strong __typeof(self) strongSelf = weakSelf;
99 if (!strongSelf) {
100 return;
101 }
102
103 struct rusage currentRusage;
104 getrusage(RUSAGE_SELF, &currentRusage);
105
106 @synchronized (self) {
107 if (currentRusage.ru_utime.tv_sec > strongSelf->_rusageBaseline + strongSelf->_runtimeSecondsBeforeWatchdog) {
108 seccritical("SecWatchdog: watchdog has detected securityd/secd is using too much CPU - attempting to exit gracefully");
109 #if !TARGET_OS_MAC
110 WriteStackshotReport(@"securityd watchdog triggered", __sec_exception_code_Watchdog);
111 #endif
112 xpc_transaction_exit_clean(); // we've used too much CPU - try to exit gracefully
113
114 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(strongSelf->_gracefulExitLeeway * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
115 // if we still haven't exited gracefully after 5 minutes, time to die unceremoniously
116 seccritical("SecWatchdog: watchdog has failed to exit securityd/secd gracefully - exiting ungracefully");
117 exit(EXIT_FAILURE);
118 });
119 }
120 else {
121 CFTimeInterval currentTime = CFAbsoluteTimeGetCurrent();
122 if (currentTime > strongSelf->_lastCheckTime + strongSelf->_resetPeriod) {
123 // 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
124 secinfo("SecWatchdog", "resetting watchdog monitoring interval ahead another 24 hours");
125 strongSelf->_lastCheckTime = currentTime;
126 strongSelf->_rusageBaseline = currentRusage.ru_utime.tv_sec;
127 }
128 }
129 }
130 });
131 dispatch_resume(_timer);
132 }
133 }
134
135 - (NSDictionary*)watchdogParameters
136 {
137 @synchronized (self) {
138 return @{ SecdWatchdogAllowedRuntime : @(_runtimeSecondsBeforeWatchdog),
139 SecdWatchdogResetPeriod : @(_resetPeriod),
140 SecdWatchdogCheckPeriod : @(_checkPeriod),
141 SecdWatchdogGracefulExitTime : @(_gracefulExitLeeway) };
142 }
143 }
144
145 - (BOOL)setWatchdogParameters:(NSDictionary*)parameters error:(NSError**)error
146 {
147 NSMutableArray* failedParameters = [NSMutableArray array];
148 @synchronized (self) {
149 __weak __typeof(self) weakSelf = self;
150 [parameters enumerateKeysAndObjectsUsingBlock:^(NSString* parameter, NSNumber* value, BOOL* stop) {
151 __strong __typeof(self) strongSelf = weakSelf;
152 if (!strongSelf) {
153 return;
154 }
155
156 if ([parameter isEqualToString:SecdWatchdogAllowedRuntime] && [value isKindOfClass:[NSNumber class]]) {
157 strongSelf->_runtimeSecondsBeforeWatchdog = value.longValue;
158 }
159 else if ([parameter isEqualToString:SecdWatchdogResetPeriod] && [value isKindOfClass:[NSNumber class]]) {
160 strongSelf->_resetPeriod = value.longValue;
161 }
162 else if ([parameter isEqualToString:SecdWatchdogCheckPeriod] && [value isKindOfClass:[NSNumber class]]) {
163 strongSelf->_checkPeriod = value.longValue;
164 }
165 else if ([parameter isEqualToString:SecdWatchdogGracefulExitTime] && [value isKindOfClass:[NSNumber class]]) {
166 strongSelf->_gracefulExitLeeway = value.longValue;
167 }
168 else {
169 [failedParameters addObject:parameter];
170 }
171 }];
172
173 dispatch_source_cancel(_timer);
174 _timer = NULL;
175 }
176
177 [self activateTimer];
178
179 if (failedParameters.count > 0) {
180 if (error) {
181 *error = [NSError errorWithDomain:@"com.apple.securityd.watchdog" code:0 userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"failed to set parameters: %@", failedParameters]}];
182 }
183
184 return NO;
185 }
186 else {
187 return YES;
188 }
189 }
190
191 @end