title: Linux seccomp-BPF 安全沙箱与系统调用隔离深度实战:从 Chrome 沙箱到容器运行时安全加固

category: 系统编程

keywords: Linux, seccomp, seccomp-BPF, 系统调用, 沙箱, 容器安全, Landlock, Docker, Chrome Sandbox, Namespaces, Capabilities, SECCOMP_RET, seccomp_unotify, libseccomp

description: 深度解析 Linux seccomp-BPF 沙箱机制:BPF 过滤器架构演进(strict→filter→unotify)、SECCOMP_RET 五大返回值行为语义、多级调度器架构(KILL/ERRNO/TRACE/ALLOW/LOG)、跨架构多 syscall 处理、libseccomp/自制 BPF 生成器对比生产使用、Docker/containerd 容器运行时安全配置、Chrome 分级沙箱实现、systemd 单元保护、用户态通知(seccomp unotify/seccomp_notify_fd)与 Landlock LSM 联合使用、系统调用拦截性能开销评估与最佳实践。


Linux seccomp-BPF 安全沙箱与系统调用隔离深度实战

1. 概述:现代沙箱的基础层

在云原生时代,容器逃逸、漏洞利用链已常态化。seccomp-BPF(Secure Computing with Berkeley Packet Filter)是 Linux 内核中最精细的系统调用过滤机制,为 Docker、containerd、Chrome、Firejail、systemd、Flatpak 等重量级项目提供底层安全保障。

与Capabilities(按能力维度隔离)和Namespaces(按资源视图隔离)不同,seccomp-BPF 直接以系统调用为粒度进行拦截和过滤,是纵深防御中最后也是最内层的一道防线。

本文从内核实现到生产部署,深入解析 seccomp-BPF 的架构设计、使用方式与最佳实践。

2. 历史演进与设计哲学

2.1 三代 safe computing 模式

版本 引入版本 模式 行为
seccomp 1 Linux 2.6.12 (2005) STRICT 仅允许 read/write/exit/sigreturn
seccomp 2 Linux 3.5 (2012) FILTER (BPF) 通过 BPF 自定义过滤规则
seccomp unotify Linux 5.0 (2019) USER_NOTIF 用户态处理不可拦截的 syscall

STRICT 模式极为有限,仅 4 个 syscall 可用。实际广泛使用的是 FILTER 模式配合 BPF 程序。

2.2 为什么用 BPF 而不是白名单枚举?

传统白名单:"I/O 相关允许,网络相关拦截"——太粗糙。

BPF 优势:

  • 参数级过滤:只拦截 write() 但仅限制目标 fd
  • 架构感知:同一进程的 x86_64 和 compイル arch 可设置不同规则
  • 可组合:多层过滤器逻辑组合
  • 高性能:内核 JIT 编译 BPF,近乎零开销

2.3 安全保证(不可绕过性)

seccomp 通过 TIF_SECCOMP 标志和 syscall_enter 路径中的检查点实现:

  • 用户不能修改自己的 seccomp 过滤器(PR_SET_SECCOMP 只能设置一次,除非 CAP_SYS_ADMIN)
  • PR_SET_NO_NEW_PRIVS 阻止子进程获取更高权限
  • 过滤器在 execve() 后继续生效

3. BPF 过滤器架构详解

3.1 seccomp 数据的内存布局


struct seccomp_data {
    int nr;                     // 系统调用号
    __u32 arch;                 // 架构标识 (AUDIT_ARCH_X86_64 等)
    __u64 instruction_pointer;  // 调用地址
    __u64 args[6];              // 6 个参数 (arg0-arg5)
};

BPF 程序通过 seccomp_data 结构获取系统调用上下文,不能直接访问用户内存(即 args 中的指针不能直接解引用)。

3.2 BPF 指令集与辅助函数

seccomp 使用 cBPF(经典 BPF),非 eBPF:


// 允许的 BPF 指令子集
BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, arch))
BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, nr))
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, AUDIT_ARCH_X86_64, 1, 0)

只能使用的辅助函数:

  • BPF_STLD / BPF_LDX — 加载指令
  • BPF_ALU — 算术运算
  • BPF_JMP — 条件跳转
  • BPF_RET — 返回动作值

3.3 返回值语义(核心决策引擎)

每个 BPF 过滤器终止于 BPF_RET|BPF_K,返回值的高 16 位(SECCOMP_RET_DATA)携带额外信息:

返回值 宏定义 行为
0x7FFC0000 SECCOMP_RET_ALLOW 允许 syscall,继续执行
0x00050000 SECCOMP_RET_ERRNO(E) 拒绝,设置 errno,返回 -1
0x00000000 SECCOMP_RET_KILL_PROCESS 杀死进程(SIGSYS)
0x00030000 SECCOMP_RET_KILL_THREAD 杀死当前线程
0x7FF00000 SECCOMP_RET_TRACE 触发 ptrace 通知
0x7FF70000 SECCOMP_RET_LOG 允许但记录日志
0x7FC00000 SECCOMP_RET_USER_NOTIF 发送事件到用户态 fd

注意:SECCOMP_RET_TRACE 与 SECCOMP_RET_USER_NOTIF 行为不同:

- TRACE 必须由 ptrace tracer 接管处理

- USER_NOTIF 允许用户态监听 fd 后异步决策

4. 现代实践:三大使用模式

4.1 模式一:快速上手(伪代码框架)


#!/usr/bin/env python3
"""
seccomp-BFP 基础演示:仅允许 read/write/exit 的 STL mode 2 实现
"""
import ctypes, ctypes.util, struct, sys, os

libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
PTRACE = ctypes.CDLL('libpthread.so.0')

# 常量定义
PR_SET_SECCOMP = 22
PR_SET_NO_NEW_PRIVS = 38
SECCOMP_MODE_FILTER = 2
SECCOMP_RET_KILL = 0x00000000
SECCOMP_RET_ALLOW = 0x7FFF0000
AUDIT_ARCH_X86_64 = 0xC000003E
SYS_read = 0
SYS_write = 1
SYS_exit = 60
SYS_exit_group = 231

def install_seccomp_filter():
    """安装 BPF 过滤器"""
    # BPF 伪指令
    bpf = [
        # 检查架构是否为 x86_64
        (0x20, 0, 0, 4),                    # ld [arch]
        (0x15, 0, 8, AUDIT_ARCH_X86_64),    # jeq AUDIT_ARCH_X86_64, +8
        (0x06, 0, 0, SECCOMP_RET_KILL),     # 非 x86_64, kill
        
        # 加载 syscall 号
        (0x20, 0, 0, 0),                    # ld [nr]
        
        # 检查 read (0)
        (0x15, 0, 1, SYS_read),             # jeq read, +1
        # 检查 write (1)
        (0x15, 0, 1, SYS_write),            # jeq write, +1
        # 检查 exit (60)
        (0x15, 0, 1, SYS_exit),             # jeq exit, +1
        # 检查 exit_group (231)
        (0x15, 0, 1, SYS_exit_group),       # jeq exit_group, +1
        # 其他系统调用, kill
        (0x06, 0, 0, SECCOMP_RET_KILL),     # ret KILL
        
        # 允许
        (0x06, 0, 0, SECCOMP_RET_ALLOW),    # ret ALLOW
        (0x06, 0, 0, SECCOMP_RET_ALLOW),
        (0x06, 0, 0, SECCOMP_RET_ALLOW),
        (0x06, 0, 0, SECCOMP_RET_ALLOW),
    ]
    
    # 编码为二进制
    bpf_prog = b''.join(struct.pack('HBBI', *insn) for insn in bpf)
    
    # 构造 sock_fprog
    class sock_fprog(ctypes.Structure):
        _fields_ = [('len', ctypes.c_ushort), ('filter', ctypes.POINTER(ctypes.c_ushort))]
    
    prog = sock_fprog(len(bpf), (ctypes.c_ushort * len(bpf)).from_buffer_copy(bpf_prog))
    
    # 设置 no_new_privs
    libc.prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
    
    # 安装过滤器
    result = libc.prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ctypes.byref(prog), 0, 0)
    if result != 0:
        print(f"prctl failed", file=sys.stderr)
        os._exit(1)
    print("Seccomp 过滤器安装成功")
    
    # 以下操作成功
    os.write(1, b"Hello from sandbox!\n")
    # 以下操作会导致进程被 kill
    # os.getpid()  # 被拦截 → SIGSYS

if __name__ == '__main__':
    install_seccomp_filter()

4.2 模式二:libseccomp(生产推荐)


/* 使用 libseccomp 构建容器沙箱过滤器 */
#include <seccomp.h>
#include <stdio.h>
#include <stdlib.h>

