• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / santa / 9022372503

09 May 2024 07:16PM UTC coverage: 62.493% (-0.1%) from 62.62%
9022372503

Pull #1342

github

web-flow
Merge 005076387 into 6cca5ab27
Pull Request #1342: santad: Bump QoS of notify handling queue

5279 of 12796 branches covered (41.26%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 1 file covered. (100.0%)

9 existing lines in 2 files now uncovered.

17661 of 23912 relevant lines covered (73.86%)

7043.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.34
/Source/santad/EventProviders/SNTEndpointSecurityClientTest.mm
1
/// Copyright 2022 Google Inc. All rights reserved.
2
///
3
/// Licensed under the Apache License, Version 2.0 (the "License");
4
/// you may not use this file except in compliance with the License.
5
/// You may obtain a copy of the License at
6
///
7
///    http://www.apache.org/licenses/LICENSE-2.0
8
///
9
///    Unless required by applicable law or agreed to in writing, software
10
///    distributed under the License is distributed on an "AS IS" BASIS,
11
///    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
///    See the License for the specific language governing permissions and
13
///    limitations under the License.
14

15
#include <EndpointSecurity/EndpointSecurity.h>
16
#import <OCMock/OCMock.h>
17
#import <XCTest/XCTest.h>
18
#include <bsm/libbsm.h>
19
#include <gmock/gmock.h>
20
#include <gtest/gtest.h>
21
#include <mach/mach_time.h>
22

23
#include <memory>
24

25
#import "Source/common/SNTCommonEnums.h"
26
#import "Source/common/SNTConfigurator.h"
27
#import "Source/common/SystemResources.h"
28
#include "Source/common/TestUtils.h"
29
#include "Source/santad/DataLayer/WatchItemPolicy.h"
30
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
31
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
32
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
33
#include "Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h"
34
#import "Source/santad/EventProviders/SNTEndpointSecurityClient.h"
35
#include "Source/santad/Metrics.h"
36

37
using santa::santad::Processor;
38
using santa::santad::data_layer::WatchItemPathType;
39
using santa::santad::event_providers::endpoint_security::Client;
40
using santa::santad::event_providers::endpoint_security::EnrichedClose;
41
using santa::santad::event_providers::endpoint_security::EnrichedFile;
42
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
43
using santa::santad::event_providers::endpoint_security::EnrichedProcess;
44
using santa::santad::event_providers::endpoint_security::Message;
45

46
@interface SNTEndpointSecurityClient (Testing)
47
- (void)establishClientOrDie;
48
- (bool)muteSelf;
49
- (NSString *)errorMessageForNewClientResult:(es_new_client_result_t)result;
50
- (void)handleMessage:(Message &&)esMsg
51
   recordEventMetrics:(void (^)(santa::santad::EventDisposition disposition))recordEventMetrics;
52
- (BOOL)shouldHandleMessage:(const Message &)esMsg;
53
- (int64_t)computeBudgetForDeadline:(uint64_t)deadline currentTime:(uint64_t)currentTime;
54

55
@property(nonatomic) double defaultBudget;
56
@property(nonatomic) int64_t minAllowedHeadroom;
57
@property(nonatomic) int64_t maxAllowedHeadroom;
58
@end
59

60
@interface SNTEndpointSecurityClientTest : XCTestCase
61
@end
62

63
@implementation SNTEndpointSecurityClientTest
64

65
- (void)testEstablishClientOrDie {
1✔
66
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
67

68
  EXPECT_CALL(*mockESApi, MuteProcess).WillOnce(testing::Return(true));
1✔
69

70
  EXPECT_CALL(*mockESApi, NewClient)
1✔
71
    .WillOnce(testing::Return(Client()))
1✔
72
    .WillOnce(testing::Return(Client(nullptr, ES_NEW_CLIENT_RESULT_SUCCESS)));
1✔
73

74
  SNTEndpointSecurityClient *client =
1✔
75
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
76
                                             metrics:nullptr
1✔
77
                                           processor:Processor::kUnknown];
1✔
78

79
  // First time throws because mock triggers failed connection
80
  // Second time succeeds
81
  XCTAssertThrows([client establishClientOrDie]);
1!
82
  XCTAssertNoThrow([client establishClientOrDie]);
1!
83

84
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
85
}
1✔
86

