Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Block Management/HostFileBlocker.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
- (void)appendExistingBlockWithRuleForDomain:(NSString*)domainName;

- (BOOL)containsSelfControlBlock;
- (BOOL)containsExpectedRulesForBlocklist:(NSArray<NSString*>*)blocklist;

- (void)removeSelfControlBlock;

Expand Down
70 changes: 70 additions & 0 deletions Block Management/HostFileBlocker.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#import "HostFileBlocker.h"
#import "SCBlockEntry.h"
#import "NSString+IPAddress.h"

NSString* const kHostFileBlockerPath = @"/etc/hosts";
NSString* const kHostFileBlockerSelfControlHeader = @"# BEGIN SELFCONTROL BLOCK";
Expand Down Expand Up @@ -182,6 +184,74 @@ - (BOOL)containsSelfControlBlock {
return ret;
}

- (NSSet<NSString*>*)selfControlBlockRuleSet {
NSMutableSet<NSString*>* ruleSet = [NSMutableSet set];

NSRange startRange = [newFileContents rangeOfString: kHostFileBlockerSelfControlHeader];
NSRange endRange = [newFileContents rangeOfString: kHostFileBlockerSelfControlFooter];
if (startRange.location == NSNotFound || endRange.location == NSNotFound || endRange.location <= startRange.location) {
return ruleSet;
}

NSUInteger blockStart = startRange.location + startRange.length;
NSRange blockRange = NSMakeRange(blockStart, endRange.location - blockStart);
NSString* blockContents = [newFileContents substringWithRange: blockRange];
NSArray<NSString*>* lines = [blockContents componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]];
NSCharacterSet* whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];

for (NSString* line in lines) {
NSString* trimmedLine = [line stringByTrimmingCharactersInSet: whitespace];
if (trimmedLine.length == 0 || [trimmedLine hasPrefix: @"#"]) {
continue;
}

NSArray<NSString*>* parts = [trimmedLine componentsSeparatedByCharactersInSet: whitespace];
NSMutableArray<NSString*>* nonEmptyParts = [NSMutableArray array];
for (NSString* part in parts) {
if (part.length > 0) {
[nonEmptyParts addObject: part];
}
}
if (nonEmptyParts.count < 2) {
continue;
}

NSString* address = nonEmptyParts[0];
for (NSUInteger i = 1; i < nonEmptyParts.count; i++) {
[ruleSet addObject: [NSString stringWithFormat: @"%@ %@", address, nonEmptyParts[i]]];
}
}

return ruleSet;
}

