Skip to content

Commit 28b7f08

Browse files
committed
Swift: UnsafeJsEval test finally compiles
1 parent 7b599f5 commit 28b7f08

File tree

3 files changed

+300
-0
lines changed

3 files changed

+300
-0
lines changed

swift/ql/test/query-tests/Security/CWE-094/UnsafeJsEval.expected

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/Security/CWE-094/UnsafeJsEval.ql
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
2+
// --- stubs ---
3+
4+
class NSObject {}
5+
6+
@MainActor class UIResponder : NSObject {}
7+
@MainActor class UIView : UIResponder {}
8+
9+
@MainActor class NSResponder : NSObject {}
10+
class NSView : NSResponder {}
11+
12+
class WKFrameInfo : NSObject {}
13+
class WKContentWorld : NSObject {
14+
class var defaultClient: WKContentWorld { WKContentWorld() }
15+
}
16+
17+
class WKWebView : UIView {
18+
19+
func evaluateJavaScript(
20+
_ javaScriptString: String
21+
) async throws -> Any { "" }
22+
23+
func evaluateJavaScript(
24+
_ javaScriptString: String,
25+
completionHandler: ((Any?, Error?) -> Void)? = nil
26+
) {
27+
completionHandler?(nil, nil)
28+
}
29+
30+
@MainActor func evaluateJavaScript(
31+
_ javaScript: String,
32+
in frame: WKFrameInfo? = nil,
33+
in contentWorld: WKContentWorld,
34+
completionHandler: ((Result<Any, Error>) -> Void)? = nil
35+
) {
36+
completionHandler?(.success(""))
37+
}
38+
39+
@MainActor func evaluateJavaScript(
40+
_ javaScript: String,
41+
in frame: WKFrameInfo? = nil,
42+
contentWorld: WKContentWorld
43+
) async throws -> Any? { nil }
44+
45+
@MainActor func callAsyncJavaScript(
46+
_ functionBody: String,
47+
arguments: [String : Any] = [:],
48+
in frame: WKFrameInfo? = nil,
49+
in contentWorld: WKContentWorld,
50+
completionHandler: ((Result<Any, Error>) -> Void)? = nil
51+
) {
52+
completionHandler?(.success(""))
53+
}
54+
55+
@MainActor func callAsyncJavaScript(
56+
_ functionBody: String,
57+
arguments: [String : Any] = [:],
58+
in frame: WKFrameInfo? = nil,
59+
contentWorld: WKContentWorld
60+
) async throws -> Any? { nil }
61+
}
62+
63+
enum WKUserScriptInjectionTime : Int, @unchecked Sendable {
64+
case atDocumentStart, atDocumentEnd
65+
}
66+
67+
class WKUserScript : NSObject {
68+
init(
69+
source: String,
70+
injectionTime: WKUserScriptInjectionTime,
71+
forMainFrameOnly: Bool
72+
) {}
73+
74+
init(
75+
source: String,
76+
injectionTime: WKUserScriptInjectionTime,
77+
forMainFrameOnly: Bool,
78+
in contentWorld: WKContentWorld
79+
) {}
80+
}
81+
82+
class WKUserContentController : NSObject {
83+
func addUserScript(_ userScript: WKUserScript) {}
84+
}
85+
86+
class UIWebView : UIView {
87+
// deprecated
88+
func stringByEvaluatingJavaScript(from script: String) -> String? { nil }
89+
}
90+
91+
class WebView : NSView {
92+
// deprecated
93+
func stringByEvaluatingJavaScript(from script: String!) -> String! { "" }
94+
}
95+
96+
class JSValue : NSObject {}
97+
98+
class JSContext {
99+
func evaluateScript(_ script: String!) -> JSValue! { return JSValue() }
100+
func evaluateScript(
101+
_ script: String!,
102+
withSourceURL sourceURL: URL!
103+
) -> JSValue! { return JSValue() }
104+
}
105+
106+
typealias JSContextRef = OpaquePointer
107+
typealias JSStringRef = OpaquePointer
108+
typealias JSObjectRef = OpaquePointer
109+
typealias JSValueRef = OpaquePointer
110+
typealias JSChar = UInt16
111+
112+
func JSStringCreateWithCharacters(
113+
_ chars: UnsafePointer<JSChar>!,
114+
_ numChars: Int
115+
) -> JSStringRef! {
116+
return chars.withMemoryRebound(to: CChar.self, capacity: numChars) {
117+
cchars in OpaquePointer(cchars)
118+
}
119+
}
120+
func JSStringCreateWithUTF8CString(_ string: UnsafePointer<CChar>!) -> JSStringRef! {
121+
return OpaquePointer(string)
122+
}
123+
func JSStringRetain(_ string: JSStringRef!) -> JSStringRef! { return string }
124+
func JSStringRelease(_ string: JSStringRef!) { }
125+
126+
func JSEvaluateScript(
127+
_ ctx: JSContextRef!,
128+
_ script: JSStringRef!,
129+
_ thisObject: JSObjectRef!,
130+
_ sourceURL: JSStringRef!,
131+
_ startingLineNumber: Int32,
132+
_ exception: UnsafeMutablePointer<JSValueRef?>!
133+
) -> JSValueRef! { return OpaquePointer(bitPattern: 0) }
134+
135+
@frozen
136+
public struct Data: Collection {
137+
public typealias Index = Int
138+
public typealias Element = UInt8
139+
public subscript(x: Index) -> Element { 0 }
140+
public var startIndex: Index { 0 }
141+
public var endIndex: Index { 0 }
142+
public func index(after i: Index) -> Index { i + 1 }
143+
init<S>(_ elements: S) {}
144+
}
145+
146+
struct URL {
147+
init?(string: String) {}
148+
init?(string: String, relativeTo: URL?) {}
149+
}
150+
151+
extension String {
152+
init(contentsOf: URL) throws {
153+
let data = ""
154+
// ...
155+
self.init(data)
156+
}
157+
}
158+
159+
// --- tests ---
160+
161+
func getRemoteData() -> String {
162+
let url = URL(string: "http://example.com/")
163+
do {
164+
return try String(contentsOf: url!)
165+
} catch {
166+
return ""
167+
}
168+
}
169+
170+
func testUsage(_ sink: @escaping (String) async throws -> ()) {
171+
Task {
172+
let localString = "console.log('localString')"
173+
let localStringFragment = "'localStringFragment'"
174+
let remoteString = getRemoteData()
175+
176+
try! await sink(localString) // GOOD: the HTML data is local
177+
try! await sink(getRemoteData()) // BAD: HTML contains remote input, may access local secrets
178+
try! await sink(remoteString) // BAD
179+
180+
try! await sink("console.log(" + localStringFragment + ")") // GOOD: the HTML data is local
181+
try! await sink("console.log(" + remoteString + ")") // BAD
182+
183+
let localData = Data(localString.utf8)
184+
let remoteData = Data(remoteString.utf8)
185+
186+
try! await sink(String(decoding: localData, as: UTF8.self)) // GOOD: the data is local
187+
try! await sink(String(decoding: remoteData, as: UTF8.self)) // BAD: the data is remote
188+
189+
try! await sink("console.log(" + String(Int(localStringFragment) ?? 0) + ")") // GOOD: Primitive conversion
190+
try! await sink("console.log(" + String(Int(remoteString) ?? 0) + ")") // GOOD: Primitive conversion
191+
192+
try! await sink("console.log(" + (localStringFragment.count != 0 ? "1" : "0") + ")") // GOOD: Primitive conversion
193+
try! await sink("console.log(" + (remoteString.count != 0 ? "1" : "0") + ")") // GOOD: Primitive conversion
194+
}
195+
}
196+
197+
func testUIWebView() {
198+
let webview = UIWebView()
199+
200+
testUsage { string in
201+
_ = await webview.stringByEvaluatingJavaScript(from: string)
202+
}
203+
}
204+
205+
func testWebView() {
206+
let webview = WebView()
207+
208+
testUsage { string in
209+
_ = await webview.stringByEvaluatingJavaScript(from: string)
210+
}
211+
}
212+
213+
func testWKWebView() {
214+
let webview = WKWebView()
215+
216+
testUsage { string in
217+
_ = try await webview.evaluateJavaScript(string)
218+
}
219+
testUsage { string in
220+
await webview.evaluateJavaScript(string) { _, _ in }
221+
}
222+
testUsage { string in
223+
await webview.evaluateJavaScript(string, in: nil, in: WKContentWorld.defaultClient) { _ in }
224+
}
225+
testUsage { string in
226+
_ = try await webview.evaluateJavaScript(string, contentWorld: .defaultClient)
227+
}
228+
testUsage { string in
229+
await webview.callAsyncJavaScript(string, in: nil, in: .defaultClient) { _ in () }
230+
}
231+
testUsage { string in
232+
_ = try await webview.callAsyncJavaScript(string, contentWorld: WKContentWorld.defaultClient)
233+
}
234+
}
235+
236+
func testWKUserContentController() {
237+
let ctrl = WKUserContentController()
238+
239+
testUsage { string in
240+
ctrl.addUserScript(WKUserScript(source: string, injectionTime: .atDocumentStart, forMainFrameOnly: false))
241+
}
242+
testUsage { string in
243+
ctrl.addUserScript(WKUserScript(source: string, injectionTime: .atDocumentEnd, forMainFrameOnly: true, in: .defaultClient))
244+
}
245+
}
246+
247+
func testJSContext() {
248+
let ctx = JSContext()
249+
250+
testUsage { string in
251+
_ = ctx.evaluateScript(string)
252+
}
253+
testUsage { string in
254+
_ = ctx.evaluateScript(string, withSourceURL: URL(string: "https://example.com"))
255+
}
256+
}
257+
258+
func testJSEvaluateScript() {
259+
testUsage { string in
260+
string.utf16.withContiguousStorageIfAvailable { stringBytes in
261+
let jsstr = JSStringRetain(JSStringCreateWithCharacters(stringBytes.baseAddress, string.count))
262+
defer { JSStringRelease(jsstr) }
263+
_ = JSEvaluateScript(
264+
/*ctx:*/ OpaquePointer(bitPattern: 0),
265+
/*script:*/ jsstr,
266+
/*thisObject:*/ OpaquePointer(bitPattern: 0),
267+
/*sourceURL:*/ OpaquePointer(bitPattern: 0),
268+
/*startingLineNumber:*/ 0,
269+
/*exception:*/ UnsafeMutablePointer(bitPattern: 0)
270+
)
271+
}
272+
}
273+
testUsage { string in
274+
string.utf8CString.withUnsafeBufferPointer { stringBytes in
275+
let jsstr = JSStringRetain(JSStringCreateWithUTF8CString(stringBytes.baseAddress))
276+
defer { JSStringRelease(jsstr) }
277+
_ = JSEvaluateScript(
278+
/*ctx:*/ OpaquePointer(bitPattern: 0),
279+
/*script:*/ jsstr,
280+
/*thisObject:*/ OpaquePointer(bitPattern: 0),
281+
/*sourceURL:*/ OpaquePointer(bitPattern: 0),
282+
/*startingLineNumber:*/ 0,
283+
/*exception:*/ UnsafeMutablePointer(bitPattern: 0)
284+
)
285+
}
286+
}
287+
}
288+
289+
func testQHelpExamples() {
290+
291+
}
292+
293+
testUIWebView()
294+
testWebView()
295+
testWKWebView()
296+
testWKUserContentController()
297+
testJSContext()
298+
testJSEvaluateScript()
299+
testQHelpExamples()

0 commit comments

Comments
 (0)