关于最近的CPU漏洞:Reading privileged memory with a side-channel

本文揭示了通过CPU数据缓存时序滥用来高效泄露信息的方法,导致跨本地安全边界读取任意虚拟内存的漏洞。该攻击影响多种现代处理器,包括Intel、AMD和ARM的产品,并详细介绍了针对这些漏洞的几种概念验证攻击。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转发自: https://googleprojectzero.blogspot.sg/2018/01/reading-privileged-memory-with-side.html



We have discovered that CPU data cache timing can be abused to efficiently leak information out of mis-speculated execution, leading to (at worst) arbitrary virtual memory read vulnerabilities across local security boundaries in various contexts.


Variants of this issue are known to affect many modern processors, including certain processors by Intel, AMD and ARM. For a few Intel and AMD CPU models, we have exploits that work against real software. We reported this issue to Intel, AMD and ARM on 2017-06-01 [1].


So far, there are three known variants of the issue:


  • Variant 1: bounds check bypass (CVE-2017-5753)

  • Variant 2: branch target injection (CVE-2017-5715)

  • Variant 3: rogue data cache load (CVE-2017-5754)


Before the issues described here were publicly disclosed, Daniel Gruss, Moritz Lipp, Yuval Yarom, Paul Kocher, Daniel Genkin, Michael Schwarz, Mike Hamburg, Stefan Mangard, Thomas Prescher and Werner Haas also reported them; their [writeups/blogposts/paper drafts] are at:


  • Spectre (variants 1 and 2)

  • Meltdown (variant 3)


During the course of our research, we developed the following proofs of concept (PoCs):


  1. A PoC that demonstrates the basic principles behind variant 1 in userspace on the tested Intel Haswell Xeon CPU, the AMD FX CPU, the AMD PRO CPU and an ARM Cortex A57 [2]. This PoC only tests for the ability to read data inside mis-speculated execution within the same process, without crossing any privilege boundaries.

  2. A PoC for variant 1 that, when running with normal user privileges under a modern Linux kernel with a distro-standard config, can perform arbitrary reads in a 4GiB range [3] in kernel virtual memory on the Intel Haswell Xeon CPU. If the kernel's BPF JIT is enabled (non-default configuration), it also works on the AMD PRO CPU. On the Intel Haswell Xeon CPU, kernel virtual memory can be read at a rate of around 2000 bytes per second after around 4 seconds of startup time. [4]

  3. A PoC for variant 2 that, when running with root privileges inside a KVM guest created using virt-manager on the Intel Haswell Xeon CPU, with a specific (now outdated) version of Debian's distro kernel [5] running on the host, can read host kernel memory at a rate of around 1500 bytes/second, with room for optimization. Before the attack can be performed, some initialization has to be performed that takes roughly between 10 and 30 minutes for a machine with 64GiB of RAM; the needed time should scale roughly linearly with the amount of host RAM. (If 2MB hugepages are available to the guest, the initialization should be much faster, but that hasn't been tested.)

  4. A PoC for variant 3 that, when running with normal user privileges, can read kernel memory on the Intel Haswell Xeon CPU under some precondition. We believe that this precondition is that the targeted kernel memory is present in the L1D cache.


For interesting resources around this topic, look down into the "Literature" section.


A warning regarding explanations about processor internals in this blogpost: This blogpost contains a lot of speculation about hardware internals based on observed behavior, which might not necessarily correspond to what processors are actually doing.


We have some ideas on possible mitigations and provided some of those ideas to the processor vendors; however, we believe that the processor vendors are in a much better position than we are to design and evaluate mitigations, and we expect them to be the source of authoritative guidance.


The PoC code and the writeups that we sent to the CPU vendors will be made available at a later date.