87
- (void)testErrorMessageForNewClientResult {
1✔
88
  std::map<es_new_client_result_t, std::string> resultMessagePairs{
1✔
89
    {ES_NEW_CLIENT_RESULT_SUCCESS, ""},
1✔
90
    {ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED, "Full-disk access not granted"},
1✔
91
    {ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED, "Not entitled"},
1✔
92
    {ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED, "Not running as root"},
1✔
93
    {ES_NEW_CLIENT_RESULT_ERR_INVALID_ARGUMENT, "Invalid argument"},
1✔
94
    {ES_NEW_CLIENT_RESULT_ERR_INTERNAL, "Internal error"},
1✔
95
    {ES_NEW_CLIENT_RESULT_ERR_TOO_MANY_CLIENTS, "Too many simultaneous clients"},
1✔
96
    {(es_new_client_result_t)123, "Unknown error"},
1✔
97
  };
1✔
98

99
  SNTEndpointSecurityClient *client =
1✔
100
    [[SNTEndpointSecurityClient alloc] initWithESAPI:nullptr
1✔
101
                                             metrics:nullptr
1✔
102
                                           processor:Processor::kUnknown];
1✔
103

104
  for (const auto &kv : resultMessagePairs) {
8✔
105
    NSString *message = [client errorMessageForNewClientResult:kv.first];
8✔
106
    XCTAssertEqual(0, strcmp([(message ?: @"") UTF8String], kv.second.c_str()));
8!
107
  }
8✔
108
}
1✔
109

110
- (void)testHandleMessage {
1✔
111
  es_message_t esMsg;
1✔
112

113
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
114
  mockESApi->SetExpectationsRetainReleaseMessage();
1✔
115

116
  SNTEndpointSecurityClient *client =
1✔
117
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
118
                                             metrics:nullptr
1✔
119
                                           processor:Processor::kUnknown];
1✔
120

121
  {
1✔
122
    XCTAssertThrows([client handleMessage:Message(mockESApi, &esMsg) recordEventMetrics:nil]);
1!
123
  }
1✔
124

125
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
126
}
1✔
127

128
- (void)testHandleMessageWithClient {
1✔
129
  es_file_t file = MakeESFile("foo");
1✔
130
  es_process_t proc = MakeESProcess(&file);
1✔
131
  es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_NOTIFY_FORK, &proc);
1✔
132

133
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
134
  mockESApi->SetExpectationsRetainReleaseMessage();
1✔
135

136
  // Have subscribe fail the first time, meaning clear cache only called once.
137
  EXPECT_CALL(*mockESApi, RespondAuthResult(testing::_, testing::_, ES_AUTH_RESULT_ALLOW, true))
1✔
138
    .WillOnce(testing::Return(true));
1✔
139

140
  id mockConfigurator = OCMStrictClassMock([SNTConfigurator class]);
1✔
141
  OCMStub([mockConfigurator configurator]).andReturn(mockConfigurator);
1!
142

143
  SNTEndpointSecurityClient *client =
1✔
144
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
145
                                             metrics:nullptr
1✔
146
                                           processor:Processor::kUnknown];
1✔
147

148
  {
1✔
149
    Message msg(mockESApi, &esMsg);
1✔
150

151
    // Is ES client, but don't ignore others == Should Handle
152
    OCMExpect([mockConfigurator ignoreOtherEndpointSecurityClients]).andReturn(NO);
1!
153
    esMsg.process->is_es_client = true;
1✔
154
    XCTAssertTrue([client shouldHandleMessage:msg]);
1!
155

156
    // Not ES client, but ignore others == Should Handle
157
    // Don't setup configurator mock since it won't be called when `is_es_client` is false
158
    esMsg.process->is_es_client = false;
1✔
159
    XCTAssertTrue([client shouldHandleMessage:msg]);
1!
160

161
    // Is ES client, don't ignore others, and non-AUTH == Don't Handle
162
    OCMExpect([mockConfigurator ignoreOtherEndpointSecurityClients]).andReturn(YES);
1!
163
    esMsg.process->is_es_client = true;
1✔
164
    XCTAssertFalse([client shouldHandleMessage:msg]);
1!
165

166
    // Is ES client, don't ignore others, and AUTH == Respond and Don't Handle
167
    OCMExpect([mockConfigurator ignoreOtherEndpointSecurityClients]).andReturn(YES);
1!
168
    esMsg.process->is_es_client = true;
1✔
169
    esMsg.action_type = ES_ACTION_TYPE_AUTH;
1✔
170
    XCTAssertFalse([client shouldHandleMessage:msg]);
1!
171
  }
