Skip to content

Commit

Permalink
Fix log redaction loading issue
Browse files Browse the repository at this point in the history
  • Loading branch information
mojganii committed Dec 2, 2024
1 parent db019b7 commit 256f943
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 107 deletions.
1 change: 1 addition & 0 deletions ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Line wrap the file at 100 chars. Th
## [2024.10 - 2024-11-20]
### Fixed
- Removed deadlock when losing connectivity without entering offline state.
- Improved log reporting.

## [2024.9 - 2024-11-07]
### Added
Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadSettings/SettingsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public enum SettingsManager {

public static func getLastUsedAccount() throws -> String {
let data = try store.read(key: .lastUsedAccount)
return String(decoding: data, as: UTF8.self)
return String(bytes: data, encoding: .utf8) ?? ""
}

public static func setLastUsedAccount(_ string: String?) throws {
Expand Down
16 changes: 16 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,7 @@
F0164EC32B4C49D30020268D /* ShadowsocksLoaderStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EC22B4C49D30020268D /* ShadowsocksLoaderStub.swift */; };
F0164ED12B4F2DCB0020268D /* AccessMethodIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164ED02B4F2DCB0020268D /* AccessMethodIterator.swift */; };
F01DAE332C2B032A00521E46 /* RelaySelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01DAE322C2B032A00521E46 /* RelaySelection.swift */; };
F022EBA62CF0C6AE009484B9 /* ConsolidatedApplicationLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */; };
F028A56A2A34D4E700C0CAA3 /* RedeemVoucherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A5692A34D4E700C0CAA3 /* RedeemVoucherViewController.swift */; };
F028A56C2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A56B2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift */; };
F02F41A02B9723AF00625A4F /* AddLocationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419A2B9723AE00625A4F /* AddLocationsViewController.swift */; };
Expand Down Expand Up @@ -946,6 +947,8 @@
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
F0A086902C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */; };
F0A1638A2C47B77300592300 /* ServerRelaysResponse+Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0ACE3342BE51745006D5333 /* ServerRelaysResponse+Stubs.swift */; };
F0A7EBB22CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A7EBB12CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift */; };
F0A7EBB62CF092CC005BB671 /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
F0ACE30D2BE4E478006D5333 /* MullvadMockData.h in Headers */ = {isa = PBXBuildFile; fileRef = F0ACE30A2BE4E478006D5333 /* MullvadMockData.h */; settings = {ATTRIBUTES = (Public, ); }; };
F0ACE3102BE4E478006D5333 /* MullvadMockData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */; };
F0ACE3112BE4E478006D5333 /* MullvadMockData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -2166,6 +2169,7 @@
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionServiceTests.swift; sourceTree = "<group>"; };
F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsStrategyTests.swift; sourceTree = "<group>"; };
F0A163882C47B46300592300 /* SingleHopEphemeralPeerExchangerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleHopEphemeralPeerExchangerTests.swift; sourceTree = "<group>"; };
F0A7EBB12CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsolidatedApplicationLogTests.swift; sourceTree = "<group>"; };
F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadMockData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F0ACE30A2BE4E478006D5333 /* MullvadMockData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadMockData.h; sourceTree = "<group>"; };
F0ACE32E2BE4EA8B006D5333 /* MockProxyFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProxyFactory.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2425,6 +2429,7 @@
440E9EF02BDA93CB00B1FD11 /* MullvadVPN */ = {
isa = PBXGroup;
children = (
F0A7EBB02CEF6C5F005BB671 /* Log */,
440E9EF62BDA957300B1FD11 /* Classes */,
440E9F002BDA997C00B1FD11 /* Extensions */,
440E9EFB2BDA97C600B1FD11 /* GeneralAPIs */,
Expand Down Expand Up @@ -4205,6 +4210,14 @@
path = GeneralAPIs;
sourceTree = "<group>";
};
F0A7EBB02CEF6C5F005BB671 /* Log */ = {
isa = PBXGroup;
children = (
F0A7EBB12CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift */,
);
path = Log;
sourceTree = "<group>";
};
F0ACE3092BE4E478006D5333 /* MullvadMockData */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -5378,6 +5391,7 @@
A9A5FA3B2ACB05910083449F /* UIMetrics.swift in Sources */,
58B07C182AEFDD6C00A09625 /* StoreTransactionLog.swift in Sources */,
A9A5FA382ACB05600083449F /* InputTextFormatter.swift in Sources */,
F022EBA62CF0C6AE009484B9 /* ConsolidatedApplicationLog.swift in Sources */,
A9A5FA372ACB052D0083449F /* ApplicationTarget.swift in Sources */,
A9A5F9E12ACB05160083449F /* AddressCacheTracker.swift in Sources */,
A9A5F9E22ACB05160083449F /* BackgroundTask.swift in Sources */,
Expand Down Expand Up @@ -5488,6 +5502,7 @@
A9A5FA282ACB05160083449F /* WgKeyRotation.swift in Sources */,
449872E42B7CB96300094DDC /* TunnelSettingsUpdateTests.swift in Sources */,
A9A5FA292ACB05160083449F /* AddressCacheTests.swift in Sources */,
F0A7EBB22CEF6C79005BB671 /* ConsolidatedApplicationLogTests.swift in Sources */,
A9B6AC182ADE8F4300F7802A /* MigrationManagerTests.swift in Sources */,
7A9BE5AB2B909A1700E2A7D0 /* LocationDataSourceProtocol.swift in Sources */,
A9A5FA2A2ACB05160083449F /* CoordinatesTests.swift in Sources */,
Expand Down Expand Up @@ -5515,6 +5530,7 @@
A9A5FA342ACB05160083449F /* StringTests.swift in Sources */,
7A52F96C2C17450C00B133B9 /* RelaySelectorWrapperTests.swift in Sources */,
A9A5FA352ACB05160083449F /* WgKeyRotationTests.swift in Sources */,
F0A7EBB62CF092CC005BB671 /* ApplicationConfiguration.swift in Sources */,
7AB4CCB92B69097E006037F5 /* IPOverrideTests.swift in Sources */,
A9A5FA362ACB05160083449F /* TunnelManagerTests.swift in Sources */,
);
Expand Down
131 changes: 81 additions & 50 deletions ios/MullvadVPN/Classes/ConsolidatedApplicationLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ private let kRedactedContainerPlaceholder = "[REDACTED CONTAINER PATH]"