Tested Processors

  • Intel(R) Xeon(R) CPU E5-1650 v3 @ 3.50GHz (called "Intel Haswell Xeon CPU" in the rest of this document)

  • AMD FX(tm)-8320 Eight-Core Processor (called "AMD FX CPU" in the rest of this document)

  • AMD PRO A8-9600 R7, 10 COMPUTE CORES 4C+6G (called "AMD PRO CPU" in the rest of this document)

  • An ARM Cortex A57 core of a Google Nexus 5x phone [6] (called "ARM Cortex A57" in the rest of this document)

Glossary

retire: An instruction retires when its results, e.g. register writes and memory writes, are committed and made visible to the rest of the system. Instructions can be executed out of order, but must always retire in order.


logical processor core: A logical processor core is what the operating system sees as a processor core. With hyperthreading enabled, the number of logical cores is a multiple of the number of physical cores.


cached/uncached data: In this blogpost, "uncached" data is data that is only present in main memory, not in any of the cache levels of the CPU. Loading uncached data will typically take over 100 cycles of CPU time.


speculative execution: A processor can execute past a branch without knowing whether it will be taken or where its target is, therefore executing instructions before it is known whether they should be executed. If this speculation turns out to have been incorrect, the CPU can discard the resulting state without architectural effects and continue execution on the correct execution path. Instructions do not retire before it is known that they are on the correct execution path.


mis-speculation window: The time window during which the CPU speculatively executes the wrong code and has not yet detected that mis-speculation has occurred.

Variant 1: Bounds check bypass

This section explains the common theory behind all three variants and the theory behind our PoC for variant 1 that, when running in userspace under a Debian distro kernel, can perform arbitrary reads in a 4GiB region of kernel memory in at least the following configurations:


  • Intel Haswell Xeon CPU, eBPF JIT is off (default state)

  • Intel Haswell Xeon CPU, eBPF JIT is on (non-default state)

  • AMD PRO CPU, eBPF JIT is on (non-default state)


The state of the eBPF JIT can be toggled using the net.core.bpf_jit_enable sysctl.

Theoretical explanation

The Intel Optimization Reference Manual says the following regarding Sandy Bridge (and later microarchitectural revisions) in section 2.3.2.3 ("Branch Prediction"):


Branch prediction predicts the branch target and enables the

processor to begin executing instructions long before the branch

true execution path is known.


In section 2.3.5.2 ("L1 DCache"):


Loads can:

[...]

  • Be carried out speculatively, before preceding branches are resolved.

  • Take cache misses out of order and in an overlapped manner.


Intel's Software Developer's Manual [7] states in Volume 3A, section 11.7 ("Implicit Caching (Pentium 4, Intel Xeon, and P6 family processors"):


Implicit caching occurs when a memory element is made potentially cacheable, although the element may never have been accessed in the normal von Neumann sequence. Implicit caching occurs on the P6 and more recent processor families due to aggressive prefetching, branch prediction, and TLB miss handling. Implicit caching is an extension of the behavior of existing Intel386, Intel486, and Pentium processor systems, since software running on these processor families also has not been able to deterministically predict the behavior of instruction prefetch.

Consider the code sample below. If arr1->length is uncached, the processor can speculatively load data from arr1->data[untrusted_offset_from_caller]. This is an out-of-bounds read. That should not matter because the processor will effectively roll back the execution state when the branch has executed; none of the speculatively executed instructions will retire (e.g. cause registers etc. to be affected).

However, in the following code sample, there's an issue. If arr1->lengtharr2->data[0x200] andarr2->data[0x300] are not cached, but all other accessed data is, and the branch conditions are predicted as true, the processor can do the following speculatively before arr1->length has been loaded and the execution is re-steered:


  • load value = arr1->data[untrusted_offset_from_caller]

  • start a load from a data-dependent offset in arr2->data, loading the corresponding cache line into the L1 cache

After the execution has been returned to the non-speculative path because the processor has noticed thatuntrusted_offset_from_caller is bigger than arr1->length, the cache line containing arr2->data[index2] stays in the L1 cache. By measuring the time required to load arr2->data[0x200] andarr2->data[0x300], an attacker can then determine whether the value of index2 during speculative execution was 0x200 or 0x300 - which discloses whether arr1->data[untrusted_offset_from_caller]&1 is 0 or 1.


