-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Add MASTG-TEST-0063: Testing Random Number Generation (iOS) #3521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
affd190
bf62947
90b3d51
5ce23ca
c915927
e916091
afa155e
7c2007c
b673c00
5f85eb8
bf421bc
18c0ffa
310fa11
779fff5
de528d6
ec1d38e
7d65361
9a20ebf
131a517
52023b2
25c4d84
5ca0919
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --- | ||
| title: Use Secure Random Number Generator APIs | ||
| alias: ios-use-secure-random | ||
| id: MASTG-BEST-00x1 | ||
| platform: ios | ||
| --- | ||
|
|
||
| Use a cryptographically secure pseudorandom number generator (PRNG) that is backed by the operating system CSPRNG. Do not build your own PRNG. | ||
|
|
||
| ## Swift / Objective-C | ||
|
|
||
| - **Security Framework (preferred)**: Use the [`SecRandomCopyBytes`](https://developer.apple.com/documentation/security/secrandomcopybytes(_:_:_:)) API from the Security framework, which produces cryptographically secure random bytes backed by the system CSPRNG. | ||
| - **CommonCrypto**: You _could_ use `CCRandomCopyBytes` or `CCRandomGenerateBytes` (not documented on the Apple Developers website), which are also backed by the system CSPRNG. However, prefer `SecRandomCopyBytes` which is a wrapper around these functions. | ||
| - **Swift Standard Library**: You can use the Swift Standard Library `.random` APIs which are backed by `SystemRandomNumberGenerator`. However, note that their random number generator can be customized, so ensure you use the default `SystemRandomNumberGenerator` (e.g., by not specifying a custom generator) or a secure alternative (ensure it is cryptographically secure). | ||
| - **CryptoKit**: CryptoKit doesn't expose a direct random byte generator, but it provides secure random nonces and keys through its cryptographic operations, which are backed by the system CSPRNG. For example, you can use `SymmetricKey` for keys and `AES.GCM.Nonce` for nonces without needing to manage raw random bytes directly. | ||
|
|
||
| See @MASTG-KNOW-0070 for code examples of these APIs. | ||
|
|
||
| ## Other Languages | ||
|
|
||
| Consult the standard library or framework to locate the API that exposes the operating system CSPRNG. This is usually the safest path, provided the library itself has no known weaknesses. | ||
|
|
||
| For cross-platform or hybrid apps on iOS rely on frameworks that forward calls to the underlying system CSPRNG. For example: | ||
|
|
||
| - In Flutter or Dart use [`Random.secure()`](https://api.flutter.dev/flutter/dart-math/Random/Random.secure.html), which is documented as cryptographically secure. It reaches `SecRandomCopyBytes` through [the platform integration layers](https://github.com/dart-lang/sdk/blob/47e77939fce74ffda0b7252f33ba1ced2ea09c52/runtime/bin/crypto_macos.cc#L16). See [this article](https://www.zellic.io/blog/proton-dart-flutter-csprng-prng/) for a security review. | ||
| - In React Native use a library such as [`react-native-secure-random`](https://github.com/robhogan/react-native-securerandom) or [`react-native-get-random-values`](https://github.com/LinusU/react-native-get-random-values), which internally calls `SecRandomCopyBytes` on iOS. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| --- | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| platform: ios | ||
| title: Uses of Insecure Random Number Generation with r2 | ||
| code: [swift] | ||
| id: MASTG-DEMO-0063 | ||
| test: MASTG-TEST-xx63 | ||
| --- | ||
|
|
||
| ### Sample | ||
|
|
||
| The following sample demonstrates various methods of generating random tokens, and contrasts insecure and secure approaches. It includes | ||
|
||
|
|
||
| - Insecure methods using libc `rand`, `srand`, and `drand48` | ||
| - Other secure methods such as direct reads from `/dev/random`, `arc4random`, `arc4random_uniform`, `SystemRandomNumberGenerator`, and `CCRandomGenerateBytes` | ||
| - A preferred secure method using `SecRandomCopyBytes` | ||
|
Comment on lines
+13
to
+15
|
||
|
|
||
| > Note that `rand` and `srand` are not part of the Swift standard library. In this demo we call the libc `rand` and `srand` symbols via our own bindings. | ||
|
|
||
| {{ MastgTest.swift }} | ||
|
|
||
| ### Steps | ||
|
|
||
| 1. Unzip the app package and locate the main binary file, as described in @MASTG-TECH-0058. For this demo the path is `./Payload/MASTestApp.app/MASTestApp`. | ||
| 2. Run @MASTG-TOOL-0073 on the binary and use the `-i` option to execute the script below. | ||
|
|
||
| {{ run.sh # insecure_random.r2}} | ||
|
|
||
| This script: | ||
|
|
||
| - Uses `ii` to list imported symbols. | ||
| - Filters that list with `~+rand` to keep only imports whose names contain `rand`, such as `rand`, `srand`, `drand48`, `arc4random`, and `arc4random_uniform`. | ||
| - Uses `[1]` to select the address column from that output. | ||
| - Uses `axtj @@=...` to run `axt` on each of those addresses and print cross references in JSON. | ||
|
Comment on lines
+30
to
+33
|
||
|
|
||
| ### Observation | ||
|
|
||
| The output of the script shows cross references to calls to functions whose names contain `rand` in the sample binary. | ||
|
|
||
| {{ output.json }} | ||
|
|
||
| **Note:** the output also shows calls to secure sources such as `SecRandomCopyBytes`, `CCRandomGenerateBytes`, `SystemRandomNumberGenerator`, and the Swift `FixedWidthInteger.random` implementation. These are present in the sample for contrast, but they are not the reason the test fails. The evaluation only treats uses of insecure libc PRNGs as findings. | ||
|
|
||
| ### Evaluation | ||
|
|
||
| The test fails because insecure PRNGs are used in a security relevant context. Specifically: | ||
|
|
||
| - `sym.imp.rand` and `sym.imp.srand`, which expose the insecure libc PRNG. | ||
| - `sym.imp.drand48`, which also uses an insecure linear congruential generator. | ||
|
|
||
| {{ evaluation.json # evaluation.sh}} | ||
|
|
||
| Now we disassemble the functions that call the insecure PRNGs to confirm their use in security-relevant contexts. | ||
|
|
||
| As an example, take `"fcn_name": "sym.MASTestApp.MastgTest.generateInsecureRandomTokenDrand48_...yFZ_"` from the evaluation output and disassemble or decompile it. | ||
|
|
||
| ```bash | ||
| [0x00002fa0]> pdf @ sym.MASTestApp.MastgTest.generateInsecureRandomTokenDrand48_...yFZ_ | ||
| <disassembly output> | ||
| ``` | ||
|
|
||
| Reading the disassembly confirms that it uses the output of `drand48` to generate a random token (we intentionally don't perform this step for you here, please try it yourself). | ||
|
|
||
| Next we look for cross references to see where it is being called from. | ||
|
|
||
| ```bash | ||
| [0x00002fa0]> axt @ sym.MASTestApp.MastgTest.generateInsecureRandomTokenDrand48_...yFZ_ | ||
| sym.MASTestApp.MastgTest.mastg.completion_...FZ_ 0x41d4 [CALL:--x] bl sym.MASTestApp.MastgTest.generateInsecureRandomTokenDrand48_...yFZ_ | ||
| ``` | ||
|
|
||
| Here it is called from `sym.MASTestApp.MastgTest.mastg.completion_...FZ_`. We can disassemble that function to understand its purpose and keep iterating. | ||
|
|
||
| If we find that it is called from a security-relevant context, such as during authentication or cryptographic operations, we can conclude that the use of the insecure PRNG is a security issue. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| import Foundation | ||
| import Security | ||
| import Darwin | ||
| import CommonCrypto | ||
|
|
||
| // Unsafe bindings to libc srand and rand | ||
| @_silgen_name("srand") | ||
| func c_srand(_ seed: UInt32) | ||
|
|
||
| @_silgen_name("rand") | ||
| func c_rand() -> Int32 | ||
|
|
||
| struct MastgTest { | ||
|
|
||
| // Insecure: libc rand seeded with time, predictable and not suitable for cryptography | ||
| static func generateInsecureRandomTokenRand() -> String { | ||
| var token = "" | ||
|
|
||
| for _ in 0..<16 { | ||
| let value = c_rand() % 256 | ||
| token += String(format: "%02x", value) | ||
| } | ||
| return token | ||
| } | ||
|
|
||
| // Cryptographically secure on Apple platforms | ||
| // Swift random APIs use SystemRandomNumberGenerator backed by the system CSPRNG via arc4random_buf | ||
| // Shown here as a secure source that is not a dedicated crypto token API | ||
| static func generateInsecureRandomTokenSwiftRandom() -> String { | ||
| var token = "" | ||
| for _ in 0..<16 { | ||
| let b = UInt8.random(in: 0...255) | ||
| token += String(format: "%02x", b) | ||
| } | ||
| return token | ||
| } | ||
|
|
||
| // Cryptographically secure: direct read from /dev/random on Apple platforms | ||
| // However, this is a low level interface and is discouraged in favor of SecRandomCopyBytes | ||
| static func generateInsecureRandomTokenDevRandom() -> String { | ||
| let count = 16 | ||
|
|
||
| let fd = open("/dev/random", O_RDONLY) | ||
| if fd < 0 { | ||
| return "Error opening /dev/random" | ||
| } | ||
|
|
||
| var buffer = [UInt8](repeating: 0, count: count) | ||
| let readCount = read(fd, &buffer, count) | ||
| close(fd) | ||
|
|
||
| if readCount != count { | ||
| return "Error reading /dev/random" | ||
| } | ||
|
|
||
| return buffer.map { String(format: "%02x", $0) }.joined() | ||
| } | ||
|
|
||
| // Cryptographically secure but discouraged as a direct token API | ||
| // On Apple platforms arc4random_uniform is strong, but SecRandomCopyBytes or CryptoKit are preferred | ||
| static func generateInsecureRandomTokenArc4RandomUniform() -> String { | ||
| var token = "" | ||
| for _ in 0..<16 { | ||
| let value = arc4random_uniform(256) | ||
| token += String(format: "%02x", value) | ||
| } | ||
| return token | ||
| } | ||
|
|
||
| // Cryptographically secure but discouraged as a direct token API | ||
| // On Apple platforms arc4random is strong, but it is not the recommended crypto API | ||
| static func generateInsecureRandomTokenArc4Random() -> String { | ||
| var token = "" | ||
| for _ in 0..<16 { | ||
| let value = arc4random() % 256 | ||
| token += String(format: "%02x", value) | ||
| } | ||
| return token | ||
| } | ||
|
|
||
| // Cryptographically secure: SystemRandomNumberGenerator uses the system CSPRNG | ||
| // It is suitable for cryptographic use, and CryptoKit builds on it | ||
| // Included here to contrast secure generators with insecure ones | ||
| static func generateInsecureRandomTokenSystemRNG() -> String { | ||
| var token = "" | ||
| var rng = SystemRandomNumberGenerator() | ||
|
|
||
| for _ in 0..<16 { | ||
| let b = UInt8.random(in: 0...255, using: &rng) | ||
| token += String(format: "%02x", b) | ||
| } | ||
| return token | ||
| } | ||
|
|
||
| // Insecure: drand48 uses a 48 bit linear congruential generator | ||
| // Not thread safe and not suitable for cryptographic purposes | ||
| static func generateInsecureRandomTokenDrand48() -> String { | ||
| var token = "" | ||
| for _ in 0..<16 { | ||
| let value = Int(drand48() * 256.0) % 256 | ||
| token += String(format: "%02x", value) | ||
| } | ||
| return token | ||
| } | ||
|
|
||
| // Cryptographically secure: CCRandomGenerateBytes uses the system CSPRNG | ||
| // Secure, but a lower level API that is generally discouraged in favor of SecRandomCopyBytes | ||
| static func generateSecureRandomTokenCC() -> String { | ||
| var buffer = [UInt8](repeating: 0, count: 16) | ||
| let status = CCRandomGenerateBytes(&buffer, buffer.count) | ||
|
|
||
| if status != kCCSuccess { | ||
| return "Error generating random bytes with CCRandomGenerateBytes" | ||
| } | ||
|
|
||
| return buffer.map { String(format: "%02x", $0) }.joined() | ||
| } | ||
|
|
||
| // Recommended: SecRandomCopyBytes is the high level, Apple recommended API for secure random bytes | ||
| static func generateSecureRandomToken() -> String { | ||
| var randomBytes = [UInt8](repeating: 0, count: 16) | ||
| let status = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes) | ||
|
|
||
| guard status == errSecSuccess else { | ||
| return "Error generating secure random bytes" | ||
| } | ||
|
|
||
| return randomBytes.map { String(format: "%02x", $0) }.joined() | ||
| } | ||
|
|
||
| static func mastgTest(completion: @escaping (String) -> Void) { | ||
| // Seed libc rand with current time | ||
| let now = UInt32(time(nil)) | ||
| c_srand(now) | ||
|
|
||
| // Example of seeding drand48 with time, which also makes it predictable if the seed is known | ||
| // srand48(time(nil)) | ||
|
|
||
| let value = """ | ||
| Using libc rand seeded with time | ||
| Token: \(generateInsecureRandomTokenRand()) | ||
|
|
||
| Using Swift random API backed by SystemRandomNumberGenerator | ||
| Token: \(generateInsecureRandomTokenSwiftRandom()) | ||
|
|
||
| Using /dev/random low level interface | ||
| Token: \(generateInsecureRandomTokenDevRandom()) | ||
|
|
||
| Using arc4random_uniform as a direct token source | ||
| Token: \(generateInsecureRandomTokenArc4RandomUniform()) | ||
|
|
||
| Using arc4random as a direct token source | ||
| Token: \(generateInsecureRandomTokenArc4Random()) | ||
|
|
||
| Using SystemRandomNumberGenerator directly | ||
| Token: \(generateInsecureRandomTokenSystemRNG()) | ||
|
|
||
| Using drand48 linear congruential generator | ||
| Token: \(generateInsecureRandomTokenDrand48()) | ||
|
|
||
| Using CCRandomGenerateBytes lower level API | ||
| Token: \(generateSecureRandomTokenCC()) | ||
|
|
||
| Using SecRandomCopyBytes | ||
| Token: \(generateSecureRandomToken()) | ||
| """ | ||
|
|
||
| completion(value) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| jq -s 'add | map(select(.refname | test("^sym[.]imp[.](rand|srand|drand48)")))' output.json > evaluation.json |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| [ | ||
| { | ||
| "from": 12444, | ||
| "type": "CALL", | ||
| "perm": "--x", | ||
| "opcode": "bl sym.imp.drand48", | ||
| "fcn_addr": 12192, | ||
| "fcn_name": "sym.MASTestApp.MastgTest.generateInsecureRandomTokenDrand48_...yFZ_", | ||
| "realname": "func.00002fa0", | ||
| "refname": "sym.imp.drand48" | ||
| }, | ||
| { | ||
| "from": 5228, | ||
| "type": "CALL", | ||
| "perm": "--x", | ||
| "opcode": "bl sym.imp.rand", | ||
| "fcn_addr": 5036, | ||
| "fcn_name": "sym.MASTestApp.MastgTest.generateInsecureRandomTokenRand_...yFZ_", | ||
| "realname": "func.000013ac", | ||
| "refname": "sym.imp.rand" | ||
| }, | ||
| { | ||
| "from": 16004, | ||
| "type": "CALL", | ||
| "perm": "--x", | ||
| "opcode": "bl sym.imp.srand", | ||
| "fcn_addr": 15768, | ||
| "fcn_name": "sym.MASTestApp.MastgTest.mastg.completion_...FZ_", | ||
| "realname": "func.00003d98", | ||
| "refname": "sym.imp.srand" | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| axtj @@=`ii~+rand[1]` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| [{"from":11936,"type":"CALL","perm":"--x","opcode":"bl sym.imp.FixedWidthInteger.random.setter....ztSGRd__lFZ","fcn_addr":11668,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenSystemRNG_...yFZ_","realname":"func.00002d94","refname":"sym.imp.FixedWidthInteger.random.setter....ztSGRd__lFZ"}] | ||
| [{"from":6784,"type":"CALL","perm":"--x","opcode":"bl sym.imp.FixedWidthInteger.random.setter_...FZ_","fcn_addr":6540,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenSwift...G0SSyFZ","realname":"func.0000198c","refname":"sym.imp.FixedWidthInteger.random.setter_...FZ_"}] | ||
| [{"from":11748,"type":"CALL","perm":"--x","opcode":"bl sym.imp.SystemRandomNumberGenerator...VABycfC","fcn_addr":11668,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenSystemRNG_...yFZ_","realname":"func.00002d94","refname":"sym.imp.SystemRandomNumberGenerator...VABycfC"}] | ||
| [] | ||
| [] | ||
| [{"from":13580,"type":"CALL","perm":"--x","opcode":"bl sym.imp.CCRandomGenerateBytes","fcn_addr":13084,"fcn_name":"sym.MASTestApp.MastgTest.generateSecureRandomTokenCC_...yFZ_","realname":"func.0000331c","refname":"sym.imp.CCRandomGenerateBytes"}] | ||
| [{"from":14936,"type":"CALL","perm":"--x","opcode":"bl sym.imp.SecRandomCopyBytes","fcn_addr":14420,"fcn_name":"sym.MASTestApp.MastgTest.generateSecureRandomToken_...yFZ_","realname":"func.00003854","refname":"sym.imp.SecRandomCopyBytes"}] | ||
| [{"from":11392,"type":"CALL","perm":"--x","opcode":"bl sym.imp.arc4random","fcn_addr":11200,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenArc4...G0SSyFZ","realname":"func.00002bc0","refname":"sym.imp.arc4random"}] | ||
| [{"from":10944,"type":"CALL","perm":"--x","opcode":"bl sym.imp.arc4random_uniform","fcn_addr":10748,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenArc4.Uniform_...yFZ_","realname":"func.000029fc","refname":"sym.imp.arc4random_uniform"}] | ||
| [{"from":12444,"type":"CALL","perm":"--x","opcode":"bl sym.imp.drand48","fcn_addr":12192,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenDrand48_...yFZ_","realname":"func.00002fa0","refname":"sym.imp.drand48"}] | ||
| [] | ||
| [{"from":5228,"type":"CALL","perm":"--x","opcode":"bl sym.imp.rand","fcn_addr":5036,"fcn_name":"sym.MASTestApp.MastgTest.generateInsecureRandomTokenRand_...yFZ_","realname":"func.000013ac","refname":"sym.imp.rand"}] | ||
| [{"from":16004,"type":"CALL","perm":"--x","opcode":"bl sym.imp.srand","fcn_addr":15768,"fcn_name":"sym.MASTestApp.MastgTest.mastg.completion_...FZ_","realname":"func.00003d98","refname":"sym.imp.srand"}] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| r2 -q -i insecure_random.r2 -A MASTestApp > output.json |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| --- | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| platform: ios | ||
| title: Uses of Insecure Random Number Generation with frida-trace | ||
| code: [swift] | ||
| id: MASTG-DEMO-0064 | ||
| test: MASTG-TEST-xx63 | ||
| --- | ||
|
|
||
| ### Sample | ||
|
|
||
| This demo uses the same sample code as in @MASTG-DEMO-0063. It demonstrates the use of insecure random number generation using various APIs. | ||
|
|
||
| {{ ../MASTG-DEMO-0063/MastgTest.swift }} | ||
|
|
||
| ### Steps | ||
|
|
||
| 1. Install the app on a device (@MASTG-TECH-0056) | ||
| 2. Make sure you have @MASTG-TOOL-0039 installed on your machine and the frida-server running on the device | ||
| 3. Run `run.sh` to spawn your app with Frida | ||
| 4. Click the **Start** button | ||
| 5. Stop the script by pressing `Ctrl+C` | ||
|
|
||
| {{ run.sh }} | ||
|
|
||
| ### Observation | ||
|
|
||
| {{ output.txt }} | ||
|
|
||
| This output contains both insecure and secure APIs. For this test case the interesting calls are: | ||
|
|
||
| - `rand` and `srand`, which expose the insecure libc PRNG. | ||
| - `drand48`, which also uses an insecure linear congruential generator. | ||
|
Comment on lines
+31
to
+32
|
||
|
|
||
| ### Evaluation | ||
|
|
||
| The test fails because insecure PRNGs are used in a security relevant context. See the evaluation section in @MASTG-DEMO-0063 for more details. | ||
|
|
||
| The same output also shows calls to secure sources such as `SecRandomCopyBytes`, `CCRandomGenerateBytes`, `SystemRandomNumberGenerator`, and the Swift standard library's `FixedWidthInteger.random` implementation. These are present in the sample for contrast, but they are not the reason the test fails. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| 2959 ms srand(seed=0x691a0327) | ||
| 2959 ms rand() | ||
| ... x 16 | ||
| 2959 ms $ss17FixedWidthIntegerPsE6random2inxSNyxG_tFZ() | ||
| 2959 ms | arc4random_buf(buf=0x16ef965a8, nbytes=0x8) | ||
| ... x 16 | ||
| 2959 ms open(path="/dev/random", oflag=0x0, ...) | ||
| 2960 ms arc4random_uniform(upper_bound=0x100) | ||
| ... x 16 | ||
| 2960 ms arc4random() | ||
| ... x 16 | ||
| 2960 ms $ss27SystemRandomNumberGeneratorVABycfC() | ||
| 2960 ms $ss17FixedWidthIntegerPsE6random2in5usingxSNyxG_qd__ztSGRd__lFZ() | ||
| 2960 ms | $ss17FixedWidthIntegerPsE7_random5usingxqd__z_tSGRd__lFZ() | ||
| 2960 ms | | arc4random_buf(buf=0x16ef964d8, nbytes=0x8) | ||
| ... x 16 | ||
| 2960 ms drand48() | ||
| ... x 16 | ||
| 2960 ms CCRandomGenerateBytes() | ||
| 2960 ms SecRandomCopyBytes() | ||
| 2960 ms | CCRandomCopyBytes() | ||
| 2960 ms | | CCRandomGenerateBytes() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| frida-trace -n 'MASTestApp' -i "*FixedWidthInteger*random*" -i "*rand" -i "*rand48" -i "arc4random*" -i "*SystemRandomNumberGenerator*" -i "CCRandom*" -i "SecRandomCopyBytes" # -i "open" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing punctuation in list items: According to the guidelines, list items that are complete sentences should end with periods. These list items are complete sentences and should have periods at the end.