class ConsolidatedApplicationLog: TextOutputStreamable {
typealias Metadata = KeyValuePairs<MetadataKey, String>
private let bufferSize: UInt64

enum MetadataKey: String {
case id, os
Expand All @@ -26,18 +27,21 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
let content: String
}

let redactCustomStrings: [String]
let redactCustomStrings: [String]?
let applicationGroupContainers: [URL]
let metadata: Metadata

private let logQueue = DispatchQueue(label: "com.mullvad.consolidation.logs.queue")
private var logs: [LogAttachment] = []

init(
redactCustomStrings: [String],
redactContainerPathsForSecurityGroupIdentifiers securityGroupIdentifiers: [String]
redactCustomStrings: [String]? = nil,
redactContainerPathsForSecurityGroupIdentifiers securityGroupIdentifiers: [String],
bufferSize: UInt64
) {
metadata = Self.makeMetadata()
self.redactCustomStrings = redactCustomStrings
self.bufferSize = bufferSize

applicationGroupContainers = securityGroupIdentifiers
.compactMap { securityGroupIdentifier -> URL? in
Expand All @@ -46,38 +50,62 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
}
}

func addLogFiles(fileURLs: [URL]) {
for fileURL in fileURLs {
addSingleLogFile(fileURL)
func addLogFiles(fileURLs: [URL], completion: (() -> Void)? = nil) {
logQueue.async(flags: .barrier) {
for fileURL in fileURLs {
self.addSingleLogFile(fileURL)
}
DispatchQueue.main.async {
completion?()
}
}
}

func addError(message: String, error: String) {
func addError(message: String, error: String, completion: (() -> Void)? = nil) {
let redactedError = redact(string: error)

logs.append(LogAttachment(label: message, content: redactedError))
logQueue.async(flags: .barrier) {
self.logs.append(LogAttachment(label: message, content: redactedError))
DispatchQueue.main.async {
completion?()
}
}
}

var string: String {
var body = ""
write(to: &body)
return body
var logsCopy: [LogAttachment] = []
var metadataCopy: Metadata = [:]
logQueue.sync {
logsCopy = logs
metadataCopy = metadata
}
guard !logsCopy.isEmpty else { return "" }
return formatLog(logs: logsCopy, metadata: metadataCopy)
}

func write(to stream: inout some TextOutputStream) {
print("System information:", to: &stream)
for (key, value) in metadata {
print("\(key.rawValue): \(value)", to: &stream)
var logsCopy: [LogAttachment] = []
var metadataCopy: Metadata = [:]
logQueue.sync {
logsCopy = logs
metadataCopy = metadata
}
print("", to: &stream)
let localOutput = formatLog(logs: logsCopy, metadata: metadataCopy)
stream.write(localOutput)
}

private func formatLog(logs: [LogAttachment], metadata: Metadata) -> String {
var result = "System information:\n"
for (key, value) in metadata {
result += "\(key.rawValue): \(value)\n"
}
result += "\n"
for attachment in logs {
print(kLogDelimiter, to: &stream)
print(attachment.label, to: &stream)
print(kLogDelimiter, to: &stream)
print(attachment.content, to: &stream)
print("", to: &stream)
result += "\(kLogDelimiter)\n"
result += "\(attachment.label)\n"
result += "\(kLogDelimiter)\n"
result += "\(attachment.content)\n\n"
}
return result
}

private func addSingleLogFile(_ fileURL: URL) {
Expand All @@ -92,10 +120,11 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
let path = fileURL.path
let redactedPath = redact(string: path)

if let lossyString = Self.readFileLossy(path: path, maxBytes: ApplicationConfiguration.logMaximumFileSize) {
if let lossyString = readFileLossy(path: path, maxBytes: bufferSize) {
let redactedString = redact(string: lossyString)

logs.append(LogAttachment(label: redactedPath, content: redactedString))
logQueue.async(flags: .barrier) {
self.logs.append(LogAttachment(label: redactedPath, content: redactedString))
}
} else {
addError(message: redactedPath, error: "Log file does not exist: \(path).")
}
Expand All @@ -113,7 +142,7 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
]
}

private static func readFileLossy(path: String, maxBytes: UInt64) -> String? {
private func readFileLossy(path: String, maxBytes: UInt64) -> String? {
guard let fileHandle = FileHandle(forReadingAtPath: path) else {
return nil
}
Expand All @@ -125,35 +154,37 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
fileHandle.seek(toFileOffset: 0)
}

let data = fileHandle.readData(ofLength: Int(ApplicationConfiguration.logMaximumFileSize))
let replacementCharacter = Character(UTF8.decode(UTF8.encodedReplacementCharacter))
let lossyString = String(
String(decoding: data, as: UTF8.self)
.drop { ch in
// Drop leading replacement characters produced when decoding data
ch == replacementCharacter
}
)

return lossyString
if let data = try? fileHandle.read(upToCount: Int(bufferSize)),
let lossyString = String(bytes: data, encoding: .utf8) {
let resultString = lossyString.drop { ch in
// Drop leading replacement characters produced when decoding data
ch == replacementCharacter
}
return String(resultString)
} else {
return nil
}
}

private func redactCustomStrings(string: String) -> String {
redactCustomStrings.reduce(string) { resultString, redact -> String in
private func redactCustomStrings(in string: String) -> String {
guard let customStrings = redactCustomStrings,
!customStrings.isEmpty else {
return string
}
return customStrings.reduce(string) { resultString, redact in
resultString.replacingOccurrences(of: redact, with: kRedactedPlaceholder)
}
}

private func redact(string: String) -> String {
[
redactContainerPaths,
Self.redactAccountNumber,
Self.redactIPv4Address,
Self.redactIPv6Address,
redactCustomStrings,
].reduce(string) { resultString, transform -> String in
transform(resultString)
}
var result = string
result = redactContainerPaths(string: result)
result = redactAccountNumber(string: result)
result = redactIPv4Address(string: result)
result = redactIPv6Address(string: result)
result = redactCustomStrings(in: result)
return result
}

private func redactContainerPaths(string: String) -> String {
Expand All @@ -165,7 +196,7 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
}
}

private static func redactAccountNumber(string: String) -> String {
private func redactAccountNumber(string: String) -> String {
redact(
// swiftlint:disable:next force_try
regularExpression: try! NSRegularExpression(pattern: #"\d{16}"#),
Expand All @@ -174,23 +205,23 @@ class ConsolidatedApplicationLog: TextOutputStreamable {
)
}

private static func redactIPv4Address(string: String) -> String {
private func redactIPv4Address(string: String) -> String {
redact(
regularExpression: NSRegularExpression.ipv4RegularExpression,
string: string,
replacementString: kRedactedPlaceholder
)
}

private static func redactIPv6Address(string: String) -> String {
private func redactIPv6Address(string: String) -> String {
redact(
regularExpression: NSRegularExpression.ipv6RegularExpression,
string: string,
replacementString: kRedactedPlaceholder
)
}

private static func redact(
private func redact(
regularExpression: NSRegularExpression,
string: String,
replacementString: String
Expand Down
Loading

0 comments on commit 256f943

Please sign in to comment.