For file/line breakpoints, create secure bookmarks for maintaining access.
authorRobert Sesek <rsesek@bluestatic.org>
Sat, 7 Dec 2019 21:35:07 +0000 (16:35 -0500)
committerRobert Sesek <rsesek@bluestatic.org>
Sat, 7 Dec 2019 21:35:07 +0000 (16:35 -0500)
Under the app sandbox, the program only has access to user-selected
files, unless it creates bookmarks for them. Each file bookmark (even
for the same file, for simplicity) now gets a bookmark when it is
created.

https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW16

MacGDBp.entitlements
Source/BSLineNumberRulerView.mm
Source/Breakpoint.h
Source/Breakpoint.m
Source/BreakpointController.m
Source/BreakpointManager.m

index 7a2230dc331daf312fe2ca6bcc81540bd5baa215..0d6bac6545b8e6146ccf83d94d36e290a8d5f563 100644 (file)
@@ -4,6 +4,10 @@
 <dict>
        <key>com.apple.security.app-sandbox</key>
        <true/>
+       <key>com.apple.security.files.bookmarks.app-scope</key>
+       <true/>
+       <key>com.apple.security.files.user-selected.read-only</key>
+       <true/>
        <key>com.apple.security.network.client</key>
        <true/>
        <key>com.apple.security.network.server</key>
index 7b1d253d1e2b4a06420cfcedd7356f60b6e53fff..e912b0201a4711a9b659bed13e867975997f82c8 100644 (file)
@@ -141,11 +141,15 @@ const CGFloat kRulerRightPadding = 2.5;
 {
   [self computeLineIndex];
 
-  // Determine the width of the ruler based on the line count.
-  NSUInteger lastElement = lineIndex_.back() + 1;
-  NSAttributedString* lastElementString = [self attributedStringForLineNumber:lastElement];
-  NSSize boundingSize = [lastElementString size];
-  [self setRuleThickness:std::max(kDefaultWidth, boundingSize.width)];
+  if (lineIndex_.empty()) {
+    [self setRuleThickness:kDefaultWidth];
+  } else {
+    // Determine the width of the ruler based on the line count.
+    NSUInteger lastElement = lineIndex_.back() + 1;
+    NSAttributedString* lastElementString = [self attributedStringForLineNumber:lastElement];
+    NSSize boundingSize = [lastElementString size];
+    [self setRuleThickness:std::max(kDefaultWidth, boundingSize.width)];
+  }
 
   [self setNeedsDisplay:YES];
 }
@@ -209,6 +213,9 @@ const CGFloat kRulerRightPadding = 2.5;
     index = NSMaxRange([text lineRangeForRange:NSMakeRange(index, 0)]);
   }
 
+  if (lineIndex_.empty())
+    return;
+
   NSUInteger lineEnd, contentEnd;
   [text getLineStart:NULL
                  end:&lineEnd
index fe2e217190767e5f697d52322d66313fdb089721..4a534978f35a5ff2843a2eecec2ca96a29393785 100644 (file)
@@ -35,6 +35,7 @@ extern NSString* const kBreakpointTypeFunctionEntry;
 // kBreakpointTypeFile:
 @property (readonly) NSString* file;
 @property (readonly) unsigned long line;
+@property (copy) NSData* secureBookmark;
 
 // kBreakpointTypeFunctionEntry:
 @property (readonly) NSString* functionName;
@@ -50,4 +51,16 @@ extern NSString* const kBreakpointTypeFunctionEntry;
 // Creates a dictionary representation for use in NSUserDefaults.
 - (NSDictionary*)dictionary;
 
+// For kBreakpointTypeFile: ////////////////////////////////////////////////////
+
+// Creates a new secure bookmark for maintaining access to the file in the App
+// Sandbox across relaunches.
+- (BOOL)createSecureBookmark;
+
+// Call to enable read-only access to the file.
+- (BOOL)startSecureFileAccess;
+
+// Call when done accessing the file.
+- (BOOL)stopSecureFileAccess;
+
 @end
index 744cf5d457482d7482c2c9a0d6f13518ef5b6e4f..f3cc6753756c90b6093429255ec4c1a79bc69550 100644 (file)
@@ -26,6 +26,8 @@ NSString* const kBreakpointTypeFunctionEntry = @"call";
   unsigned long _debuggerId;
 
   NSString* _file;
+  NSData* _secureBookmark;
+  NSURL* _secureFileAccess;
 
   NSString* _functionName;
 }
