ใครมีปลายอีกด้านของซ็อกเก็ตยูนิกซ์นี้?


54

ฉันต้องการตรวจสอบว่ากระบวนการใดมีส่วนอื่น ๆ ของซ็อกเก็ต UNIX

โดยเฉพาะฉันถามเกี่ยวกับสิ่งที่สร้างขึ้นด้วยsocketpair()แม้ว่าปัญหาจะเหมือนกันสำหรับซ็อกเก็ต UNIX ใด ๆ

ผมมีโปรแกรมparentที่สร้างsocketpair(AF_UNIX, SOCK_STREAM, 0, fds)และfork()s กระบวนการหลักปิดfds[1]และfds[0]ทำการสื่อสารต่อไป close(fds[0]); s=fds[1]เด็กไม่ตรงข้าม จากนั้นเด็กก็เข้าexec()ร่วมโปรแกรมอื่น, child1. ทั้งสองสามารถสื่อสารไปมาผ่านซ็อกเก็ตคู่นี้ได้

ตอนนี้ขอบอกว่าฉันรู้ว่าใครparentเป็น แต่ฉันต้องการที่จะคิดออกที่child1เป็น ฉันจะทำสิ่งนี้ได้อย่างไร

มีเครื่องมือหลายอย่างที่ฉันจัดการ แต่ไม่มีใครสามารถบอกได้ว่ากระบวนการใดอยู่ที่ปลายอีกด้านของซ็อกเก็ต ฉันเหนื่อย:

  • lsof -c progname
  • lsof -c parent -c child1
  • ls -l /proc/$(pidof server)/fd
  • cat /proc/net/unix

โดยพื้นฐานแล้วฉันสามารถเห็นซ็อกเก็ตทั้งสองและทุกอย่างเกี่ยวกับพวกเขา แต่ไม่สามารถบอกได้ว่าพวกเขาเชื่อมต่อกัน ฉันพยายามระบุว่า FD ใดในผู้ปกครองกำลังสื่อสารกับกระบวนการลูก

คำตอบ:


27

ตั้งแต่เคอร์เนล 3.3 ก็เป็นไปได้ใช้ssหรือlsof-4.89ขึ้นไป - ดูคำตอบStéphane Chazelas ของ

ในรุ่นเก่าตามผู้เขียนlsofมันเป็นไปไม่ได้ที่จะหาสิ่งนี้: เคอร์เนล Linux ไม่เปิดเผยข้อมูลนี้ แหล่งที่มา: 2003 กระทู้ใน comp.unix.admin

หมายเลขที่แสดง/proc/$pid/fd/$fdเป็นหมายเลขไอโหนดของซ็อกเก็ตในระบบไฟล์ซ็อกเก็ตเสมือน เมื่อคุณสร้างคู่ไพพ์หรือซ็อกเก็ตปลายแต่ละด้านจะรับหมายเลขไอโหนดอย่างต่อเนื่อง ตัวเลขมีการเรียงลำดับตามลำดับดังนั้นจึงมีความเป็นไปได้สูงที่ตัวเลขจะแตกต่างกัน 1 แต่ไม่รับประกัน (อย่างใดอย่างหนึ่งเนื่องจากซ็อกเก็ตแรกคือNและN +1 ถูกใช้งานแล้วเนื่องจากมีการตัดหรือเนื่องจากเธรดอื่น ๆ กำหนดเวลาระหว่างการจัดสรร inode สองครั้งและเธรดนั้นก็สร้างบางไอโหนดด้วย)

ฉันจะตรวจสอบความหมายของsocketpairในเคอร์เนล 2.6.39และปลายทั้งสองข้างของซ็อกเก็ตไม่ได้มีความสัมพันธ์ยกเว้นตามประเภทเฉพาะวิธีsocketpair ซ็อกเก็ตสำหรับยูนิกซ์ที่ในunix_socketpairnet/unix/af_unix.c


2
ขอบคุณ @Gillles ฉันจำได้ว่ากำลังอ่านอะไรบางอย่างเกี่ยวกับเรื่องนั้นอยู่พักหนึ่ง แต่ก็ไม่สามารถค้นหามันได้ ฉันอาจต้องไปเขียนแพทช์สำหรับ / proc / net / unix
Jonathon Reinhart

และใช่ฉันได้ทำการสังเกตด้วยการเพิ่มจำนวนไอโหนดและปัจจุบันคือสิ่งที่ฉันทำงานด้วย อย่างไรก็ตามอย่างที่คุณสังเกตไว้มันไม่รับประกัน กระบวนการที่ฉันดูมีซ็อกเก็ตยูนิกซ์เปิดอย่างน้อย 40 ซ็อกเก็ตและฉันเห็นอินสแตนซ์หนึ่งที่ N + 1 ไม่ถือเป็นจริง คนเกียจคร้าน
Jonathon Reinhart

