日B视频 亚洲,啪啪啪网站一区二区,91色情精品久久,日日噜狠狠色综合久,超碰人妻少妇97在线,999青青视频,亚洲一区二卡,让本一区二区视频,日韩网站推荐

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

宋寶華: Linux為什么一定要copy_from_user ?

Linux閱碼場 ? 來源:Linuxer ? 2020-07-01 14:49 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

網(wǎng)上很多人提問為什么一定要copy_from_user,也有人解答。比如百度一下:

但是這里面很多的解答沒有回答到點子上,不能真正回答這個問題。我決定寫篇文章正式回答一下這個問題,消除讀者的各種疑慮。

這個問題,我認為需要從2個層面回答

第一個層次是為什么要拷貝,可不可以不拷貝?

第二個層次是為什么要用copy_from_user而不是直接memcpy

為什么要拷貝

拷貝這個事情是必須的,這個事情甚至都跟Linux都沒有什么關(guān)系。比如Linux有個kobject結(jié)構(gòu)體,kobject結(jié)構(gòu)體里面有個name指針:

struct kobject { const char *name; struct list_head entry; struct kobject *parent; struct kset *kset; struct kobj_type *ktype; struct kernfs_node *sd; /* sysfs directory entry */ struct kref kref;...};

但我們設(shè)置一個設(shè)備的名字的時候,其實就是設(shè)置device的kobject的name:

int dev_set_name(struct device *dev, const char *fmt, ...){ va_list vargs; int err; va_start(vargs, fmt); err = kobject_set_name_vargs(&dev->kobj, fmt, vargs); va_end(vargs); return err;}

驅(qū)動里面經(jīng)常要設(shè)置name,比如:

dev_set_name(&chan->dev->device, "dma%dchan%d", device->dev_id, chan->chan_id);

但是Linux沒有傻到直接把name的指針這樣賦值:

struct device { struct kobject kobj; ...}; dev_set_name(struct device *dev, char *name){ dev->kobj.name = name_param; //假想的爛代碼}

如果它這樣做了的話,那么它就完蛋了,因為驅(qū)動里面完全可以這樣設(shè)置name:

driver_func(){char name[100];....dev_set_name(dev, name);}

傳給dev_set_name()的根本是個stack區(qū)域的臨時變量,是一個匆匆過客。而device的name對于這個device來講,必須長期存在。所以你看內(nèi)核真實的代碼,是給kobject的name重新申請一份內(nèi)存,然后把dev_set_name()傳給它的name拷貝進來:

int kobject_set_name_vargs(struct kobject *kobj, const char *fmt, va_list vargs){constchar*s; .. s = kvasprintf_const(GFP_KERNEL, fmt, vargs); ... if (strchr(s, '/')) { char *t; t = kstrdup(s, GFP_KERNEL); kfree_const(s); if (!t) return -ENOMEM; strreplace(t, '/', '!'); s = t; } kfree_const(kobj->name); kobj->name = s; return 0;}

這個問題在用戶空間和內(nèi)核空間的交界點上是完全存在的。假設(shè)內(nèi)核里面某個驅(qū)動的xxx_write()是這么寫的:

struct globalmem_dev { struct cdev cdev; unsigned char *mem; struct mutex mutex;}; static ssize_t globalmem_write(struct file *filp, const char __user * buf, size_t size, loff_t * ppos){ struct globalmem_dev *dev = filp->private_data; dev->mem=buf;//假想的爛代碼 return ret;}

這樣的代碼絕對是要完蛋的,因為dev->mem這個內(nèi)核態(tài)的指針完全有可能被內(nèi)核態(tài)的中斷服務(wù)程序、被workqueue的callback函數(shù)、被內(nèi)核線程,或者被用戶空間的另外一個進程通過globalmem_read()去讀,但是它卻指向一個某個進程用戶空間的buffer。

在內(nèi)核里面直接使用用戶態(tài)傳過來的const char __user * buf指針,是災(zāi)難性的,因為buf的虛擬地址,只在這個進程空間是有效的,跨進程是無效的。但是調(diào)度一直在發(fā)生,中斷是存在的,workqueue是存在的,內(nèi)核線程是存在的,其他進程是存在的,原先的用戶進程的buffer地址,切了個進程之后就不知道是個什么鬼!換個進程,頁表都特碼變了,你這個buf地址還能找著人?進程1的buf地址,在下面的紅框里面,什么都不是!

所以內(nèi)核的正確做法是,把buf拷貝到一個跨中斷、跨進程、跨workqueue、跨內(nèi)核線程的長期有效的內(nèi)存里面:

struct globalmem_dev { struct cdev cdev; unsigned char mem[GLOBALMEM_SIZE];//長期有效 struct mutex mutex;}; static ssize_t globalmem_write(struct file *filp, const char __user * buf, size_t size, loff_t * ppos){ unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data; .... if (copy_from_user(dev->mem + p, buf, count))//拷貝??! ret = -EFAULT; else { *ppos += count; ret = count; ...}

記住,對于內(nèi)核而言,用戶態(tài)此刻傳入的指針只是一個匆匆過客,只是個燦爛煙花,只是個曇花一現(xiàn),瞬間即逝!它甚至都沒有許諾你天長地久,隨時可能劈腿!

所以,如果一定要給個需要拷貝的理由,原因就是防止劈腿!別給我扯些有的沒的。

必須拷貝的第二個理由,可能與安全有關(guān)。比如用戶態(tài)做類似pwritev, preadv這樣的調(diào)用:

ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);

用戶傳給內(nèi)核一個iov的數(shù)組,數(shù)組每個成員描述一個buffer的基地址和長度:

struct iovec{ void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ __kernel_size_t iov_len; /* Must be size_t (1003.1g) */};

用戶傳過來的是一個iovec的數(shù)組,里面有每個iov的len和base(base也是指向用戶態(tài)的buffer的),傳進內(nèi)核的時候,內(nèi)核會對iovec的地址進行check,保證它確實每個buffer都在用戶空間,并且會把整個iovec數(shù)組拷貝到內(nèi)核空間:

ssize_t import_iovec(int type, const struct iovec __user * uvector, unsigned nr_segs, unsigned fast_segs, struct iovec **iov, struct iov_iter *i){ ssize_t n; struct iovec *p; n = rw_copy_check_uvector(type, uvector, nr_segs, fast_segs, *iov, &p);... iov_iter_init(i, type, p, nr_segs, n); *iov = p == *iov ? NULL : p; return n;}

這個過程是有嚴格的安全考量的,整個iov數(shù)組會被copy_from_user(),而數(shù)組里面的每個buf都要被access_ok的檢查:

ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector, unsigned long nr_segs, unsigned long fast_segs, struct iovec *fast_pointer, struct iovec **ret_pointer){ ... if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) { ret = -EFAULT; goto out; } ... ret = 0; for (seg = 0; seg < nr_segs; seg++) { void __user *buf = iov[seg].iov_base; ssize_t len = (ssize_t)iov[seg].iov_len; ... if (type >= 0 && unlikely(!access_ok(buf, len))) { ret = -EFAULT; goto out; } ... }out: *ret_pointer = iov; return ret;}

access_ok(buf, len)是確保從buf開始的len長的區(qū)間,一定是位于用戶空間的,應(yīng)用程序不能傳入一個內(nèi)核空間的地址來傳給系統(tǒng)調(diào)用,這樣用戶可以通過系統(tǒng)調(diào)用,讓內(nèi)核寫壞內(nèi)核本身,造成一系列內(nèi)核安全漏洞。

假設(shè)內(nèi)核不把整個iov數(shù)組通過如下代碼拷貝進內(nèi)核:

copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))

而是直接訪問用戶態(tài)的iov,那個這個access_ok就完全失去價值了,因為,用戶完全可以在你做access_ok檢查的時候,傳給你的是用戶態(tài)buffer,之后把iov_base的內(nèi)容改成指向一個內(nèi)核態(tài)的buffer去。