1✔
172

173
  XCTAssertTrue(OCMVerifyAll(mockConfigurator));
1!
174
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
175

176
  [mockConfigurator stopMocking];
1✔
177
}
1✔
178

179
- (void)testPopulateAuditTokenSelf {
1✔
180
  audit_token_t myAuditToken;
1✔
181

182
  [SNTEndpointSecurityClient populateAuditTokenSelf:&myAuditToken];
1✔
183

184
  XCTAssertEqual(audit_token_to_pid(myAuditToken), getpid());
1!
185
  XCTAssertNotEqual(audit_token_to_pidversion(myAuditToken), 0);
1!
186
}
1✔
187

188
- (void)testMuteSelf {
1✔
189
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
190
  SNTEndpointSecurityClient *client =
1✔
191
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
192
                                             metrics:nullptr
1✔
193
                                           processor:Processor::kUnknown];
1✔
194

195
  EXPECT_CALL(*mockESApi, MuteProcess)
1✔
196
    .WillOnce(testing::Return(true))
1✔
197
    .WillOnce(testing::Return(false));
1✔
198

199
  XCTAssertTrue([client muteSelf]);
1!
200
  XCTAssertFalse([client muteSelf]);
1!
201

202
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
203
}
1✔
204

205
- (void)testClearCache {
1✔
206
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
207
  SNTEndpointSecurityClient *client =
1✔
208
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
209
                                             metrics:nullptr
1✔
210
                                           processor:Processor::kUnknown];
1✔
211

212
  // Test the underlying clear cache impl returning both true and false
213
  EXPECT_CALL(*mockESApi, ClearCache)
1✔
214
    .WillOnce(testing::Return(true))
1✔
215
    .WillOnce(testing::Return(false));
1✔
216

217
  XCTAssertTrue([client clearCache]);
1!
218
  XCTAssertFalse([client clearCache]);
1!
219

220
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
221
}
1✔
222

223
- (void)testSubscribe {
1✔
224
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
225
  SNTEndpointSecurityClient *client =
1✔
226
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
227
                                             metrics:nullptr
1✔
228
                                           processor:Processor::kUnknown];
1✔
229

230
  std::set<es_event_type_t> events = {
1✔
231
    ES_EVENT_TYPE_NOTIFY_CLOSE,
1✔
232
    ES_EVENT_TYPE_NOTIFY_EXIT,
1✔
233
  };
1✔
234

235
  // Test the underlying subscribe impl returning both true and false
236
  EXPECT_CALL(*mockESApi, Subscribe(testing::_, events))
1✔
237
    .WillOnce(testing::Return(true))
1✔
238
    .WillOnce(testing::Return(false));
1✔
239

240
  XCTAssertTrue([client subscribe:events]);
1!
241
  XCTAssertFalse([client subscribe:events]);
1!
242

243
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
244
}
1✔
245

246
- (void)testSubscribeAndClearCache {
1✔
247
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
248
  SNTEndpointSecurityClient *client =
1✔
249
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
250
                                             metrics:nullptr
1✔
251
                                           processor:Processor::kUnknown];
1✔
252

253
  // Have subscribe fail the first time, meaning clear cache only called once.
254
  EXPECT_CALL(*mockESApi, ClearCache)
1✔
255
    .After(EXPECT_CALL(*mockESApi, Subscribe)
1✔
256
             .WillOnce(testing::Return(false))
1✔
257
             .WillOnce(testing::Return(true)))
1✔
258
    .WillOnce(testing::Return(true));
1✔
259

260
  XCTAssertFalse([client subscribeAndClearCache:{}]);
1!
261
  XCTAssertTrue([client subscribeAndClearCache:{}]);
1!
262

263
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
264
}
1✔
265

266
- (void)testUnsubscribeAll {
1✔
267
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
268
  SNTEndpointSecurityClient *client =
1✔
269
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
270
                                             metrics:nullptr
1✔
271
                                           processor:Processor::kUnknown];
1✔
272

273
  // Test the underlying unsubscribe all impl returning both true and false
274
  EXPECT_CALL(*mockESApi, UnsubscribeAll)