@@ -55,6 +57,7 @@ NSString* const kBreakpointTypeFunctionEntry = @"call";
       _type = kBreakpointTypeFile;
       _file = [[dict valueForKey:@"file"] copy];
       _line = [[dict valueForKey:@"line"] intValue];
+      _secureBookmark = [[dict valueForKey:@"secureBookmark"] copy];
     } else if ([type isEqualToString:kBreakpointTypeFunctionEntry]) {
       _type = kBreakpointTypeFunctionEntry;
       _functionName = [[dict valueForKey:@"function"] copy];
@@ -66,6 +69,11 @@ NSString* const kBreakpointTypeFunctionEntry = @"call";
   return self;
 }
 
+- (void)dealloc {
+  if (_secureFileAccess)
+    [self stopSecureFileAccess];
+}
+
 /**
  * Returns the string to display in the breakpoints list.
  */
@@ -124,11 +132,14 @@ NSString* const kBreakpointTypeFunctionEntry = @"call";
 - (NSDictionary*)dictionary
 {
   if (self.type == kBreakpointTypeFile) {
-    return @{
+    NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithDictionary:@{
       @"type" : self.type,
       @"file" : self.file,
-      @"line" : @(self.line)
-    };
+      @"line" : @(self.line),
+    }];
+    if (self.secureBookmark)
+      [dict setObject:self.secureBookmark forKey:@"secureBookmark"];
+    return dict;
   } else if (self.type == kBreakpointTypeFunctionEntry) {
     return @{
       @"type"     : self.type,
@@ -138,6 +149,66 @@ NSString* const kBreakpointTypeFunctionEntry = @"call";
   return nil;
 }
 
+- (BOOL)createSecureBookmark
+{
+  NSURL* fileURL = [NSURL fileURLWithPath:self.file];
+  return [self _createSecureBookmarkWithURL:fileURL];
+}
+
+- (BOOL)_createSecureBookmarkWithURL:(NSURL*)url
+{
+  NSError* error;
+  NSData* secureBookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope | NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess
+                         includingResourceValuesForKeys:nil
+                                          relativeToURL:nil
+                                                  error:&error];
+  if (secureBookmark) {
+    self.secureBookmark = secureBookmark;
+    return YES;
+  } else {
+    NSLog(@"Failed to create secure bookmark: %@", error);
+    return NO;
+  }
+}
+
+- (BOOL)startSecureFileAccess
+{
+  assert(self.type == kBreakpointTypeFile);
+  if (_secureFileAccess)
+    return YES;
+  if (!_secureBookmark)
+    return NO;
+
+  BOOL isStale;
+  NSError* error;
+  _secureFileAccess = [NSURL URLByResolvingBookmarkData:_secureBookmark
+                                                options:NSURLBookmarkResolutionWithSecurityScope
+                                          relativeToURL:nil
+                                    bookmarkDataIsStale:&isStale
+                                                  error:&error];
+  if (error) {
+    NSLog(@"Failed to access file via secure bookmark: %@", error);
+    return NO;
+  }
+  if (isStale)
+    [self _createSecureBookmarkWithURL:_secureFileAccess];
+
+  return [_secureFileAccess startAccessingSecurityScopedResource];
+}
+
+- (BOOL)stopSecureFileAccess
+{
+  assert(self.type == kBreakpointTypeFile);
+  if (!_secureFileAccess)
+    return YES;
+  if (!_secureBookmark)
+    return NO;
+
+  [_secureFileAccess stopAccessingSecurityScopedResource];
+  _secureFileAccess = nil;
+  return YES;
+}
+
 - (NSString*)description
 {
   return [NSString stringWithFormat:@"Breakpoint %@", [[self dictionary] description]];
index b7047e0b3107edbd83d31b9d747ad8565361f226..9e539c87e751078f2cb2ffd5dbf08fb46f62ddd7 100644 (file)
@@ -21,6 +21,7 @@
 
 @implementation BreakpointController {
   BreakpointManager* _manager;
+  Breakpoint* _selection;
 
   BSSourceView* _sourceView;
 
  */
 - (void)tableViewSelectionDidChange:(NSNotification*)notif
 {
+  if (_selection.type == kBreakpointTypeFile) {
+    [_selection stopSecureFileAccess];
+    _selection = nil;
+  }
+
   NSArray* selection = [_arrayController selectedObjects];
   if ([selection count] < 1) {
+    [_sourceView setString:@"" asFile:nil];
+    [_sourceView setMarkers:[NSSet set]];
     return;
   }
-  
-  Breakpoint* bp = [selection objectAtIndex:0];
-  if (bp.type != kBreakpointTypeFile) {
+
+  _selection = [selection objectAtIndex:0];
+  if (_selection.type != kBreakpointTypeFile) {
     return;
   }
 
-  [_sourceView setFile:[bp file]];
-  [_sourceView scrollToLine:[bp line]];
-  [_sourceView setMarkers:[_manager breakpointsForFile:bp.file]];
+  [_selection startSecureFileAccess];
+
+  [_sourceView setFile:[_selection file]];
+  [_sourceView scrollToLine:[_selection line]];
+  [_sourceView setMarkers:[_manager breakpointsForFile:_selection.file]];
 }
 
 @end
index 0cfea233f2aeb4639193588c3fec1e4d07f364ae..bb535119cb839500771370942046929c0187ddce 100644 (file)
  */
 - (void)addBreakpoint:(Breakpoint*)bp;
 {
-  if (![_breakpoints containsObject:bp])
-  {
-    [self willChangeValueForKey:@"breakpoints"];
-    [_breakpoints addObject:bp];
-    [self didChangeValueForKey:@"breakpoints"];
+  if ([_breakpoints containsObject:bp])
+    return;
+
+  if (bp.type == kBreakpointTypeFile && !bp.secureBookmark) {
+    // There is no secure bookmark for this file, so first see if any other
+    // bookmarks exist for the same file. If not, try and create a bookmark.
+    if (!bp.secureBookmark) {
+      [bp createSecureBookmark];
+      /*
+      for (Breakpoint* other in _breakpoints) {
+        if ([other.file isEqualToString:bp.file] && other.secureBookmark) {
+          bp.secureBookmark = other.secureBookmark;
+          break;
+        }
+      }
 
-    [_connection addBreakpoint:bp];
+      if (!bp.secureBookmark) {
+        [bp createSecureBookmark];
+      }
+       */
+    }
+  }
 
-    [_savedBreakpoints addObject:[bp dictionary]];
-    [[NSUserDefaults standardUserDefaults] setObject:_savedBreakpoints forKey:kPrefBreakpoints];
+  [self willChangeValueForKey:@"breakpoints"];
+  [_breakpoints addObject:bp];
+  [self didChangeValueForKey:@"breakpoints"];
 
-    [self updateDisplaysForFile:[bp file]];
-  }
+  [_connection addBreakpoint:bp];
+
+  [_savedBreakpoints addObject:[bp dictionary]];
+  [[NSUserDefaults standardUserDefaults] setObject:_savedBreakpoints forKey:kPrefBreakpoints];
+  [self updateDisplaysForFile:[bp file]];
 }
 
 - (Breakpoint*)removeBreakpoint:(Breakpoint*)bp
 {
-  if ([_breakpoints containsObject:bp]) {
-    [self willChangeValueForKey:@"breakpoints"];
-    [_breakpoints removeObject:bp];
-    [self didChangeValueForKey:@"breakpoints"];
+  if (![_breakpoints containsObject:bp])
+    return nil;
 
-    [_connection removeBreakpoint:bp];
+  [self willChangeValueForKey:@"breakpoints"];
+  [_breakpoints removeObject:bp];
+  [self didChangeValueForKey:@"breakpoints"];
 
-    [_savedBreakpoints removeObject:[bp dictionary]];
-    [[NSUserDefaults standardUserDefaults] setObject:_savedBreakpoints forKey:kPrefBreakpoints];
+  [_connection removeBreakpoint:bp];
 
-    if (bp.file)
-      [self updateDisplaysForFile:bp.file];
+  [_savedBreakpoints removeObject:[bp dictionary]];
+  [[NSUserDefaults standardUserDefaults] setObject:_savedBreakpoints forKey:kPrefBreakpoints];
 
-    return bp;
-  }
-  return nil;
+  if (bp.file)
+    [self updateDisplaysForFile:bp.file];
+
+  return bp;
 }
 
 /**