int apply_seccomp_filter(int target) {
    scmp_filter_ctx ctx;
    int rc;
    
    /* 创建上下文:默认动作 */
    ctx = seccomp_init(target);
    if (ctx == NULL) return -1;
    
    /* 设置默认 */
    /* 方案A: 默认拒绝,白名单放行 */
    /* 方案B: 默认允许,黑名单拦截 */
    
    /* 方案A 示例:默认 kill,允许基础 syscall */
    ctx = seccomp_init(SCMP_ACT_KILL_PROCESS);
    
    /* 基础永远允许的 */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
    
    /* 信号相关 */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sigaltstack), 0);
    
    /* 条件过滤:仅允许 stdout/stderr 的 write */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
        SCMP_A0(SCMP_CMP_EQ, 1)  /* stdout */
    );
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
        SCMP_A0(SCMP_CMP_EQ, 2)  /* stderr */
    );
    
    /* mmap 条件过滤:仅允许匿名映射 */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 1,
        SCMP_A3(SCMP_CMP_MASKED_EQ, MAP_ANONYMOUS, MAP_ANONYMOUS)
    );
    
    /* 拦截会修改进程属性的 syscall */
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(ptrace), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(mount), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(umount2), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(reboot), 0);
    
    /* 加载 */
    rc = seccomp_load(ctx);
    seccomp_release(ctx);
    return rc;
}

4.3 模式三:seccomp unotify — 用户态决策


/* 使用 seccomp unotify 实现用户态 syscall 代理 */
#include <sys/ioctl.h>
#include <linux/seccomp.h>
#include <poll.h>

#define SECCOMP_IOCTL_NOTIF_ID_VALID  0x40082100U
#define SECCOMP_IOCTL_NOTIF_RECV      0x40082102U
#define SECCOMP_IOCTL_NOTIF_SEND      0xc0082102U

struct seccomp_notif {
    __u64 id;
    __u32 pid;
    __u32 flags;
    struct seccomp_data data;
};

struct seccomp_notif_resp {
    __u64 id;
    __s64 val;
    __s32 error;
    __u32 flags;
};

/* 工作线程:监听并代理 syscall */
int seccomp_notif_worker(int notify_fd) {
    struct seccomp_notif req;
    struct seccomp_notif_resp resp;
    
    while (1) {
        memset(&req, 0, sizeof(req));
        /* 阻塞等待事件 */
        if (ioctl(notify_fd, SECCOMP_IOCTL_NOTIF_RECV, &req) < 0)
            continue;
        
        memset(&resp, 0, sizeof(resp));
        resp.id = req.id;
        resp.flags = 0;
        
        /* 根据 syscall 号执行用户态逻辑 */
        switch (req.data.nr) {
        case __NR_openat: {
            /* 代理 openat:检查路径合法性 */
            int dirfd = req.data.args[0];
            const char *path = (const char *)(uintptr_t) req.data.args[1];
            int flags = req.data.args[2];
            mode_t mode = req.data.args[3];
            
            /* 用户态读取路径字符串 */
            char buf[PATH_MAX];
            int ret = read_path_from_pid(req.pid, dirfd, path, buf, sizeof(buf));
            
            if (is_path_allowed(buf)) {
                /* 在用户态执行 openat */
                int fd = openat(dirfd, path, flags, mode);
                resp.val = fd;
                resp.error = fd < 0 ? errno : 0;
            } else {
                resp.error = EACCES;
            }
            break;
        }
        case __NR_connect: {
            /* 代理 connect:检查目标 */
            int sockfd = req.data.args[0];
            struct sockaddr *addr = (void*)(uintptr_t) req.data.args[1];
            socklen_t addrlen = req.data.args[2];
            
            /* 解析地址并决策 */
            if (is_connection_allowed(addr, addrlen)) {
                /* 用户态执行 connect */
                resp.val = connect(sockfd, addr, addrlen);
                resp.error = errno;
            } else {
                resp.error = ECONNREFUSED;
            }
            break;
        }
        default:
            resp.error = ENOSYS;
            break;
        }
        
        /* 回复决策 */
        ioctl(notify_fd, SECCOMP_IOCTL_NOTIF_SEND, &resp);
    }
}

5. 容器场景:运行时安全的基石

5.1 Docker 的默认 seccomp 配置文件

Docker 内置的默认 seccomp profile(default.json)阻塞了约 44 个危险 syscall,主要包括:

类别 被拦截的 syscall 原因
内核模块 create_module, init_module, finit_module, delete_module 防止内核模块注入
系统管理 setns, unshare, kexec_load, reboot 防止容器逃逸
时钟操作 clock_settime, clock_adjtime, adjtimex 防止时间篡改
底层 I/O lookup_dcookie, kcmp, process_vm_readv 防止进程间侦察
审计控制 acct, syslog, syslog 防止审计旁路

5.2 自定义 Docker seccomp Profile


{
    "defaultAction": "SCMP_ACT_ERRNO",
    "architectures": ["SCMP_ARCH_X86_64"],
    "syscalls": [
        {
            "names": ["read", "write", "close", "exit", "exit_group", "rt_sigreturn"],
            "action": "SCMP_ACT_ALLOW"
        },
        {
            "names": ["write"],
            "action": "SCMP_ACT_ALLOW",
            "args": [
                { "index": 0, "value": 1, "op": "SCMP_CMP_EQ" },
                { "index": 0, "value": 2, "op": "SCMP_CMP_EQ" }
            ]
        },
        {
            "names": ["openat"],
            "action": "SCMP_ACT_ALLOW",
            "args": [
                { "index": 1, "value": "/app/data", "op": "SCMP_CMP_MASKED_EQ" }
            ]
        },
        {
            "names": ["ptrace", "mount", "umount2", "reboot", "kexec_load"],
            "action": "SCMP_ACT_KILL_PROCESS"
        }
    ]
}

5.3 containerd 与 Kubernetes Pod Security


apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  annotations:
    seccomp.security.alpha.kubernetes.io/pod: runtime/default
    # 或自定义 profile
    seccomp.security.alpha/kubernetes.io/pod: localhost/my-profile.json
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: my-app:latest
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]

6. Chrome 沙箱架构解析

Chrome 的多进程沙箱模型是 seccomp-BPF 最复杂且成熟的应用之一。

6.1 Linux 下的分层沙箱


层级 0: setuid sandbox (Chromium < 20 已废弃)
层级 1: Namespaces (PID/Network/Mount/User)
层级 2: seccomp-BPF (限制 syscall)
层级 3: RLIMIT 限制资源

6.2 Chrome seccomp 策略

Chrome 的渲染进程使用非常严格的 seccomp 策略:

  • 允许的基础 syscall 仅约 150 个(Linux x86_64)
  • 关键过滤:socket() 仅允许 AF_UNIX,open() 可读
  • 通过 perf_event_open 获取性能计数器的特定过滤规则

6.3 自定义生成 BPF

Chrome 使用自己的 BPF 代码生成器(非 libseccomp),位于 sandbox/linux/seccomp-bpf/:

  • bpf_gpu_policy_linux.c — GPU 进程沙箱
  • bpf_renderer_policy_linux.c — 渲染进程沙箱
  • bpf_ppapi_policy_linux.c — PPAPI 插件沙箱

7. 现代化替代方案:Landlock LSM

Landlock 是 Linux 5.13+ 引入的 unprivileged LSM,提供文件系统访问控制:


/* Landlock + seccomp 联合使用示例 */
#include <linux/landlock.h>
#include <sys/syscall.h>

int setup_landlock(void) {
    struct landlock_ruleset_attr ruleset_attr = {
        .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE |
                             LANDLOCK_ACCESS_FS_READ_FILE |
                             LANDLOCK_ACCESS_FS_WRITE_FILE |
                             LANDLOCK_ACCESS_FS_READ_DIR |
                             LANDLOCK_ACCESS_FS_MAKE_DIR,
    };
    
    int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
    
    /* 仅允许读取 /app 目录 */
    struct landlock_path_beneath_attr path_beneath = {
        .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR,
        .parent_fd = open("/app", O_PATH | O_CLOEXEC),
    };
    landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0);
    
    /* 禁止写入 */
    struct landlock_path_beneath_attr deny_write = {
        .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE,
        .parent_fd = open("/etc", O_PATH | O_CLOEXEC),
    };
    landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &deny_write, 0);
    
    landlock_restrict_self(ruleset_fd, 0);
    close(ruleset_fd);
    return 0;
}

设计模式:seccomp 控制 syscall 入口,Landlock 控制文件路径访问——两者正交结合。

8. 性能影响评估

8.1 系统调用开销对比

场景 每次 syscall 开销 说明
无 seccomp ~100ns 基准线
seccomp KILL (allow) ~100-120ns BPF JIT,几乎无感知
seccomp KILL (deny) ~200ns SIGSYS 路径更短
SECCOMP_RET_TRACE ~12μs ptrace 断点停止
SECCOMP_RET_USER_NOTIF ~60-160μs 涉及用户态上下文切换