1✔
275
    .WillOnce(testing::Return(true))
1✔
276
    .WillOnce(testing::Return(false));
1✔
277

278
  XCTAssertTrue([client unsubscribeAll]);
1!
279
  XCTAssertFalse([client unsubscribeAll]);
1!
280

281
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
282
}
1✔
283

284
- (void)testUnmuteAllTargetPaths {
1✔
285
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
286
  SNTEndpointSecurityClient *client =
1✔
287
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
288
                                             metrics:nullptr
1✔
289
                                           processor:Processor::kUnknown];
1✔
290

291
  // Test the underlying unmute impl returning both true and false
292
  EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths)
1✔
293
    .WillOnce(testing::Return(true))
1✔
294
    .WillOnce(testing::Return(false));
1✔
295

296
  XCTAssertTrue([client unmuteAllTargetPaths]);
1!
297
  XCTAssertFalse([client unmuteAllTargetPaths]);
1!
298

299
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
300
}
1✔
301

302
- (void)testEnableTargetPathWatching {
1✔
303
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
304
  SNTEndpointSecurityClient *client =
1✔
305
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
306
                                             metrics:nullptr
1✔
307
                                           processor:Processor::kUnknown];
1✔
308

309
  // UnmuteAllTargetPaths is always attempted.
310
  EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).Times(2).WillRepeatedly(testing::Return(true));
1✔
311

312
  // Test the underlying invert nute impl returning both true and false
313
  EXPECT_CALL(*mockESApi, InvertTargetPathMuting)
1✔
314
    .WillOnce(testing::Return(true))
1✔
315
    .WillOnce(testing::Return(false));
1✔
316

317
  XCTAssertTrue([client enableTargetPathWatching]);
1!
318
  XCTAssertFalse([client enableTargetPathWatching]);
1!
319

320
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
321
}
1✔
322

323
- (void)testMuteTargetPaths {
1✔
324
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
325
  SNTEndpointSecurityClient *client =
1✔
326
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
327
                                             metrics:nullptr
1✔
328
                                           processor:Processor::kUnknown];
1✔
329

330
  // Ensure all paths are attempted to be muted even if some fail.
331
  // Ensure if any paths fail the overall result is false.
332
  EXPECT_CALL(*mockESApi,
1✔
333
              MuteTargetPath(testing::_, std::string_view("a"), WatchItemPathType::kLiteral))
1✔
334
    .WillOnce(testing::Return(true));
1✔
335
  EXPECT_CALL(*mockESApi,
1✔
336
              MuteTargetPath(testing::_, std::string_view("b"), WatchItemPathType::kLiteral))
1✔
337
    .WillOnce(testing::Return(false));
1✔
338
  EXPECT_CALL(*mockESApi,
1✔
339
              MuteTargetPath(testing::_, std::string_view("c"), WatchItemPathType::kPrefix))
1✔
340
    .WillOnce(testing::Return(true));
1✔
341

342
  std::vector<std::pair<std::string, WatchItemPathType>> paths = {
1✔
343
    {"a", WatchItemPathType::kLiteral},
1✔
344
    {"b", WatchItemPathType::kLiteral},
1✔
345
    {"c", WatchItemPathType::kPrefix},
1✔
346
  };
1✔
347

348
  XCTAssertFalse([client muteTargetPaths:paths]);
1!
349

350
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
351
}
1✔
352

353
- (void)testUnmuteTargetPaths {
1✔
354
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
355
  SNTEndpointSecurityClient *client =
1✔
356
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
357
                                             metrics:nullptr
1✔
358
                                           processor:Processor::kUnknown];
1✔
359

360
  // Ensure all paths are attempted to be unmuted even if some fail.
361
  // Ensure if any paths fail the overall result is false.
362
  EXPECT_CALL(*mockESApi,
1✔
363
              UnmuteTargetPath(testing::_, std::string_view("a"), WatchItemPathType::kLiteral))
1✔
364
    .WillOnce(testing::Return(true));
1✔
365
  EXPECT_CALL(*mockESApi,
1✔
366
              UnmuteTargetPath(testing::_, std::string_view("b"), WatchItemPathType::kLiteral))
1✔
367
    .WillOnce(testing::Return(false));
