Forum: PC-Programmierung "/proc might be inaccessible"?


von Bauform B. (bauformb)


Lesenswert?

Mahlzeit!

Eigentlich sollte mein Programm nur /proc/self/exe lesen. Und dann finde 
ich das[1]:
1
a) /proc might not be mounted at all, or it might be mounted somewhere
2
   other than /proc.
3
b) / might not be / (e.g. chroot, containers, etc.), and so /proc might
4
   be inaccessible.
5
c) There are a variety of situations where the called process's binary
6
   is not directly accessible to the called process as a regular file,
7
   and /proc/self/exe appears to be a broken symlink.
a) egal, das sollte sich leicht beheben lassen.

b) funktioniert etwa "mount -t proc proc /proc" in so einem Container 
nicht? Was funktioniert ohne /proc überhaupt noch?

c) wenn das Programm auf einer anderen Partition liegt, ist die busy, 
umount wird verweigert. Das einzige, was mir einfällt: das Programm 
liegt auf einem NFS-Server und der hat sich verabschiedet. Das mag bei 
kleinen Programmen und viel RAM zunächst nicht auffallen, nur der Link 
ist kaputt. Aber was könnte noch alles hinter "variety" stecken?

[1] https://lwn.net/Articles/883113/

von Daniel A. (daniel-a)


Lesenswert?

Bauform B. schrieb:
> b) funktioniert etwa "mount -t proc proc /proc" in so einem Container
> nicht? Was funktioniert ohne /proc überhaupt noch?

Kommt drauf an, es gibt tatsächlich fälle, wo das innerhalb eines mount 
user namespaces nicht mehr erlaubt ist. Aber alle container manager, die 
ich kenne, mounten es sowieso von Anfang an.

> c) wenn das Programm auf einer anderen Partition liegt, ist die busy,
> umount wird verweigert. Das einzige, was mir einfällt: das Programm
> liegt auf einem NFS-Server und der hat sich verabschiedet. Das mag bei
> kleinen Programmen und viel RAM zunächst nicht auffallen, nur der Link
> ist kaputt. Aber was könnte noch alles hinter "variety" stecken?

Und auch wenn ich es lösche kann ich noch darauf zugreifen:
x.sh
1
set -ex
2
cp /bin/sh .
3
./sh -ex -c '
4
  rm ./sh
5
  ls -la /proc/$$/exe
6
  exec 4</proc/self/exe
7
  ls -la /proc/self/fd/4
8
  exec 4<&-
9
'
output:
1
+ cp /bin/sh .
2
+ ./sh -ex -c 
3
  rm ./sh
4
  ls -la /proc/$$/exe
5
  exec 4</proc/self/exe
6
  ls -la /proc/self/fd/4
7
  exec 4<&-
8
  /proc/self/exe -c "echo hello"
9
10
+ rm ./sh
11
+ ls -la /proc/25578/exe
12
lrwxrwxrwx 1 dpa users 0 19. Apr 14:05 /proc/25578/exe -> '/home/dpa/sh (deleted)'
13
+ exec
14
+ ls -la /proc/self/fd/4
15
lr-x------ 1 dpa users 64 19. Apr 14:05 /proc/self/fd/4 -> '/home/dpa/sh (deleted)'
16
+ exec
17
+ /proc/self/exe -c echo hello
18
hello

Es gibt auch noch pidfd_open, aber ich habe noch nie gesehen, dass das 
jemand nutzen würde.

von Bauform B. (bauformb)


Lesenswert?

Daniel A. schrieb:
> + ls -la /proc/self/fd/4
> lr-x------ 1 dpa users 64 19. Apr 14:05 /proc/self/fd/4 -> '/home/dpa/sh
> (deleted)

Aha, readlink liefert den Pfad mit angehängtem " (deleted)", was ja 
formal ein gültiger Dateiname ist. Also kann man den nicht mehr 1:1 
verwenden. Das gleiche passiert nach einem mv auf eine andere Partition. 
Ein mv auf der gleichen Partition aktualisiert auch den Link.

Mit NFS dazwischen gibt es erst dann ein (deleted), wenn der Cache 
aktualisiert wurde. Solange liefert stat -L Daten aus dem Cache, 
anschließend dann "Stale file handle".