数据来源:Phoronix 测试及 Linux 内核 seccomp_bpf benchmark。

8.2 优化建议

  1. 常用 syscall 放在过滤器前部(分支预测友好)
  2. 避免频繁触发 USER_NOTIF(同步跨进程开销大)
  3. 共享过滤器:SECCOMP_FILTER_FLAG_TSYNC 同步多线程过滤器
  4. 注意过滤器数量限制:默认 65536 BPF 指令(`/proc/sys/net/core/bpf_jit_enable`)
  5. JIT 开启:echo 1 > /proc/sys/net/core/bpf_jit_enable

9. 调试与监控

9.1 strace + seccomp 行为


# 查看 seccomp 过滤器安装
cat /proc/<PID>/status | grep -i seccomp
# 输出: Seccomp:    2  (filter mode)

# sysdig 监控 syscall
syscall=%{evt.type} proc=%{proc.name} res=%{evt.res}

# 查看内核日志被拦截的 syscall
dmesg | grep -i seccomp

9.2 日志记录(SECCOMP_RET_LOG)

配置审计日志:


# 在过滤器中返回 SECCOMP_RET_LOG 的 syscall 会被 audit 记录
auditctl -a always,exit -F arch=b64 -S all -F subj_type=myapp_t
journalctl -f _AUDIT_TYPE=1408  # AUDIT_SECCOMP

9.3 seccomp 事件追踪点


# 使用 bpftrace 追踪
bpftrace -e 'tracepoint:raw_syscalls:sys_enter /args->id == __NR_write/ { 
    printf("write called by %d\n", pid); 
}'

# 使用 perf trace
perf trace -e 'syscalls:sys_exit_*' --filter 'ret == -38'  # ENOSYS

10. 生产环境最佳实践

10.1 设计 seccomp 策略的 5 条准则

  1. 默认拒绝 + 白名单(SCMP_ACT_KILL / SCMP_ACT_ERRNO)
  2. 先读后写:先用 SECCOMP_RET_LOG 观察,确认无误杀后切换到拒绝
  3. 跨架构准备:检查 arch 字段,防止 64/32 位绕过
  4. 条件过滤:对 openat / bind / connect / ptrace 做参数过滤
  5. 组合防御:seccomp + Namespaces + Capabilities + Landlock + cgroup

10.2 常见陷阱

陷阱 后果 防护
未设置 NO_NEW_PRIVS 子进程通过 exec 恢复完整权限 始终先 prctl(PR_SET_NO_NEW_PRIVS, 1)
忽略 arch 检查 32 位进程绕过 64 位过滤器 先 load arch → jeq native
SECCOMP_RET_TRACE 未配置 tracer 进程被暂停不恢复 确保有 ptrace tracer 救援
忘记 execve() 兼容性 exec 新 bin 因 seccomp 崩溃 使用 SCMP_ALLOW 或调整策略

10.3 systemd 单元保护


[Service]
# 调用 systemd 内置的 seccomp 白名单
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@mount @reboot @swap @module @debug @privileged @resources

# 或完全自定义
SystemCallFilter=~clock_settime reboot kexec_load ptrace ...
SystemCallErrorNumber=EPERM

11. 总结

seccomp-BPF 是现代 Linux 安全纵深防御中不可或缺的一环:

  • 容器运行时:Docker/containerd 的默认 seccomp profile 是防止容器逃逸的关键层
  • 浏览器:Chrome 的沙箱策略保护数亿用户免受渲染进程漏洞影响
  • 桌面应用:Flatpak、Snap 使用 seccomp+BubbleWrap 实现应用隔离
  • 系统服务:systemd 的 SystemCallFilter 提供声明式的服务级保护

与 Namespaces(视图隔离)、Capabilities(权限拆分)、Landlock(文件系统访问控制)结合,seccomp-BFP 构成了内核安全控制的完整技术栈。

设计安全策略时,始终遵循"默认拒绝、审计过渡、多层防御"的原则,并在生产环境部署前通过 strace/bpftrace/sysdig 等工具充分测试 syscall 覆盖情况。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
/* 跳过导航链接 (无障碍) */ position: absolute; top: -100px; left: 15px; z-index: 99999; padding: 8px 16px; background: #007bff; color: #fff; font-size: 14px; border-radius: 0 0 4px 4px; text-decoration: none; transition: top 0.2s; } top: 0; outline: 3px solid #0056b3; }