1✔
368
  EXPECT_CALL(*mockESApi,
1✔
369
              UnmuteTargetPath(testing::_, std::string_view("c"), WatchItemPathType::kPrefix))
1✔
370
    .WillOnce(testing::Return(true));
1✔
371

372
  std::vector<std::pair<std::string, WatchItemPathType>> paths = {
1✔
373
    {"a", WatchItemPathType::kLiteral},
1✔
374
    {"b", WatchItemPathType::kLiteral},
1✔
375
    {"c", WatchItemPathType::kPrefix},
1✔
376
  };
1✔
377

378
  XCTAssertFalse([client unmuteTargetPaths:paths]);
1!
379

380
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
381
}
1✔
382

383
- (void)testRespondToMessageWithAuthResultCacheable {
1✔
384
  es_message_t esMsg;
1✔
385
  esMsg.event_type = ES_EVENT_TYPE_AUTH_EXEC;
1✔
386

387
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
388
  mockESApi->SetExpectationsRetainReleaseMessage();
1✔
389

390
  es_auth_result_t result = ES_AUTH_RESULT_DENY;
1✔
391
  bool cacheable = true;
1✔
392

393
  // Have subscribe fail the first time, meaning clear cache only called once.
394
  EXPECT_CALL(*mockESApi, RespondAuthResult(testing::_, testing::_, result, cacheable))
1✔
395
    .WillOnce(testing::Return(true));
1✔
396

397
  SNTEndpointSecurityClient *client =
1✔
398
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
399
                                             metrics:nullptr
1✔
400
                                           processor:Processor::kUnknown];
1✔
401

402
  {
1✔
403
    Message msg(mockESApi, &esMsg);
1✔
404
    XCTAssertTrue([client respondToMessage:msg withAuthResult:result cacheable:cacheable]);
1!
405
  }
1✔
406

407
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
408
}
1✔
409

410
- (void)testProcessEnrichedMessageHandler {
1✔
411
  es_message_t esMsg;
1✔
412
  dispatch_semaphore_t sema = dispatch_semaphore_create(0);
1✔
413
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
414

415
  mockESApi->SetExpectationsRetainReleaseMessage();
1✔
416

417
  SNTEndpointSecurityClient *client =
1✔
418
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
419
                                             metrics:nullptr
1✔
420
                                           processor:Processor::kUnknown];
1✔
421
  {
1✔
422
    auto enrichedMsg = std::make_unique<EnrichedMessage>(EnrichedClose(
1✔
423
      Message(mockESApi, &esMsg),
1✔
424
      EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt,
1✔
425
                      EnrichedFile(std::nullopt, std::nullopt, std::nullopt), std::nullopt),
1✔
426
      EnrichedFile(std::nullopt, std::nullopt, std::nullopt)));
1✔
427

428
    [client processEnrichedMessage:std::move(enrichedMsg)
1✔
429
                           handler:^(std::unique_ptr<EnrichedMessage> msg) {
1✔
430
                             // reset the shared_ptr to drop the held message.
431
                             // This is a workaround for a TSAN only false positive
432
                             // which happens if we switch back to the sem wait
433
                             // after signaling, but _before_ the implicit release
434
                             // of msg. In that case, the mock verify and the
435
                             // call of the mock's Release method can data race.
436
                             msg.reset();
1✔
437
                             dispatch_semaphore_signal(sema);
1✔
438
                           }];
1✔
439

440
    XCTAssertEqual(
1!
441
      0, dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
1✔
442
      "Handler block not called within expected time window");
1✔
443
  }
1✔
444

445
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
446
}
1✔
447

448
- (void)testIsProtectedPath {
1✔
449
  XCTAssertTrue([SNTEndpointSecurityClient isProtectedPath:"/private/var/db/santa/rules.db"]);
1!
450
  XCTAssertTrue([SNTEndpointSecurityClient isProtectedPath:"/private/var/db/santa/events.db"]);
1!
451

452
  XCTAssertFalse([SNTEndpointSecurityClient isProtectedPath:"/not/a/db/path"]);
1!
453
}
1✔
454

455
- (void)testProcessMessageHandlerBadEventType {
1✔
456
  es_file_t proc_file = MakeESFile("foo");
1✔
457
  es_process_t proc = MakeESProcess(&proc_file);
1✔
458
  es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_NOTIFY_EXIT, &proc);
1✔
459

460
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
461
  mockESApi->SetExpectationsRetainReleaseMessage();