- (BOOL)containsExpectedRulesForBlocklist:(NSArray<NSString*>*)blocklist {
[strLock lock];

BOOL ret = ([newFileContents rangeOfString: kHostFileBlockerSelfControlHeader].location != NSNotFound);
if (ret) {
NSSet<NSString*>* blockRuleSet = [self selfControlBlockRuleSet];

for (NSString* blocklistString in blocklist) {
SCBlockEntry* entry = [SCBlockEntry entryFromString: blocklistString];
if (entry == nil || entry.port || [entry.hostname isEqualToString: @"*"] || [entry.hostname isValidIPAddress]) {
continue;
}

NSString* ipv4Rule = [NSString stringWithFormat: @"0.0.0.0 %@", entry.hostname];
NSString* ipv6Rule = [NSString stringWithFormat: @":: %@", entry.hostname];
if (![blockRuleSet containsObject: ipv4Rule] || ![blockRuleSet containsObject: ipv6Rule]) {
NSLog(@"INFO: SelfControl hosts block is missing expected rule(s) for %@", entry.hostname);
ret = NO;
break;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

[strLock unlock];
return ret;
}

- (void)removeSelfControlBlock {
if(![self containsSelfControlBlock])
return;
Expand Down
8 changes: 8 additions & 0 deletions Block Management/HostFileBlockerSet.m
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ - (BOOL)containsSelfControlBlock {
return ret;
}

- (BOOL)containsExpectedRulesForBlocklist:(NSArray<NSString*>*)blocklist {
BOOL ret = YES;
for (HostFileBlocker* blocker in self.blockers) {
ret = ret && [blocker containsExpectedRulesForBlocklist: blocklist];
}
return ret;
}

- (void)removeSelfControlBlock {
for (HostFileBlocker* blocker in self.blockers) {
[blocker removeSelfControlBlock];
Expand Down
8 changes: 4 additions & 4 deletions Common/SCSettings.m
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,10 @@ - (void)writeSettingsWithCompletion:(nullable void(^)(NSError* _Nullable))comple
NSError* chmodErr;
BOOL chmodSuccessful = [[NSFileManager defaultManager]
setAttributes: @{
NSFileOwnerAccountID: [NSNumber numberWithUnsignedLong: 0],
NSFileGroupOwnerAccountID: [NSNumber numberWithUnsignedLong: 0],
NSFilePosixPermissions: [NSNumber numberWithShort: 0755]
}
NSFileOwnerAccountID: [NSNumber numberWithUnsignedLong: 0],
NSFileGroupOwnerAccountID: [NSNumber numberWithUnsignedLong: 0],
NSFilePosixPermissions: [NSNumber numberWithShort: 0600]
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
ofItemAtPath: SCSettings.securedSettingsFilePath
error: &chmodErr];

Expand Down
3 changes: 2 additions & 1 deletion Daemon/SCDaemonBlockMethods.m
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ + (void)checkBlockIntegrity {
SCSettings* settings = [SCSettings sharedSettings];
PacketFilter* pf = [[PacketFilter alloc] init];
HostFileBlockerSet* hostFileBlockerSet = [[HostFileBlockerSet alloc] init];
if(![pf containsSelfControlBlock] || (![settings boolForKey: @"ActiveBlockAsWhitelist"] && ![hostFileBlockerSet.defaultBlocker containsSelfControlBlock])) {
BOOL hostsBlockIsValid = [settings boolForKey: @"ActiveBlockAsWhitelist"] || [hostFileBlockerSet.defaultBlocker containsExpectedRulesForBlocklist: [settings valueForKey: @"ActiveBlocklist"]];
if(![pf containsSelfControlBlock] || !hostsBlockIsValid) {
NSLog(@"INFO: Block is missing in PF or hosts, re-adding...");
// The firewall is missing at least the block header. Let's clear everything
// before we re-add to make sure everything goes smoothly.
Expand Down
32 changes: 32 additions & 0 deletions SelfControlTests/SCUtilityTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "SCSentry.h"
#import "SCErr.h"
#import "SCSettings.h"
#import "HostFileBlocker.h"

@interface SCUtilityTests : XCTestCase

Expand Down Expand Up @@ -164,6 +165,37 @@ - (void) testModernBlockDetection {
XCTAssert([SCBlockUtilities currentBlockIsExpired]);
}

- (NSURL*)temporaryHostsFileURLWithName:(NSString*)name contents:(NSString*)contents {
NSURL* url = [[NSURL fileURLWithPath: NSTemporaryDirectory()] URLByAppendingPathComponent: name];
[contents writeToURL: url atomically: YES encoding: NSUTF8StringEncoding error: nil];
return url;
}

- (void) testHostBlockIntegrityRequiresExpectedRules {
NSURL* hostsURL = [self temporaryHostsFileURLWithName: @"selfcontrol-empty-hosts-block-test"
contents: @"127.0.0.1 localhost\n\n# BEGIN SELFCONTROL BLOCK\n# END SELFCONTROL BLOCK\n"];
HostFileBlocker* blocker = [[HostFileBlocker alloc] initWithPath: hostsURL.path];

XCTAssert([blocker containsSelfControlBlock]);
XCTAssertFalse([blocker containsExpectedRulesForBlocklist: @[ @"youtube.com" ]]);

[[NSFileManager defaultManager] removeItemAtURL: hostsURL error: nil];
}

- (void) testHostBlockIntegrityAcceptsExpectedRules {
NSURL* hostsURL = [self temporaryHostsFileURLWithName: @"selfcontrol-valid-hosts-block-test"
contents: @"127.0.0.1 localhost\n"];
HostFileBlocker* blocker = [[HostFileBlocker alloc] initWithPath: hostsURL.path];
[blocker addSelfControlBlockHeader];
[blocker addRuleBlockingDomain: @"youtube.com"];
[blocker addSelfControlBlockFooter];

XCTAssert([blocker containsExpectedRulesForBlocklist: @[ @"youtube.com" ]]);
XCTAssertFalse([blocker containsExpectedRulesForBlocklist: @[ @"youtube.com", @"www.youtube.com" ]]);

[[NSFileManager defaultManager] removeItemAtURL: hostsURL error: nil];
}

- (void) testLegacyBlockDetection {
// test blockIsRunningInLegacyDictionary
// the block is "running" even if it's expired, since it hasn't been removed
Expand Down