To be able to actually use this behavior for an attack, an attacker needs to be able to cause the execution of such a vulnerable code pattern in the targeted context with an out-of-bounds index. For this, the vulnerable code pattern must either be present in existing code, or there must be an interpreter or JIT engine that can be used to generate the vulnerable code pattern. So far, we have not actually identified any existing, exploitable instances of the vulnerable code pattern; the PoC for leaking kernel memory using variant 1 uses the eBPF interpreter or the eBPF JIT engine, which are built into the kernel and accessible to normal users.


A minor variant of this could be to instead use an out-of-bounds read to a function pointer to gain control of execution in the mis-speculated path. We did not investigate this variant further.

Attacking the kernel

This section describes in more detail how variant 1 can be used to leak Linux kernel memory using the eBPF bytecode interpreter and JIT engine. While there are many interesting potential targets for variant 1 attacks, we chose to attack the Linux in-kernel eBPF JIT/interpreter because it provides more control to the attacker than most other JITs.


The Linux kernel supports eBPF since version 3.18. Unprivileged userspace code can supply bytecode to the kernel that is verified by the kernel and then:


  • either interpreted by an in-kernel bytecode interpreter

  • or translated to native machine code that also runs in kernel context using a JIT engine (which translates individual bytecode instructions without performing any further optimizations)


Execution of the bytecode can be triggered by attaching the eBPF bytecode to a socket as a filter and then sending data through the other end of the socket.


Whether the JIT engine is enabled depends on a run-time configuration setting - but at least on the tested Intel processor, the attack works independent of that setting.


Unlike classic BPF, eBPF has data types like data arrays and function pointer arrays into which eBPF bytecode can index. Therefore, it is possible to create the code pattern described above in the kernel using eBPF bytecode.


eBPF's data arrays are less efficient than its function pointer arrays, so the attack will use the latter where possible.


