]>
Commit | Line | Data |
---|---|---|
866f8763 A |
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, ¤tRusage); | |
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 |