Alles ziemlich normal, nicht perfekt, nicht tragisch. Unmittelbar nach 
dem execve() muss der Link einfach gültig sein. Anschließend gibt es 
viele Möglichkeiten, alles kaputt zu spielen. Zuverlässiger als argv[0] 
ist /proc/self/exe auf jeden Fall.

von Daniel A. (daniel-a)


Lesenswert?

Bauform B. schrieb:
> Daniel A. schrieb:
>> + ls -la /proc/self/fd/4
>> lr-x------ 1 dpa users 64 19. Apr 14:05 /proc/self/fd/4 -> '/home/dpa/sh
>> (deleted)
>
> Aha, readlink liefert den Pfad mit angehängtem " (deleted)", was ja
> formal ein gültiger Dateiname ist. Also kann man den nicht mehr 1:1
> verwenden.

Das interessante ist aber, wie man oben sieht, folgt es dem Link nicht, 
wenn man /proc/self/exe öffnet, sondern öffnet direkt die gelöschte 
Datei als wäre nichts gewesen.

von Foobar (asdfasd)


Lesenswert?

> Zuverlässiger als argv[0] ist /proc/self/exe auf jeden Fall.

Naja, popen("/proc/self/exe foo bar", "r") geht schon mal schief - 
"self" ist "/bin/sh".  Entspr. auch system().  NFS-Server könnten ein 
Problem sein (Datei verschwindet) und in wieweit Sachen über z.B. fuse 
funktionieren, weiß ich nicht.  Ebenso wenn Privilegien gedropt oder uid 
geändert werden.

Das Problem, seinen eigenen Pfad/Namen zu finden, war schon von (Unix) 
Anfang an ... problematisch[1].  Linux kam dann mit dem /proc/self/exe 
Symlink und das schien die Lösung zu sein - das " (deleted)" hat's dann 
doch wieder kaputt gemacht (tatsächlicher Name oder nur Metainfo?). 
Falls mehrere Pfade zum exe existieren (ggf mit unterschiedlichen 
Zugriffsrechten), weiß man auch nicht, welcher angezeigt wird.  Also 
benutzen die meisten weiterhin das ebenfalls unperfekte aber portable 
argv[0].

PS: Soviel ich weiß, war das vom Verhalten her immer ein Hardlink - die 
Präsentation als Softlink ist nur Informationshalber, eine erneute 
Pfadauflösung findet nicht statt.

Irgendwie ist /proc/self/exe ne Schmuddelecke :-)

PPS: Im verlinkten Thread geht es um einen Security-Bug falls argc==0 
ist.  Ist halt einfach ein Bug - die meisten Programme vertragen es auch 
nicht, wenn stdin/out/err geschlossen sind ...

[1] Ähnlich problematisch ist es, den Pfad des aktuellen Verzeichnisses 
(CWD) herauszufinden (Rekursiv ".." hochwandern und nach einem Namen 
durchsuchen, der die gleiche Inode wie "." hat).


--
Sorry für die Schwafelei.

: Bearbeitet durch User
von Bauform B. (bauformb)


Lesenswert?

Foobar schrieb:
>> Zuverlässiger als argv[0] ist /proc/self/exe auf jeden Fall.
>
> Naja, popen("/proc/self/exe foo bar", "r") geht schon mal schief -
> "self" ist "/bin/sh".  Entspr. auch system().

Warum gibt es eigentlich kein popen() ohne die shell dazwischen?

> Das Problem, seinen eigenen Pfad/Namen zu finden, war schon von (Unix)
> Anfang an ... problematisch[1].  Linux kam dann mit dem /proc/self/exe
> Symlink und das schien die Lösung zu sein - das " (deleted)" hat's dann
> doch wieder kaputt gemacht (tatsächlicher Name oder nur Metainfo?).

Kaputt macht es der User, der sein Programm löscht. Dann existiert die 
Datei und existiert gleichzeitig nicht. Nur wer weiß, was er tut, darf 
sie noch benutzen ;) Dafür ist (deleted) doch eine gute Beschreibung.

> Falls mehrere Pfade zum exe existieren (ggf mit unterschiedlichen
> Zugriffsrechten), weiß man auch nicht, welcher angezeigt wird.