1
@JonathonReinhart ฉันจะตรวจสอบความหมายของsocketpairและทั้งสองปลายของซ็อกเก็ตไม่ได้มีความสัมพันธ์ยกเว้นตามประเภทเฉพาะวิธีsocketpair ซ็อกเก็ตสำหรับยูนิกซ์ที่unix_socketpairใน `สุทธิ มันคงจะดีถ้ามีข้อมูลนี้สำหรับท่อด้วย
Gilles 'หยุดชั่วร้าย'

36

หมายเหตุ : ตอนนี้ฉันมีlsofwrapper ที่รวมทั้งสองวิธีที่อธิบายไว้ที่นี่และยังเพิ่มข้อมูลสำหรับการเชื่อมต่อ TCP แบบวนกลับที่https://github.com/stephane-chazelas/misc-scripts/blob/master/lsofc

Linux-3.3 ขึ้นไป

บน Linux เนื่องจากเคอร์เนลเวอร์ชัน 3.3 (และจัดเตรียมUNIX_DIAGคุณลักษณะถูกสร้างขึ้นในเคอร์เนล) เพียร์ของซ็อกเก็ตโดเมน unix ที่กำหนด (รวมถึงซ็อกเก็ตคู่) สามารถรับได้โดยใช้netlink API ใหม่

lsof เนื่องจากเวอร์ชัน 4.89 สามารถใช้ประโยชน์ API ดังกล่าวได้:

lsof +E -aUc Xorg

จะแสดงรายการซ็อกเก็ตโดเมน Unix ทั้งหมดที่มีกระบวนการที่ชื่อขึ้นต้นด้วยXorgปลายทั้งสองในรูปแบบที่คล้ายกับ:

Xorg       2777       root   56u  unix 0xffff8802419a7c00      0t0   34036 @/tmp/.X11-unix/X0 type=STREAM ->INO=33273 4120,xterm,3u

หากเวอร์ชั่นของlsofคุณเก่าเกินไปจะมีตัวเลือกเพิ่มอีกสองสามตัว

ssยูทิลิตี้ (จากiproute2) ทำให้การใช้ API เดียวกันกับที่จะดึงและแสดงข้อมูลในรายชื่อของซ็อกเก็ตโดเมนยูนิกซ์ในระบบรวมทั้งข้อมูลที่เพียร์

ซ็อกเก็ตมีการระบุโดยพวกเขาจำนวน inode โปรดทราบว่ามันไม่เกี่ยวข้องกับระบบไฟล์ inode ของไฟล์ซ็อกเก็ต

ตัวอย่างเช่นใน:

$ ss -x
[...]
u_str  ESTAB    0    0   @/tmp/.X11-unix/X0 3435997     * 3435996

มันบอกว่าซ็อกเก็ต 3435997 (ที่ถูกผูกไว้กับซ็อกเก็ต ABSTRACT /tmp/.X11-unix/X0) เชื่อมต่อกับซ็อกเก็ต 3435996 -pตัวเลือกสามารถบอกคุณว่ากระบวนการใดมีซ็อกเก็ตเปิดอยู่ มันไม่ได้ว่าการดำเนินการบางอย่างreadlinkบน/proc/$pid/fd/*ดังนั้นจึงสามารถทำอย่างนั้นกับกระบวนการคุณเป็นเจ้าของ (เว้นแต่คุณroot) เช่นที่นี่:

$ sudo ss -xp
[...]
u_str  ESTAB  0  0  @/tmp/.X11-unix/X0 3435997 * 3435996 users:(("Xorg",pid=3080,fd=83))
[...]
$ sudo ls -l /proc/3080/fd/23
lrwx------ 1 root root 64 Mar 12 16:34 /proc/3080/fd/83 -> socket:[3435997]

หากต้องการค้นหาว่ากระบวนการใดมี 3435996 คุณสามารถค้นหารายการของตนเองในผลลัพธ์ของss -xp:

$ ss -xp | awk '$6 == 3435996'
u_str  ESTAB  0  0  * 3435996  * 3435997 users:(("xterm",pid=29215,fd=3))

คุณสามารถใช้สคริปต์นี้เป็น wrapper lsofเพื่อแสดงข้อมูลที่เกี่ยวข้องได้อย่างง่ายดายที่นั่น:

#! /usr/bin/perl
# lsof wrapper to add peer information for unix domain socket.
# Needs Linux 3.3 or above and CONFIG_UNIX_DIAG enabled.

# retrieve peer and direction information from ss
my (%peer, %dir);
open SS, '-|', 'ss', '-nexa';
while (<SS>) {
  if (/\s(\d+)\s+\*\s+(\d+) ([<-]-[->])$/) {
    $peer{$1} = $2;
    $dir{$1} = $3;
  }
}
close SS;

# Now get info about processes tied to sockets using lsof
my (%fields, %proc);
open LSOF, '-|', 'lsof', '-nPUFpcfin';
while (<LSOF>) {
  if (/(.)(.*)/) {
    $fields{$1} = $2;
    if ($1 eq 'n') {
      $proc{$fields{i}}->{"$fields{c},$fields{p}" .
      ($fields{n} =~ m{^([@/].*?)( type=\w+)?$} ? ",$1" : "")} = "";
    }
  }
}
close LSOF;

# and finally process the lsof output
open LSOF, '-|', 'lsof', @ARGV;
while (<LSOF>) {
  chomp;
  if (/\sunix\s+\S+\s+\S+\s+(\d+)\s/) {
    my $peer = $peer{$1};
    if (defined($peer)) {
      $_ .= $peer ?
            " ${dir{$1}} $peer\[" . (join("|", keys%{$proc{$peer}})||"?") . "]" :
            "[LISTENING]";
    }
  }
  print "$_\n";
}
close LSOF or exit(1);

ตัวอย่างเช่น:

$ sudo that-lsof-wrapper -ad3 -p 29215
คำสั่ง PID USER FD ประเภทอุปกรณ์ขนาด / ปิดชื่อโหนด
xterm 29215 stephane 3u unix 0xffff8800a07da4c0 0t0 3435996 ประเภท = STREAM <-> 3435997 [Xorg, 3080, @ / tmp / .X11-unix / X0]

ก่อน linux-3.3

Linux API เก่าเพื่อดึงข้อมูลซ็อกเก็ตยูนิกซ์คือผ่าน/proc/net/unixไฟล์ข้อความ มันแสดงรายการซ็อกเก็ตโดเมน Unix ทั้งหมด (รวมถึงซ็อกเก็ตคู่) ฟิลด์แรกที่อยู่ในนั้น (หากไม่ได้ซ่อนไว้กับผู้ที่ไม่ใช่ผู้ใช้ระดับสูงด้วยkernel.kptr_restrictพารามิเตอร์ sysctl) ดังที่อธิบายโดย @Totor แล้วมีเคอร์เนลที่อยู่ของunix_sockโครงสร้างที่มีpeerฟิลด์ที่ชี้ไปยังเพียร์ที่ unix_sockเกี่ยวข้อง นอกจากนี้ยังเป็นสิ่งที่lsofส่งออกสำหรับDEVICEคอลัมน์ในซ็อกเก็ต Unix

ตอนนี้การรับค่าของpeerฟิลด์นั้นหมายถึงความสามารถในการอ่านหน่วยความจำเคอร์เนลและรู้ค่าออฟเซ็ตของpeerฟิลด์นั้นโดยคำนึงถึงที่unix_sockอยู่

หลายgdbชั่นและsystemtap-basedโซลูชั่นได้รับแล้วรับ แต่พวกเขาจำเป็นต้องมีgdb/ systemtapและลินุกซ์เคอร์เนลสัญลักษณ์การแก้ปัญหาสำหรับเคอร์เนลทำงานถูกติดตั้งซึ่งโดยทั่วไปไม่ได้กรณีที่เกี่ยวกับระบบการผลิต

ฮาร์ดโค้ดที่ออฟเซตนั้นไม่ใช่ตัวเลือกที่ต่างกันไปตามรุ่นเคอร์เนล

ตอนนี้เราสามารถใช้วิธีแก้ปัญหาแบบฮิวริสติกเพื่อกำหนดออฟเซ็ต: ให้เครื่องมือของเราสร้างดัมมี่socketpair(จากนั้นเรารู้ที่อยู่ของเพื่อนทั้งคู่) และค้นหาที่อยู่ของเพียร์รอบหน่วยความจำที่ปลายอีกด้าน

นี่คือบทพิสูจน์แนวคิดของสคริปต์ที่ใช้perl(ทดสอบกับเคอร์เนล 2.4.27 และ 2.6.32 บน i386 และ 3.13 และ 3.16 ใน amd64) เหมือนด้านบนมันทำงานเป็น wrapper รอบ ๆlsof:

ตัวอย่างเช่น:

$ that-lsof-wrapper -aUc nm-applet
คำสั่ง PID USER FD ประเภทอุปกรณ์ขนาด / ปิดชื่อโหนด
นาโนเมตรแอปเพล็ 4183 Stephane 4u ยูนิกซ์ 0xffff8800a055eb40 0t0 36,888 type = STREAM -> 0xffff8800a055e7c0 [dbus-ภูต, 4190 @ / tmp / dbus-AiBCXOnuP6] 
นาโนเมตรแอปเพล็ 4183 Stephane 7U ยูนิกซ์ 0xffff8800a055e440 0t0 36,890 type = STREAM -> 0xffff8800a055e0c0 [Xorg, 3080 @ / tmp / .X11-Unix / X 0] 
นาโนเมตรแอปเพล็ 4183 8U Stephane ยูนิกซ์ 0xffff8800a05c1040 0t0 36201 type = STREAM -> 0xffff8800a05c13c0 [dbus-ภูต, 4118, @ / tmp / dbus-yxxNr1NkYC] 
นาโนเมตรแอปเพล็ 4183 11u Stephane ยูนิกซ์ 0xffff8800a055d080 0t0 36,219 type = STREAM -> 0xffff8800a055d400 [dbus-ภูต, 4118, @ / tmp / dbus-yxxNr1NkYC] 
นาโนเมตรแอปเพล็ 4183 Stephane 12U ยูนิกซ์ 0xffff88022e0dfb80 0t0 36,221 type = STREAM -> 0xffff88022e0df800 [dbus-ภูต, 2268, / var / ทำงาน / dbus / system_bus_socket]
nm-applet 4183 stephane 13u unix 0xffff88022e0f80c0 0t0 37025 ประเภท = STREAM -> 0xffff88022e29ec00 [dbus-daemon, 2268, / var / run / dbus / system_bus_socket]

นี่คือสคริปต์:

#! /usr/bin/perl
# wrapper around lsof to add peer information for Unix
# domain sockets. needs lsof, and superuser privileges.
# Copyright Stephane Chazelas 2015, public domain.
# example: sudo this-lsof-wrapper -aUc Xorg
use Socket;

open K, "<", "/proc/kcore" or die "open kcore: $!";
read K, $h, 8192 # should be more than enough
 or die "read kcore: $!";

# parse ELF header
my ($t,$o,$n) = unpack("x4Cx[C19L!]L!x[L!C8]S", $h);
$t = $t == 1 ? "L3x4Lx12" : "Lx4QQx8Qx16"; # program header ELF32 or ELF64
my @headers = unpack("x$o($t)$n",$h);

# read data from kcore at given address (obtaining file offset from ELF
# @headers)
sub readaddr {
  my @h = @headers;
  my ($addr, $length) = @_;
  my $offset;
  while (my ($t, $o, $v, $s) = splice @h, 0, 4) {
    if ($addr >= $v && $addr < $v + $s) {
      $offset = $o + $addr - $v;
      if ($addr + $length - $v > $s) {
        $length = $s - ($addr - $v);
      }
      last;
    }
  }
  return undef unless defined($offset);
  seek K, $offset, 0 or die "seek kcore: $!";
  my $ret;
  read K, $ret, $length or die "read($length) kcore \@$offset: $!";
  return $ret;
}

# create a dummy socketpair to try find the offset in the
# kernel structure
socketpair(Rdr, Wtr, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
 or die "socketpair: $!";
$r = readlink("/proc/self/fd/" . fileno(Rdr)) or die "readlink Rdr: $!";
$r =~ /\[(\d+)/; $r = $1;
$w = readlink("/proc/self/fd/" . fileno(Wtr)) or die "readlink Wtr: $!";
$w =~ /\[(\d+)/; $w = $1;
# now $r and $w contain the socket inodes of both ends of the socketpair
die "Can't determine peer offset" unless $r && $w;

# get the inode->address mapping
open U, "<", "/proc/net/unix" or die "open unix: $!";
while (<U>) {
  if (/^([0-9a-f]+):(?:\s+\S+){5}\s+(\d+)/) {
    $addr{$2} = hex $1;
  }
}
close U;

die "Can't determine peer offset" unless $addr{$r} && $addr{$w};

# read 2048 bytes starting at the address of Rdr and hope to find
# the address of Wtr referenced somewhere in there.
$around = readaddr $addr{$r}, 2048;
my $offset = 0;
my $ptr_size = length(pack("L!",0));
my $found;
for (unpack("L!*", $around)) {
  if ($_ == $addr{$w}) {
    $found = 1;
    last;
  }
  $offset += $ptr_size;
}
die "Can't determine peer offset" unless $found;

my %peer;
# now retrieve peer for each socket
for my $inode (keys %addr) {
  $peer{$addr{$inode}} = unpack("L!", readaddr($addr{$inode}+$offset,$ptr_size));
}
close K;

# Now get info about processes tied to sockets using lsof
my (%fields, %proc);
open LSOF, '-|', 'lsof', '-nPUFpcfdn';
while (<LSOF>) {
  if (/(.)(.*)/) {
    $fields{$1} = $2;
    if ($1 eq 'n') {
      $proc{hex($fields{d})}->{"$fields{c},$fields{p}" .
      ($fields{n} =~ m{^([@/].*?)( type=\w+)?$} ? ",$1" : "")} = "";
    }
  }
}
close LSOF;

# and finally process the lsof output
open LSOF, '-|', 'lsof', @ARGV;
while (<LSOF>) {
  chomp;
  for my $addr (/0x[0-9a-f]+/g) {
    $addr = hex $addr;
    my $peer = $peer{$addr};
    if (defined($peer)) {
      $_ .= $peer ?
            sprintf(" -> 0x%x[", $peer) . join("|", keys%{$proc{$peer}}) . "]" :
            "[LISTENING]";
      last;
    }
  }
  print "$_\n";
}
close LSOF or exit(1);

1
@mikeserv นั่นคือการติดตามความคิดเห็นนั้น การไม่สามารถหาปลายอีกด้านของซ็อกเก็ตยูนิกซ์เป็นสิ่งที่ทำให้ฉันรำคาญอยู่เสมอ (มักจะพยายามค้นหาลูกค้า X และมีคำถามเมื่อเร็ว ๆ นี้ ) ผมจะพยายามและดูว่าวิธีการที่คล้ายกันสามารถนำมาใช้สำหรับการหลอกขั้วและแนะนำผู้ที่จะlsofเขียน
Stéphane Chazelas

1
ฉันยังไม่อยากเชื่อว่าเคอร์เนลจะไม่ได้จัดเตรียมไว้ให้! ฉันควรส่งแพตช์จริง ๆ หากไม่มีอะไรอื่นนอกจากค้นพบว่าทำไมมันถึงไม่มีอยู่จริง
Jonathon Reinhart

1
ไม่ssได้ทำเช่นนี้? เป็นชนิดของเหนือหัวของฉัน แต่ss -pxแสดงรายการจำนวนมากของซ็อกเก็ตยูนิกซ์ที่มีข้อมูลเพียร์ชอบusers: ("nacl_helper",pid=18992,fd=6),("chrome",pid=18987,fd=6),("chrome",pid=18975,fd=5)) u_str ESTAB\t0\t0\t/run/dbus/system_bus_socket 8760\t\t* 15068และส่วนหัวของคอลัมน์ที่มี ...State\tRecv-Q\tSend-Q\tLocal Address:Port\tPeer Address:Port
mikeserv

1
นอกจากนี้ถ้าฉันlsof -c terminologyฉันสามารถเห็นterminolo 12731\tmikeserv\t12u\tunix\t0xffff880600e82680\t0t0\t1312426\ttype=STREAMแต่ถ้าฉันจะss -px | grep terminologyได้รับ:u_str\tESTAB\t0\t0\t* 1312426\t*1315046\tusers:(("terminology",pid=12731,fd=12))
mikeserv

1
@mikeserv ดูเหมือนว่ามันจะทำแน่นอน! ดูเหมือนว่าฉันจะเสียเวลามากเมื่อเร็ว ๆ นี้ ...
Stéphane Chazelas

9

Erkki Seppala จริงมีเครื่องมือที่ดึงข้อมูลจากเคอร์เนลกับ gdb .. มันมีอยู่ที่นี่


2
ข้อมูลที่มีประโยชน์มาก! แม้ว่าเครื่องมือจะไม่ทำงานนอกกรอบสำหรับฉัน (มันทำให้เคอร์เนลโอ๊ะโอ) ความคิดช่วยฉันระบุส่วนอื่น ๆ ฉันอธิบายวิธีแก้ปัญหาของฉันใน Stack Overflow
MvG

8

ตั้งแต่เคอร์เนล 3.3

ตอนนี้คุณสามารถ รับข้อมูลนี้ด้วย:ss

# ss -xp

ตอนนี้คุณสามารถเห็นPeerID คอลัมน์ (หมายเลขไอโหนด) ซึ่งสอดคล้องกับ ID อื่นในLocalคอลัมน์ การจับคู่รหัสเป็นปลายทั้งสองของซ็อกเก็ต

หมายเหตุ: UNIX_DIAGต้องเปิดใช้งานตัวเลือกในเคอร์เนลของคุณ

ก่อนเคอร์เนล 3.3

Linux ไม่เปิดเผยข้อมูลนี้แก่ผู้ใช้

อย่างไรก็ตามโดยการค้นหาในหน่วยความจำเคอร์เนลเราสามารถเข้าถึงข้อมูลนี้

หมายเหตุ: คำตอบนี้ทำได้โดยใช้gdbโปรดดูคำตอบของ @ StéphaneChazelasซึ่งอธิบายเพิ่มเติมในเรื่องนี้

# lsof | grep whatever
mysqld 14450 (...) unix 0xffff8801011e8280 (...) /var/run/mysqld/mysqld.sock
mysqld 14450 (...) unix 0xffff8801011e9600 (...) /var/run/mysqld/mysqld.sock

มีซ็อกเก็ตที่แตกต่างกัน 2 แบบคือ 1 ฟังและตั้ง 1 หมายเลขฐานสิบหกเป็นที่อยู่ไปยังunix_sockโครงสร้างเคอร์เนลที่สอดคล้องกันโดยมีpeerแอตทริบิวต์เป็นที่อยู่ของปลายอีกด้านหนึ่งของซ็อกเก็ต ( unix_sockเช่นโครงสร้างตัวอย่าง)

ตอนนี้เราสามารถใช้gdbค้นหาpeerหน่วยความจำเคอร์เนลภายใน:

# gdb /usr/lib/debug/boot/vmlinux-3.2.0-4-amd64 /proc/kcore
(gdb) print ((struct unix_sock*)0xffff8801011e9600)->peer
$1 = (struct sock *) 0xffff880171f078c0

# lsof | grep 0xffff880171f078c0
mysql 14815 (...) unix 0xffff880171f078c0 (...) socket

ที่นี่คุณไปปลายอีกด้านของซ็อกเก็ตที่จัดขึ้นโดยmysqlPID 14815

เคอร์เนลของคุณจะต้องรวบรวมกับการใช้งานKCORE_ELF /proc/kcoreนอกจากนี้คุณต้องมีเวอร์ชันของอิมเมจเคอร์เนลที่มีสัญลักษณ์การดีบัก ใน Debian 7 apt-get install linux-image-3.2.0-4-amd64-dbgจะให้ไฟล์นี้

ไม่จำเป็นสำหรับเคอร์เนลอิมเมจที่ debuggable ...

หากคุณไม่มี (หรือไม่ต้องการเก็บไว้) ภาพเคอร์เนลการดีบักในระบบคุณสามารถให้gdbหน่วยความจำออฟเซ็ตเพื่อเข้าถึงpeerค่า"ด้วยตนเอง" ค่าออฟเซ็ตนี้มักแตกต่างกับเวอร์ชันหรือสถาปัตยกรรมเคอร์เนล

บนเคอร์เนลของฉันฉันรู้ว่าออฟเซ็ตคือ 680 ไบต์นั่นคือ 85 คูณ 64 บิต ดังนั้นฉันสามารถทำ:

# gdb /boot/vmlinux-3.2.0-4-amd64 /proc/kcore
(gdb) print ((void**)0xffff8801011e9600)[85]
$1 = (void *) 0xffff880171f078c0

ผล, เช่นเดียวกับข้างต้น

หากคุณมีเคอร์เนลตัวเดียวกันรันอยู่ในหลาย ๆ เครื่องมันง่ายกว่าที่จะใช้ตัวแปรนี้เพราะคุณไม่จำเป็นต้องใช้อิมเมจดีบักเฉพาะค่าออฟเซ็ต

หากต้องการ (ค้นพบค่าชดเชยนี้ได้ง่ายในตอนแรกคุณต้องใช้ภาพดีบัก:

$ pahole -C unix_sock /usr/lib/debug/boot/vmlinux-3.2.0-4-amd64
struct unix_sock {
  (...)
  struct sock *              peer;                 /*   680     8 */
  (...)
}

ที่นี่คุณไป 680 ไบต์นี่คือ 85 x 64 บิตหรือ 170 x 32 บิต

ส่วนใหญ่ของเครดิตสำหรับคำตอบนี้ไปMVG


2
อีกวิธีหนึ่งในการดึงออฟเซ็ตอาจสร้างซ็อกเก็ตแพร์ระบุรายการที่เกี่ยวข้องใน / proc / net / unix ตามหมายเลขไอโหนดจาก readlinks บน / proc / pif / fd / * และสแกนหน่วยความจำรอบที่อยู่ของซ็อกเก็ตสำหรับ ที่อยู่ของอื่น ๆ ที่สามารถทำให้เป็นแบบพกพาที่สมเหตุสมผล (ตรงข้ามรุ่น Linux และสถาปัตยกรรม) ที่สามารถนำมาใช้โดยตัวของมันเอง ฉันจะลองคิดหา PoC
Stéphane Chazelas

2
ตอนนี้ฉันได้เพิ่มPoCซึ่งดูเหมือนว่าทำงานได้ดีกับระบบที่ฉันทดสอบ
Stéphane Chazelas

5

วิธีแก้ปัญหานี้แม้ว่าจะใช้งานได้นั้นมีความสนใจ จำกัด เนื่องจากถ้าคุณมี systemtap เพียงพอเมื่อเร็ว ๆ นี้โอกาสที่คุณจะมีเคอร์เนลที่เพียงพอล่าสุดซึ่งคุณสามารถใช้วิธีการssพื้นฐาน และหากคุณใช้เคอร์เนลรุ่นเก่านั่นก็คือ วิธีแก้ปัญหาแม้ว่าแฮ็คมากขึ้นมีแนวโน้มที่จะทำงานได้มากกว่าและไม่ต้องการซอฟต์แวร์เพิ่มเติม

ยังคงมีประโยชน์เป็นการสาธิตวิธีการใช้systemtapงานประเภทนี้

หากบนระบบลีนุกซ์ล่าสุดที่มี systemtap (1.8 หรือใหม่กว่า) คุณสามารถใช้สคริปต์ด้านล่างเพื่อโพสต์ประมวลผลผลลัพธ์ของlsof:

ตัวอย่างเช่น:

$ lsof -aUc nm-applet | sudo สคริปต์นั้น
คำสั่ง PID USER FD ประเภทอุปกรณ์ขนาด / ปิดชื่อโหนด
นาโนเมตรแอปเพล็ 4183 Stephane 4u ยูนิกซ์ 0xffff8800a055eb40 0t0 36,888 type = STREAM -> 0xffff8800a055e7c0 [dbus-ภูต, 4190 @ / tmp / dbus-AiBCXOnuP6] 
นาโนเมตรแอปเพล็ 4183 Stephane 7U ยูนิกซ์ 0xffff8800a055e440 0t0 36,890 type = STREAM -> 0xffff8800a055e0c0 [Xorg, 3080 @ / tmp / .X11-Unix / X 0] 
นาโนเมตรแอปเพล็ 4183 8U Stephane ยูนิกซ์ 0xffff8800a05c1040 0t0 36201 type = STREAM -> 0xffff8800a05c13c0 [dbus-ภูต, 4118, @ / tmp / dbus-yxxNr1NkYC] 
นาโนเมตรแอปเพล็ 4183 11u Stephane ยูนิกซ์ 0xffff8800a055d080 0t0 36,219 type = STREAM -> 0xffff8800a055d400 [dbus-ภูต, 4118, @ / tmp / dbus-yxxNr1NkYC] 
นาโนเมตรแอปเพล็ 4183 Stephane 12U ยูนิกซ์ 0xffff88022e0dfb80 0t0 36,221 type = STREAM -> 0xffff88022e0df800 [dbus-ภูต, 2268, / var / ทำงาน / dbus / system_bus_socket]
nm-applet 4183 stephane 13u unix 0xffff88022e0f80c0 0t0 37025 ประเภท = STREAM -> 0xffff88022e29ec00 [dbus-daemon, 2268, / var / run / dbus / system_bus_socket]

(หากคุณเห็น 0x00000000000000000000 ด้านบนแทนที่จะเป็น 0xffff ... นั่นเป็นเพราะkernel.kptr_restrictพารามิเตอร์ sysctl ถูกตั้งค่าในระบบของคุณซึ่งทำให้เคอร์เนลเคอร์เนลถูกซ่อนจากกระบวนการที่ไม่มีสิทธิพิเศษซึ่งในกรณีนี้คุณจะต้องเรียกใช้lsofเป็นรูทเพื่อรับ ผลลัพธ์ที่มีความหมาย)

สคริปต์นี้ไม่ได้พยายามจัดการกับชื่อไฟล์ซ็อกเก็ตด้วยอักขระขึ้นบรรทัดใหม่ แต่จะไม่ทำเช่นนั้นlsof(และไม่lsofจัดการกับช่องว่างหรือโคลอน)

systemtapที่นี่ใช้เพื่อถ่ายโอนข้อมูลที่อยู่และที่อยู่เพียร์ของunix_sockโครงสร้างทั้งหมดในunix_socket_tableแฮชในเคอร์เนล

ทดสอบเฉพาะกับ Linux 3.16 amd64 พร้อม systemtap 2.6 และ 3.13 พร้อม 2.3

#! /usr/bin/perl
# meant to process lsof output to try and find the peer of a given
# unix domain socket. Needs a working systemtap, lsof, and superuser
# privileges. Copyright Stephane Chazelas 2015, public domain.
# Example: lsof -aUc X | sudo this-script
open STAP, '-|', 'stap', '-e', q{
  probe begin {
    offset = &@cast(0, "struct sock")->__sk_common->skc_node;
    for (i = 0; i < 512; i++) 
      for (p = @var("unix_socket_table@net/unix/af_unix.c")[i]->first;
           p;
           p=@cast(p, "struct hlist_node")->next
          ) {
        sock = p - offset;
        printf("%p %p\n", sock, @cast(sock, "struct unix_sock")->peer);
    }
    exit()
  }
};  
my %peer;
while (<STAP>) {
  chomp;
  my ($a, $b) = split;
  $peer{$a} = $b;
}
close STAP;

my %f, %addr;
open LSOF, '-|', 'lsof', '-nPUFpcfdn';
while (<LSOF>) {
  if (/(.)(.*)/) {
    $f{$1} = $2;
    if ($1 eq 'n') {
      $addr{$f{d}}->{"$f{c},$f{p}" . ($f{n} =~ m{^([@/].*?)( type=\w+)?$} ? ",$1" : "")} = "";
    }
  }
}
close LSOF;

while (<>) {
  chomp;
  for my $addr (/0x[0-9a-f]+/g) {
    my $peer = $peer{$addr};
    if (defined($peer)) {
      $_ .= $peer eq '0x0' ?
            "[LISTENING]" :
            " -> $peer\[" . join("|", keys%{$addr{$peer}}) . "]";
      last;
    }
  }
  print "$_\n";
}

parse error: unknown statistic operator @var: ฉันพลาดอะไรไปรึเปล่า?
Totor

@Totor @varถูกบันทึกอยู่ใน systemtap 1.8, 2012-06-17 (ล่าสุดคือ 2.7)
Stéphane Chazelas

2

4.89 ของ lsof รองรับการแสดงตัวเลือกปลายทาง

ยกมาจาก lsof.8:

+|-E +E specifies that process intercommunication channels should be
     displayed with endpoint information and the channels
     of the endpoints should also be displayed.  Currently
     only pipe on Linux is implemented.

     Endpoint information is displayed in the NAME column
     in the form "PID,cmd,FDmode".  PID is the endpoint
     process ID; cmd is the endpoint process command; FD is
     the endpoint file's descriptor; and mode is the
     endpoint file's access mode.  Multiple occurrences of
     this information can appear in a file's NAME column.

     -E specfies that Linux pipe files should only be
     displayed with endpoint information.

ตัวอย่างผลลัพธ์:

mozStorag 21535 22254  yamato    6u     unix 0xf...       0t0     348924 type=STREAM pino=351122 4249,dbus-daem,55u
mozStorag 21535 22254  yamato   10u     unix 0xf...       0t0     356193 type=STREAM pino=356194 21535,gdbus,11u
mozStorag 21535 22254  yamato   11u     unix 0xf...       0t0     356194 type=STREAM pino=356193 21535,gdbus,10u
mozStorag 21535 22254  yamato   21u     unix 0xf...       0t0     355141 type=STREAM pino=357544 4249,dbus-daem,60u
mozStorag 21535 22254  yamato   26u     unix 0xf...       0t0     351134 type=STREAM pino=355142 5015,gdbus,17u
mozStorag 21535 22254  yamato   69u     unix 0xf...       0t0     469354 type=STREAM pino=468160 4545,alsa-sink,21u
mozStorag 21535 22254  yamato   82u     unix 0xf...       0t0     449383 type=STREAM pino=449384 12257,Chrome_Ch,3u
mozStorag 21535 22254  yamato   86u     unix 0xf...       0t0     355174 type=SEQPACKET pino=355175 21535,gdbus,95u
mozStorag 21535 22254  yamato   95u     unix 0xf...       0t0     355175 type=SEQPACKET pino=355174 21535,gdbus,86u 12257,Chrome_Ch,4u
mozStorag 21535 22254  yamato  100u     unix 0xf...       0t0     449389 type=STREAM pino=456453 3614,Xorg,38u
mozStorag 21535 22254  yamato  105u     unix 0xf...       0t0     582613 type=STREAM pino=586261
obexd     22163        yamato    1u     unix 0xf...       0t0     361859 type=STREAM pino=365931
obexd     22163        yamato    2u     unix 0xf...       0t0     361860 type=STREAM pino=365934
obexd     22163        yamato    3u     unix 0xf...       0t0     361241 type=DGRAM pino=10028
obexd     22163        yamato    6u     unix 0xf...       0t0     361242 type=STREAM pino=361864 4249,dbus-daem,70u

2

เนื่องจาก Linux kernel 4.2 นั้นมีอยู่CONFIG_UNIX_DIAGซึ่งให้ข้อมูลเพิ่มเติมเกี่ยวกับซ็อกเก็ตโดเมน UNIX คือข้อมูลVirtual File System(VFS) ซึ่งมีข้อมูลที่ขาดหายไปเพื่อเชื่อมโยง Inode จากพา ธ ไปยังกระบวนการ สามารถสอบถามได้โดยใช้ssเครื่องมือจากiproute2เริ่มต้นด้วยรุ่นv4.19.0 ~ 55 :

$ ss --processes --unix --all --extened
...
Netid  State   Recv-Q  Send-Q  Local Address:Port      Peer Address:Port
u_str  LISTEN  0       5         /tmp/socket 13381347             * 0     users:(("nc",pid=12550,fd=3)) <-> ino:1569897 dev:0/65025 peers:

หมายเลขอุปกรณ์และเส้นทาง Inode ที่คุณจะได้รับ

$ stat -c 'ino:%i dev:0/%d' /tmp/socket
ino:1569946 dev:0/65025

ss ยังรองรับการกรอง:

 ss --processes --unix --all --extended 'sport = /tmp/socket'

แต่โปรดระวังว่านี่อาจไม่แสดงซ็อกเก็ตที่เหมาะสมสำหรับคุณเนื่องจากกระบวนการที่ชั่วร้ายอาจเปลี่ยนชื่อซ็อกเก็ตดั้งเดิมของคุณและแทนที่ด้วยซ็อกเก็ตที่เป็นของตัวเอง:

mv /tmp/socket /tmp/socket.orig
nc -U -l /tmp/socket.evil &
mv /tmp/socket.evil /tmp/socket

lsof /tmp/socket, fuser /tmp/socketและss --processes --unix --all --extended 'sport = /tmp/socket'ทั้งหมดจะแสดงรายการกระบวนการเดิมไม่เปลี่ยนความชั่วร้าย ใช้สิ่งนี้แทน:

id=$(stat -c 'ino:%i dev:0/%d' /tmp/socket)
ss --processes --unix --all --extended | grep -F "$id"

หรือเขียนโปรแกรม litte ของคุณเองที่ใช้เทมเพลทที่มีอยู่ในมนุษย์ 7 sock_diag

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.