所以,從這個理由上來講,最開始的拷貝也是必須的。但是這個理由遠遠沒有最開始那個隨時劈腿的理由充分!

為什么不直接用memcpy?

這個問題主要涉及到2個層面,一個是copy_from_user()有自帶的access_ok檢查,如果用戶傳進來的buffer不屬于用戶空間而是內(nèi)核空間,根本不會拷貝;二是copy_from_user()有自帶的page fault后exception修復(fù)機制。

先看第一個問題,如果代碼直接用memcpy():

static ssize_t globalmem_write(struct file *filp, const char __user * buf, size_t size, loff_t * ppos){ struct globalmem_dev *dev = filp->private_data; .... memcpy(dev->mem + p, buf, count)) return ret;}

memcpy是沒有這個檢查的,哪怕用戶傳入進來的這個buf,指向的是內(nèi)核態(tài)的地址,這個拷貝也是要做的。試想,用戶做系統(tǒng)調(diào)用的時候,隨便可以把內(nèi)核的指針傳進來,那用戶不是可以隨便為所欲為?比如內(nèi)核的這個commit,引起了著名的安全漏洞:

CVE-2017-5123

就是因為,作者把有access_ok的put_user改為了沒有access_ok的unsafe_put_user。這樣,用戶如果把某個進程的uid地址傳給內(nèi)核,內(nèi)核unsafe_put_user的時候,不是完全可以把它的uid改為0?

所以,你看到內(nèi)核修復(fù)這個CVE的時候,是對這些地址進行了一個access_ok的:

下面我們看第二個問題,page fault的修復(fù)機制。假設(shè)用戶程序隨便胡亂傳個用戶態(tài)的地址給內(nèi)核:

void main(void){ int fd; fd = open("/dev/globalfifo", O_RDWR, S_IRUSR | S_IWUSR); if (fd != -1) {intret=write(fd,0x40000000,10);//假想的代碼 if (ret < 0) perror("write error "); }}

0x40000000這個地址是用戶態(tài)的,所以access_ok是沒有問題的。但是這個地址,根本什么有效的數(shù)據(jù)、heap、stack都不是。我特碼就是瞎寫的。

如果內(nèi)核驅(qū)動用memcpy會發(fā)生什么呢?我們會看到一段內(nèi)核Oops:

用戶進程也會被kill掉:

# ./a.out Killed

當然如果你設(shè)置了/proc/sys/kernel/panic_on_oops為1的話,內(nèi)核就不是Opps這么簡單了,而是直接panic了。

但是如果內(nèi)核用的是copy_from_user呢?內(nèi)核是不會Oops的,用戶態(tài)應(yīng)用程序也是不會死的,它只是收到了bad address的錯誤:

# ./a.out write error: Bad address

內(nèi)核只是友好地提示你用戶闖進來的buffer地址0x40000000是個錯誤的地址,這個系統(tǒng)調(diào)用的參數(shù)是不對的,這顯然更加符合系統(tǒng)調(diào)用的本質(zhì)。

內(nèi)核針對copy_from_user,有exception fixup機制,而memcpy()是沒有的。詳細的exception修復(fù)機制見:

https://www.kernel.org/doc/Documentation/x86/exception-tables.txt

PAN

如果我們想研究地更深,硬件和軟件協(xié)同做了一個更加安全的機制,這個機制叫做PAN (Privileged Access Never)。它可以把內(nèi)核對用戶空間的buffer訪問限制在特定的代碼區(qū)間里面。PAN可以阻止kernel直接訪問用戶,它要求訪問之前,必須在硬件上開啟訪問權(quán)限。根據(jù)ARM的spec文檔

https://static.docs.arm.com/ddi0557/ab/DDI0557A_b_armv8_1_supplement.pdf

描述:

所以,內(nèi)核每次訪問用戶之前,需要修改PSATE寄存器開啟訪問權(quán)限,完事后應(yīng)該再次修改PSTATE,關(guān)閉內(nèi)核對用戶的訪問權(quán)限。

根據(jù)補?。?/p>

https://patchwork.kernel.org/patch/6808781/

copy_from_user這樣的代碼,是有這個開啟和關(guān)閉的過程的。

所以,一旦你開啟了內(nèi)核的PAN支持,你是不能在一個隨隨便便的位置訪問用戶空間的buffer的。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 驅(qū)動
    +關(guān)注

    關(guān)注

    12

    文章

    1994

    瀏覽量

    88735
  • Linux
    +關(guān)注

    關(guān)注

    88

    文章

    11825

    瀏覽量

    219619

原文標題:宋寶華: Linux為什么一定要copy_from_user ?

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    yocto編譯IPCF sample_user報錯的原因?怎么解決?

    /s32g399avtvmcu2-fsl-linux/ipc-shm/1.0-r0/git/sample_user\' | Building app file: sample.c | aarch64-fsl-linux
    發(fā)表于 04-07 07:49

    電子人一定要學(xué)會的20種模擬電路

    很多剛接觸模擬電路的同學(xué)都會有同感:原理看得懂,電路圖上手就懵;公式背得熟,到實際應(yīng)用就卡殼。其實模擬電路并沒有想象中那么難,核心就是把最經(jīng)典、最常用的基礎(chǔ)電路吃透練熟。從電源處理到信號放大,從
    的頭像 發(fā)表于 04-01 09:05 ?335次閱讀
    電子人<b class='flag-5'>一定要</b>學(xué)會的20種模擬電路

    產(chǎn)品出口美國一定要 FCC 認證嗎?企業(yè)必須搞清楚的合規(guī)邊界

    在產(chǎn)品出口美國前,很多企業(yè)都會遇到同個問題:“是不是只要賣到美國,就一定要做 FCC 認證?”這個問題如果理解不清,很容易出現(xiàn)兩種極端情況: 要么不該做卻做了,增加成本;要么該做卻沒做,導(dǎo)致產(chǎn)品被
    的頭像 發(fā)表于 02-05 15:03 ?740次閱讀
    產(chǎn)品出口美國<b class='flag-5'>一定要</b> FCC 認證嗎?企業(yè)必須搞清楚的合規(guī)邊界

    厚德筑基自強興業(yè),金航標和薩科微仕強先生源于華強北的創(chuàng)業(yè)史!

    了深圳市金航標電子有限公司(KinghelmElectronics)與深圳市薩科微半導(dǎo)體有限公司(SlkorMicroSemicon),并擔(dān)任總經(jīng)理職。仕強先生早年
    的頭像 發(fā)表于 01-22 16:40 ?1452次閱讀
    厚德筑基自強興業(yè),金航標和薩科微<b class='flag-5'>宋</b>仕強先生源于華強北的創(chuàng)業(yè)史!

    液晶屏一定要做屏保

    液晶屏一定要做屏保,避免不可逆的顯示問題,學(xué)到了。
    發(fā)表于 09-29 11:38

    為什么自動駕駛感知系統(tǒng)一定要注意時間同步?

    [首發(fā)于智駕最前沿微信公眾號]時間同步,看似非常簡單的個概念,但在自動駕駛中有著非常重要的作用。一定要明白,時間同步不是感知系統(tǒng)的可選項,而是多傳感器系統(tǒng)能否正確工作的基礎(chǔ)性約束。自動駕駛系統(tǒng)依賴
    的頭像 發(fā)表于 09-10 09:00 ?996次閱讀
    為什么自動駕駛感知系統(tǒng)<b class='flag-5'>一定要</b>注意時間同步?

    【嘉楠堪智K230開發(fā)板試用體驗】編寫個GPIO 的字符驅(qū)動

    value; int ret; if (count > sizeof(tmp_buf) - 1) { return -EINVAL; } if (copy_from_user
    發(fā)表于 09-07 01:03

    充電可以無線充電嗎?

    無線充電利用電磁感應(yīng)技術(shù)實現(xiàn)無線充電,兼容Qi標準,兼具便捷性與實用性,成為傳統(tǒng)充電的替代方案。
    的頭像 發(fā)表于 09-06 08:28 ?2213次閱讀
    充電<b class='flag-5'>寶</b>可以無線充電嗎?

    【重要通知】秋DFM舊版本暫停服務(wù)公告

    。 隨著秋DFM多個版本的迭代升級,我們在 增強軟件性能、提升分析精度和擴展功能模塊等方面都取得了顯著的進步 。然而,伴隨著版本的不斷積累,這些 舊版本在功能完整性、穩(wěn)定性及用戶體驗方面存在一定
    發(fā)表于 09-05 13:45

    秋DFM軟件丨操作教程——自定義快捷鍵篇

    Hi,各位秋DFM的新老粉絲們,感謝大家直以來的支持和關(guān)注呀~咱們后臺的留言,小編直都有在認真記錄哦!看到近期新粉絲咨詢比較多的問題,是關(guān)于秋DFM軟件的使用教程和操作這塊。雖
    發(fā)表于 08-13 16:29

    文掌握Linux命令

    作為名運維工程師,熟練掌握Linux命令是基本功中的基本功。無論是日常工作中的系統(tǒng)維護,還是面試時的技術(shù)考核,Linux命令都是繞不開的核心技能。本文將從實戰(zhàn)角度出發(fā),系統(tǒng)梳理運維工程師必須掌握的
    的頭像 發(fā)表于 07-22 15:23 ?747次閱讀

    邦電子總結(jié)芯片行業(yè)十大黑話

    芯片行業(yè)中往往幾個字母就能傳遞連串關(guān)鍵有效的信息,那些專業(yè)術(shù)語,濃縮了行業(yè)技術(shù)要點,涵蓋在工程師們的工作日常中,今天邦博士給大家一一破譯、解碼你一定要了解的邦電子十大常見“黑話”。
    的頭像 發(fā)表于 07-15 11:44 ?1755次閱讀

    充電快充協(xié)議是什么

    充電快充協(xié)議是充電與設(shè)備之間實現(xiàn)快速充電的通信規(guī)則,它定義了電壓、電流、功率等參數(shù)的傳輸標準,確保設(shè)備與充電高效匹配,實現(xiàn)安全快充。 以下是主流快充協(xié)議的詳細解析: 、快充協(xié)議
    的頭像 發(fā)表于 06-30 09:17 ?1w次閱讀

    全新比亞迪PLUS登陸巴西市場

    近日,比亞迪在巴西圣保羅隆重發(fā)布全新PLUS與PLUS PREMIUM雙版本,吸引了逾500位嘉賓出席現(xiàn)場,其中包括256位媒體記者與知名意見領(lǐng)袖,見證這款劃時代的插電混合動力SUV閃耀登場。
    的頭像 發(fā)表于 06-10 16:41 ?1036次閱讀

    【免費工具】秋AI電路識別助手:讓電路設(shè)計與分析變得輕松高效!

    電子工程師注意!還在為熬夜解析電路圖崩潰?AI黑科技讓電路設(shè)計與分析變得輕松高效!如果你還在為電路分析感到頭疼,那么一定要試試這款超好用的工具——秋AI電路識別助手小程序!這是款由
    的頭像 發(fā)表于 06-05 18:18 ?2983次閱讀
    【免費工具】<b class='flag-5'>華</b>秋AI電路識別助手:讓電路設(shè)計與分析變得輕松高效!
    金溪县| 刚察县| 江永县| 从化市| 铁岭县| 怀柔区| 万年县| 太仓市| 盐亭县| 张家港市| 苏尼特右旗| 长海县| 宜州市| 抚远县| 罗田县| 英吉沙县| 乾安县| 涡阳县| 安阳县| 冷水江市| 治县。| 蒙山县| 萍乡市| 闽清县| 轮台县| 蒙城县| 库尔勒市| 栾川县| 兴和县| 临湘市| 旬邑县| 承德县| 武宁县| 随州市| 曲麻莱县| 鸡西市| 丰城市| 深泽县| 平昌县| 海林市| 南宁市|