1✔
462

463
  SNTEndpointSecurityClient *client =
1✔
464
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
465
                                             metrics:nullptr
1✔
466
                                           processor:Processor::kUnknown];
1✔
467

468
  {
1✔
469
    XCTAssertThrows([client processMessage:Message(mockESApi, &esMsg)
1!
470
                                   handler:^(const Message &msg){
1✔
471
                                   }]);
1✔
472
  }
1✔
473

474
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
475
}
1✔
476

477
// Note: This test triggers a leak warning on the mock object, however it is
478
// benign. The dispatch block to handle deadline expiration in
479
// `processMessage:handler:` will retain the mock object an extra time.
480
// But since this test sets a long deadline in order to ensure the handler block
481
// runs first, the deadline handler block will not have finished executing by
482
// the time the test exits, making GMock think the object was leaked.
483
- (void)testProcessMessageHandler {
1✔
484
  es_file_t proc_file = MakeESFile("foo");
1✔
485
  es_process_t proc = MakeESProcess(&proc_file);
1✔
486
  es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_AUTH_OPEN, &proc, ActionType::Auth,
1✔
487
                                     45 * 1000);  // Long deadline to not hit
1✔
488

489
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
490
  mockESApi->SetExpectationsRetainReleaseMessage();
1✔
491

492
  dispatch_semaphore_t sema = dispatch_semaphore_create(0);
1✔
493

494
  SNTEndpointSecurityClient *client =
1✔
495
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
496
                                             metrics:nullptr
1✔
497
                                           processor:Processor::kUnknown];
1✔
498

499
  {
1✔
500
    XCTAssertNoThrow([client processMessage:Message(mockESApi, &esMsg)
1!
501
                                    handler:^(const Message &msg) {
×
UNCOV
502
                                      dispatch_semaphore_signal(sema);
×
UNCOV
503
                                    }]);
×
504

505
    XCTAssertEqual(
1!
506
      0, dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)),
1✔
507
      "Handler block not called within expected time window");
1✔
508
  }
1✔
509

510
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
511
}
1✔
512

513
- (void)testComputeBudgetForDeadlineCurrentTime {
1✔
514
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
1✔
515

516
  SNTEndpointSecurityClient *client =
1✔
517
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
1✔
518
                                             metrics:nullptr
1✔
519
                                           processor:Processor::kUnknown];
1✔
520

521
  // The test uses crafted values to make even numbers. Ensure the client has
522
  // expected values for these properties so the test can fail early if not.
523
  XCTAssertEqual(client.defaultBudget, 0.8);
1!
524
  XCTAssertEqual(client.minAllowedHeadroom, 1 * NSEC_PER_SEC);
1!
525
  XCTAssertEqual(client.maxAllowedHeadroom, 5 * NSEC_PER_SEC);
1!
526

527
  std::map<uint64_t, int64_t> deadlineMillisToBudgetMillis{
1✔
528
    // Further out deadlines clamp processing budget to maxAllowedHeadroom
529
    {45000, 40000},
1✔
530

531
    // Closer deadlines allow a set percentage processing budget
532
    {15000, 12000},
1✔
533

534
    // Near deadlines clamp processing budget to minAllowedHeadroom
535
    {3500, 2500}};
1✔
536

537
  uint64_t curTime = mach_absolute_time();
1✔
538

539
  for (const auto [deadlineMS, budgetMS] : deadlineMillisToBudgetMillis) {
3✔
540
    int64_t got =
3✔
541
      [client computeBudgetForDeadline:AddNanosecondsToMachTime(deadlineMS * NSEC_PER_MSEC, curTime)
3✔
542
                           currentTime:curTime];
3✔
543

544
    // Add 100us, then clip to ms to account for non-exact values due to timebase division
545
    got = (int64_t)((double)(got + (100 * NSEC_PER_USEC)) / (double)NSEC_PER_MSEC);
3✔
546

547
    XCTAssertEqual(got, budgetMS);
3!
548
  }
3✔
549

550
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
1!
551
}
1✔
552