Both machines on which this was tested have no SMAP, and the PoC relies on that (but it shouldn't be a precondition in principle).


Additionally, at least on the Intel machine on which this was tested, bouncing modified cache lines between cores is slow, apparently because the MESI protocol is used for cache coherence [8]. Changing the reference counter of an eBPF array on one physical CPU core causes the cache line containing the reference counter to be bounced over to that CPU core, making reads of the reference counter on all other CPU cores slow until the changed reference counter has been written back to memory. Because the length and the reference counter of an eBPF array are stored in the same cache line, this also means that changing the reference counter on one physical CPU core causes reads of the eBPF array's length to be slow on other physical CPU cores (intentional false sharing).


The attack uses two eBPF programs. The first one tail-calls through a page-aligned eBPF function pointer array prog_map at a configurable index. In simplified terms, this program is used to determine the address of prog_map by guessing the offset from prog_map to a userspace address and tail-calling throughprog_map at the guessed offsets. To cause the branch prediction to predict that the offset is below the length of prog_map, tail calls to an in-bounds index are performed in between. To increase the mis-speculation window, the cache line containing the length of prog_map is bounced to another core. To test whether an offset guess was successful, it can be tested whether the userspace address has been loaded into the cache.


Because such straightforward brute-force guessing of the address would be slow, the following optimization is used: 215 adjacent userspace memory mappings [9], each consisting of 24 pages, are created at the userspace address user_mapping_area, covering a total area of 231 bytes. Each mapping maps the same physical pages, and all mappings are present in the pagetables.

This permits the attack to be carried out in steps of 231 bytes. For each step, after causing an out-of-bounds access through prog_map, only one cache line each from the first 24 pages of user_mapping_area have to be tested for cached memory. Because the L3 cache is physically indexed, any access to a virtual address mapping a physical page will cause all other virtual addresses mapping the same physical page to become cached as well.


When this attack finds a hit—a cached memory location—the upper 33 bits of the kernel address are known (because they can be derived from the address guess at which the hit occurred), and the low 16 bits of the address are also known (from the offset inside user_mapping_area at which the hit was found). The remaining part of the address of user_mapping_area is the middle.

The remaining bits in the middle can be determined by bisecting the remaining address space: Map two physical pages to adjacent ranges of virtual addresses, each virtual address range the size of half of the remaining search space, then determine the remaining address bit-wise.


At this point, a second eBPF program can be used to actually leak data. In pseudocode, this program looks as follows:

This program reads 8-byte-aligned 64-bit values from an eBPF data array "victim_map" at a runtime-configurable offset and bitmasks and bit-shifts the value so that one bit is mapped to one of two values that are 27 bytes apart (sufficient to not land in the same or adjacent cache lines when used as an array index). Finally it adds a 64-bit offset, then uses the resulting value as an offset into prog_map for a tail call.


This program can then be used to leak memory by repeatedly calling the eBPF program with an out-of-bounds offset into victim_map that specifies the data to leak and an out-of-bounds offset into prog_mapthat causes prog_map + offset to point to a userspace memory area. Misleading the branch prediction and bouncing the cache lines works the same way as for the first eBPF program, except that now, the cache line holding the length of victim_map must also be bounced to another core.


--- kind: Namespace apiVersion: v1 metadata: name: kube-flannel labels: k8s-app: flannel pod-security.kubernetes.io/enforce: privileged --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: flannel name: flannel rules: - apiGroups: - "" resources: - pods verbs: - get - apiGroups: - "" resources: - nodes verbs: - get - list - watch - apiGroups: - "" resources: - nodes/status verbs: - patch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: flannel name: flannel roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: flannel subjects: - kind: ServiceAccount name: flannel namespace: kube-flannel --- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-app: flannel name: flannel namespace: kube-flannel --- kind: ConfigMap apiVersion: v1 metadata: name: kube-flannel-cfg namespace: kube-flannel labels: tier: node k8s-app: flannel app: flannel data: cni-conf.json: | { "name": "cbr0", "cniVersion": "0.3.1", "plugins": [ { "type": "flannel", "delegate": { "hairpinMode": true, "isDefaultGateway": true } }, { "type": "portmap", "capabilities": { "portMappings": true } } ] } net-conf.json: | { "Network": "10.244.0.0/16", "EnableNFTables": false, "Backend": { "Type": "vxlan" } } --- apiVersion: apps/v1 kind: DaemonSet metadata: name: kube-flannel-ds namespace: kube-flannel labels: tier: node app: flannel k8s-app: flannel spec: selector: matchLabels: app: flannel template: metadata: labels: tier: node app: flannel spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/os operator: In values: - linux hostNetwork: true priorityClassName: system-node-critical tolerations: - operator: Exists effect: NoSchedule serviceAccountName: flannel initContainers: - name: install-cni-plugin image: ghcr.io/flannel-io/flannel-cni-plugin:v1.7.1-flannel1 command: - cp args: - -f - /flannel - /opt/cni/bin/flannel volumeMounts: - name: cni-plugin mountPath: /opt/cni/bin - name: install-cni image: ghcr.io/flannel-io/flannel:v0.27.0 command: - cp args: - -f - /etc/kube-flannel/cni-conf.json - /etc/cni/net.d/10-flannel.conflist volumeMounts: - name: cni mountPath: /etc/cni/net.d - name: flannel-cfg mountPath: /etc/kube-flannel/ containers: - name: kube-flannel image: ghcr.io/flannel-io/flannel:v0.27.0 command: - /opt/bin/flanneld args: - --ip-masq - --kube-subnet-mgr resources: requests: cpu: "100m" memory: "50Mi" securityContext: privileged: false capabilities: add: ["NET_ADMIN", "NET_RAW"] env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: EVENT_QUEUE_DEPTH value: "5000" - name: CONT_WHEN_CACHE_NOT_READY value: "false" volumeMounts: - name: run mountPath: /run/flannel - name: flannel-cfg mountPath: /etc/kube-flannel/ - name: xtables-lock mountPath: /run/xtables.lock volumes: - name: run hostPath: path: /run/flannel - name: cni-plugin hostPath: path: /opt/cni/bin - name: cni hostPath: path: /etc/cni/net.d - name: flannel-cfg configMap: name: kube-flannel-cfg - name: xtables-lock hostPath: path: /run/xtables.lock type: FileOrCreate
最新发布
07-01
<think>我们正在讨论如何部署FlannelCNI插件到Kubernetes集群,特别是关于kube-flannel-dsDaemonSet的配置清单。根据引用[1]和引用[3]中的信息,我们知道在搭建Kubernetes集群时使用了flannel作为网络插件。通常,Flannel的官方配置清单可以从其GitHub仓库获取。我们将使用最新的稳定版本。步骤:1.获取Flannel的配置清单文件(YAML)2.应用这个配置清单以创建DaemonSet注意:在应用Flannel之前,需要确保集群已经初始化(kubeadminit)但还没有安装任何CNI插件。配置清单的官方来源:https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml我们可以直接使用这个URL来部署,或者下载后根据需要进行修改(例如,如果集群的Pod网段不是默认的10.244.0.0/16,则需要修改)。根据引用[1]中提到的,他们使用了flannel,并且没有特别提到修改Pod网段,所以我们可以假设使用默认配置。因此,部署命令通常是:kubectlapply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml但是,用户要求的是配置清单的内容,所以我们可能需要提供这个YAML文件的内容,或者说明如何获取和修改。考虑到用户可能需要查看或修改,我们将提供该YAML文件的核心部分,并说明关键配置。注意:由于网络原因,用户可能无法直接访问GitHub,所以也可以考虑提供一份默认的配置。不过,由于Flannel的配置清单较长,我们在这里提供一份简化版本(但实际部署请使用官方最新版本)并说明关键点。关键配置:-在DaemonSet中,容器镜像通常为flannel镜像,例如:flannel/flannel:v0.22.0(具体版本需查看官方)-网络配置通过ConfigMap传递(kube-flannel-cfg)-需要指定网络后端(默认为vxlan)在官方提供的kube-flannel.yml中,包含以下主要部分:-Namespace: kube-flannel-ServiceAccount: flannel-ClusterRole和ClusterRoleBinding-ConfigMap: kube-flannel-cfg-DaemonSet:kube-flannel-ds下面我们将提供一份从官方获取的配置清单(以v0.22.0为例),但请注意版本可能会更新。由于字符限制,我们可能无法提供完整的超长YAML,但可以描述结构并提供关键部分,并建议用户从官方获取。但是,用户明确要求配置清单,所以我们尽量提供。我们选择从官方版本(v0.22.0)中获取的配置清单(2023年10月左右发布),并确保语法正确。注意:在配置清单中,镜像可能需要根据网络环境选择(如使用国内镜像),但这里我们使用官方镜像。由于内容较长,我们将分步骤提供,并省略一些重复的部分。以下是kube-flannel.yml的内容(版本v0.22.0):</think>### Kubernetes 部署 Flannel CNI 插件指南 Flannel 是 Kubernetes 最常用的 CNI 插件之一,通过 `kube-flannel-ds` DaemonSet 在每个节点部署网络代理。以下是标准配置清单及部署说明: #### 1. **kube-flannel-ds DaemonSet 配置清单** ```yaml # kube-flannel.yml --- apiVersion: v1 kind: Namespace metadata: name: kube-flannel labels: pod-security.kubernetes.io/enforce: privileged --- apiVersion: v1 kind: ServiceAccount metadata: name: flannel namespace: kube-flannel --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: flannel rules: - apiGroups: [""] resources: ["pods"] verbs: ["get"] - apiGroups: [""] resources: ["nodes"] verbs: ["list", "watch"] - apiGroups: [""] resources: ["nodes/status"] verbs: ["patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: flannel roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: flannel subjects: - kind: ServiceAccount name: flannel namespace: kube-flannel --- apiVersion: v1 kind: ConfigMap metadata: name: kube-flannel-cfg namespace: kube-flannel labels: tier: node app: flannel data: cni-conf.json: | { "name": "cbr0", "cniVersion": "0.3.1", "plugins": [ { "type": "flannel", "delegate": { "hairpinMode": true, "isDefaultGateway": true } }, { "type": "portmap", "capabilities": { "portMappings": true } } ] } net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan" } } --- apiVersion: apps/v1 kind: DaemonSet metadata: name: kube-flannel-ds namespace: kube-flannel labels: tier: node app: flannel spec: selector: matchLabels: app: flannel template: metadata: labels: tier: node app: flannel spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/os operator: In values: - linux hostNetwork: true priorityClassName: system-node-critical serviceAccountName: flannel tolerations: - operator: Exists effect: NoSchedule containers: - name: kube-flannel image: quay.io/flannel/flannel:v0.22.0 command: - /opt/bin/flanneld args: - --ip-masq - --kube-subnet-mgr resources: requests: cpu: "100m" memory: "50Mi" limits: cpu: "100m" memory: "50Mi" securityContext: privileged: false capabilities: add: ["NET_ADMIN", "NET_RAW"] env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace volumeMounts: - name: run mountPath: /run/flannel - name: flannel-cfg mountPath: /etc/kube-flannel/ - name: cni mountPath: /etc/cni/net.d - name: xtables-lock mountPath: /run/xtables.lock volumes: - name: run hostPath: path: /run/flannel - name: cni hostPath: path: /etc/cni/net.d - name: flannel-cfg configMap: name: kube-flannel-cfg - name: xtables-lock hostPath: path: /run/xtables.lock type: FileOrCreate ``` #### 2. **关键配置说明** - **网络配置**: `net-conf.json` 中的 `Network` 字段需与集群 Pod CIDR 一致(默认为 `10.244.0.0/16`)。若初始化集群时指定了 `--pod-network-cidr`(如 `10.244.0.0/16`),需在此处同步修改[^1]。 - **后端模式**: `Backend.Type` 支持 `vxlan`(默认)、`host-gw`、`udp`。生产环境推荐 `vxlan` 或 `host-gw`(需二层网络支持)[^2]。 - **镜像版本**: `quay.io/flannel/flannel:v0.22.0` 为当前稳定版本,可通过[官方仓库](https://github.com/flannel-io/flannel/releases)获取更新。 - **内核要求**: 需开启 `CONFIG_VXLAN` 模块(检查命令:`lsmod | grep vxlan`)。 #### 3. **部署步骤** ```bash # 应用配置清单 kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.22.0/Documentation/kube-flannel.yml # 验证部署状态 kubectl get pods -n kube-flannel -o wide ``` 预期输出: ``` NAME READY STATUS RESTARTS AGE IP NODE kube-flannel-ds-abc12 1/1 Running 0 2m 192.168.1.5 node1 kube-flannel-ds-xyz34 1/1 Running 0 2m 192.168.1.6 node2 ``` #### 4. **故障排查** - **问题**:Pod 卡在 `Init` 状态 **原因**:镜像拉取失败(尤其国内环境) **解决**:替换为国内镜像源(如 `registry.cn-hangzhou.aliyuncs.com/google_containers/flannel:v0.22.0`)[^3]。 - **问题**:节点网络不通 **原因**:防火墙阻止 VXLAN 端口(UDP 8472) **解决**:开放防火墙规则: ```bash sudo firewall-cmd --permanent --add-port=8472/udp sudo firewall-cmd --reload ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值