← All reports

[SecurityFlags] Introduce SecurityFlags and propagate them to the privileged child processes

Component: WebKit process infrastructure | 722d0b3

Source/WTF/Scripts/GenerateSecurityFlags.rb

+# Keys as the file spells them, in order and including repeats. Validation cannot use the hash YAML.load_file
+# returns, because YAML silently collapses a repeated key and keeps only the last one.
+def keysInFileOrder(path)
+ ...
+end
+
+# Keys sort by magnitude: a plain string sort would put radar1000000000 before radar99999999.
+def radarNumber(name)
+ digits = name[/\Aradar([1-9][0-9]*)\z/, 1]
+ digits && digits.to_i
+end
+
+def load(path)
+ seen = {}
+ previousName = nil
+
+ keysInFileOrder(path).each do |name|
+ if seen[name]
+ STDERR.puts "error: Input file #{path} defines '#{name}' more than once. Only the last one would survive, silently discarding the other entry."

WebKit의 process model에서는 WebContent를 신뢰할 수 없는 sandboxed process로 취급하고, Networking, GPU, Model process는 더 privileged한 존재로 다룹니다. UIProcess와 이 child process들 사이의 IPC가 바로 security mitigation이 의존하는 trust boundary에 해당합니다. 이번 commit은 SecurityFlags라는 메커니즘을 새로 추가합니다. Radar 번호를 key로 갖는 이름 있는 boolean flag들의 bitset을 YAML로 정의하고, WebKit이 런타임에 이를 비활성화할 수 있도록 합니다. 이 flag들은 UIProcess의 singleton controller로부터 creation parameter와 새로운 SecurityFlagsDidChange IPC 메시지를 통해 privileged child process들로 전파됩니다.

이번 commit에서 들어온 것은 배관(plumbing)뿐입니다. Placeholder flag가 하나 존재하지만 이를 실제로 사용하는 코드는 없고, 서버 측 transport도 아직 없습니다. 기존에 다른 곳에서 쓰이던 것과 같은 creation-parameters/live-update 패턴(SharedPreferencesForWebProcessDidChange)을 그대로 따르지만, connection 단위 scoping과 reply acknowledgment는 의도적으로 생략되어 있습니다. 대신 업데이트가 도착하기 전까지는 fail-safe 상태, 즉 항상 enforced 상태를 기본값으로 유지합니다. 저장 방식은 WTF::BitSet을 validating factory를 통해 고정 폭 wire format으로 직렬화하는 형태이며, 읽기는 relaxed atomic word load로 이루어집니다. 이 덕분에 GPU process의 stream receiver가 non-main thread에서도 lock 없이 flag를 확인할 수 있습니다.

이는 배포된 보안 패치를 소프트웨어 업데이트 없이 현장에서 비활성화할 수 있게 해주는, WebKit 쪽 remote kill-switch의 절반에 해당합니다. 강력한 기능이지만 양날의 검이기도 한데, mitigation의 enforcement 자체를 원격으로 끌 수 있다는 의미이기 때문입니다. WebContent process는 이미 compromise된 것으로 취급되어 의도적으로 이 메커니즘에서 제외되어 있고, disable API는 ENGINEERING_BUILD 뒤에 게이트되어 있습니다. 따라서 다른 곳의 process-trust assumption을 살펴보기 전에, 먼저 이 두 경계를 이해해둘 필요가 있습니다.

가장 직접적으로 이어지는 패턴은, connection scoping도 acknowledgment도 없이 security-relevant state를 변경하는 live-update IPC 메시지입니다. 좁게 보면, SecurityFlagsDidChangeSharedPreferences 계열 메시지와 달리 ProcessIdentifier scoping도 reply도 갖고 있지 않습니다. 여기서 눈여겨봐야 할 신호는, 어느 connection이 보냈는지 확인하지 않고 process-global singleton에 그대로 값을 기록하는 message receiver입니다. 조금 더 넓혀 보면, UIProcess에서 child로 향하는 다른 live-update 메시지들 — SharedPreferencesForWebProcessDidChange, sandbox-extension grant, sandbox parameter update 등 — 도 global policy state를 변경하는 만큼, 같은 scoping 공백이 있는지 하나씩 점검해볼 필요가 있습니다. Compromise된 privileged process가 이런 메시지 중 하나를 자기 자신이나 sibling process에게 보낼 수 있는 구조인지가 핵심적으로 찾아야 할 형태입니다. 가장 넓게 보면, wire-format validating factory는 "어떤 flag에도 속하지 않는 bit를 가진 값을 거부한다"는 일반적인 패턴을 구현하고 있습니다. 이 패턴과 유사한 다른 구현들도 실제 container의 word width 대비 off-by-one 오류가 없는지 점검할 필요가 있는데, flag 목록의 개수가 word size의 배수가 아닌 순간 bitset의 word 개수와 선언된 bit 개수가 어긋나기 때문입니다. 아울러 ENGINEERING_BUILD처럼 disable SPI를 막는 build-configuration gate가 사용자에게 배포되는 모든 configuration에서 일관되게 적용되는지도 확인해야 합니다. 한 build system에서만 강제되고 다른 곳에서는 그렇지 않은 gate는 사실상 gate가 없는 것과 다르지 않기 때문입니다.