553
- (void)checkDeadlineExpiredFailClosed:(BOOL)shouldFailClosed {
2✔
554
  // Set a es_message_t deadline of 750ms
555
  // Set a deadline leeway in the `SNTEndpointSecurityClient` of 500ms
556
  // Mock `RespondFlagsResult` which is called from the deadline handler
557
  // Signal the semaphore from the mock
558
  // Wait a few seconds for the semaphore (should take ~250ms)
559
  //
560
  // Two semaphotes are used:
561
  // 1. deadlineSema - used to wait in the handler block until the deadline
562
  //    block has a chance to execute
563
  // 2. controlSema - used to block control flow in the test until the
564
  //    deadlineSema is signaled (or a timeout waiting on deadlineSema)
565
  es_file_t proc_file = MakeESFile("foo");
2✔
566
  es_process_t proc = MakeESProcess(&proc_file);
2✔
567
  es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_AUTH_EXEC, &proc, ActionType::Auth,
2✔
568
                                     750);  // 750ms timeout
2✔
569

570
  auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
2✔
571
  mockESApi->SetExpectationsRetainReleaseMessage();
2✔
572

573
  dispatch_semaphore_t deadlineSema = dispatch_semaphore_create(0);
2✔
574
  dispatch_semaphore_t controlSema = dispatch_semaphore_create(0);
2✔
575

576
  es_auth_result_t wantAuthResult = shouldFailClosed ? ES_AUTH_RESULT_DENY : ES_AUTH_RESULT_ALLOW;
2✔
577
  EXPECT_CALL(*mockESApi, RespondAuthResult(testing::_, testing::_, wantAuthResult, false))
2✔
578
    .WillOnce(testing::InvokeWithoutArgs(^() {
2✔
579
      // Signal deadlineSema to let the handler block continue execution
580
      dispatch_semaphore_signal(deadlineSema);
2✔
581
      return true;
2✔
582
    }));
2✔
583

584
  id mockConfigurator = OCMClassMock([SNTConfigurator class]);
2✔
585
  OCMStub([mockConfigurator configurator]).andReturn(mockConfigurator);
2!
586

587
  OCMExpect([mockConfigurator failClosed]).andReturn(shouldFailClosed);
2!
588

589
  SNTEndpointSecurityClient *client =
2✔
590
    [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi
2✔
591
                                             metrics:nullptr
2✔
592
                                           processor:Processor::kUnknown];
2✔
593

594
  // Set min/max headroom the same to clamp the value for this test
595
  client.minAllowedHeadroom = 500 * NSEC_PER_MSEC;
2✔
596
  client.maxAllowedHeadroom = 500 * NSEC_PER_MSEC;
2✔
597

598
  {
2✔
599
    __block long result;
2✔
600
    XCTAssertNoThrow([client processMessage:Message(mockESApi, &esMsg)
2!
601
                                    handler:^(const Message &msg) {
×
602
                                      result = dispatch_semaphore_wait(
×
UNCOV
603
                                        deadlineSema,
×
UNCOV
604
                                        dispatch_time(DISPATCH_TIME_NOW, 4 * NSEC_PER_SEC));
×
605

606
                                      // Once done waiting on deadlineSema, trigger controlSema to
607
                                      // continue test
UNCOV
608
                                      dispatch_semaphore_signal(controlSema);
×
UNCOV
609
                                    }]);
×
610

611
    XCTAssertEqual(
2!
UNCOV
612
      0, dispatch_semaphore_wait(controlSema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
×
UNCOV
613
      "Control sema not signaled within expected time window");
×
614

615
    XCTAssertEqual(result, 0);
2!
616
  }
2✔
617

618
  // Allow some time for the threads in `processMessage:handler:` to finish.
619
  // It isn't critical that they do, but if the dispatch blocks don't complete
620
  // we may get warnings from GMock about calls to ReleaseMessage after
621
  // verifying and clearing. Sleep a little bit here to reduce chances of
622
  // seeing the warning (but still possible)
623
  SleepMS(100);
2✔
624

625
  XCTAssertTrue(OCMVerifyAll(mockConfigurator));
2!
626
  XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
2!
627

628
  [mockConfigurator stopMocking];
2✔
629
}
2✔
630

631
- (void)testDeadlineExpiredFailClosed {
1✔
632
  [self checkDeadlineExpiredFailClosed:YES];
1✔
633
}
1✔
634

635
- (void)testDeadlineExpiredFailOpen {
1✔
636
  [self checkDeadlineExpiredFailClosed:NO];
1✔
637
}
1✔
638

639
@end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2024 Coveralls, Inc