Doch; der Pfad, der beim execve() benutzt wurde. Aber innen drin ist 
viel Magie im Spiel: ein rename(2) ändert auch den Link.

> Also benutzen die meisten weiterhin das ebenfalls unperfekte
> aber portable argv[0].

für Fehlermeldungen ist argv[0] ja auch richtig - und unbrauchbar, wenn 
man wissen will, wo das Programm installiert ist.

> PS: Soviel ich weiß, war das vom Verhalten her immer ein Hardlink - die
> Präsentation als Softlink ist nur Informationshalber, eine erneute
> Pfadauflösung findet nicht statt.

Ursprünglich (sagt 'man proc') lieferte /proc/*/exe die inode, das war 
natürlich viel einfacher und übersichtlicher und dann siegte die 
Bequemlichkeit...

> Irgendwie ist /proc/self/exe ne Schmuddelecke :-)

> PPS: Im verlinkten Thread geht es um einen Security-Bug falls argc==0
> ist.  Ist halt einfach ein Bug

Da hatte mich nur diese Aussage schockiert: "There are a variety of 
situations where the called process's binary is not directly accessible 
to the called process as a regular file". Was ja zunächst völlig 
unmöglich erscheint. Alle Beispiele dafür funktionieren ja auch erst 
hinterher und/oder sind mehr oder weniger illegal.

von Rolf M. (rmagnus)


Lesenswert?

Foobar schrieb:
>> Zuverlässiger als argv[0] ist /proc/self/exe auf jeden Fall.
>
> Naja, popen("/proc/self/exe foo bar", "r") geht schon mal schief -
> "self" ist "/bin/sh".

Gut, aber warum sollte man das machen wollen?

Foobar schrieb:
> Falls mehrere Pfade zum exe existieren (ggf mit unterschiedlichen
> Zugriffsrechten), weiß man auch nicht, welcher angezeigt wird.

Ich hätte gedacht, es wird der angezeigt, über den das Programm 
gestartet wurde.

>  Also benutzen die meisten weiterhin das ebenfalls unperfekte aber
> portable argv[0].

Naja, was bei einen popen(argv[0], …) rauskommt, ist dann eher 
Glückssache, weil der Aufrufer beim exec() da reinschreiben kann, was 
immer er will, und das ist mitnichten immer nur der Name des Executables 
inklusive vollständigem Pfad.
Das genannte Problem mit mehreren Pfaden zum Executable hat man da 
übrigens genauso.

> PPS: Im verlinkten Thread geht es um einen Security-Bug falls argc==0
> ist.  Ist halt einfach ein Bug - die meisten Programme vertragen es auch
> nicht, wenn stdin/out/err geschlossen sind ...

Ein Bug im Aufrufer.

Bauform B. schrieb:
> Warum gibt es eigentlich kein popen() ohne die shell dazwischen?

Das ist halt eine vereinfachte Komfort-Funktion auf Ebene der 
Standardlib (in dem Sinne, dass es FILE* für die Kommunikation nutzt). 
Du kannst natürlich immer fork/exec*/pipe/dup2 verwenden, um sowas auch 
ohne Shell zu implementieren. Genauso gibt es auch kein system() ohne 
Shell. Wenn man das braucht, nimmt man eben fork und execl.

>> Das Problem, seinen eigenen Pfad/Namen zu finden, war schon von (Unix)
>> Anfang an ... problematisch[1].  Linux kam dann mit dem /proc/self/exe
>> Symlink und das schien die Lösung zu sein - das " (deleted)" hat's dann
>> doch wieder kaputt gemacht (tatsächlicher Name oder nur Metainfo?).
>
> Kaputt macht es der User, der sein Programm löscht. Dann existiert die
> Datei und existiert gleichzeitig nicht.

In Unix löscht man als User/Programm eigentlich keine Dateien. Man löst 
nur Verzeichniseinträge (unlink). Und wenn kein Eintrag mehr existiert 
und die Datei auch von keinem Programm mehr geöffnet ist, d.h. der 
Referenz-Zähler wird 0, dann wird auch der Inhalt gelöscht. Deshalb kann 
man unter Linux Dateien "löschen", auch wenn ein Programm sie noch offen 
hat, was unter Windows so nicht geht, weil ihm dieser Mechanismus fehlt.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.