[LINUX] The story of trying to push SSH_AUTH_SOCK obsolete on screen with LD_PRELOAD

\ <ins > 2018/04/26 postscript

What happened after two years

Write a shell function called find_agent to resolve it manually each time. It's certainly convenient to switch automatically, but there are few times when this is necessary, and LD_PRELOAD sometimes gets in the way when debugging, so there was not much merit ...

I have used github to check the connection of SSH_AUTH_SOCK. The key I usually use is also put in github, so I'm having trouble with ssh because it's about the timing of git push. Socket files should glob the pathname appropriately

find_agent () {
    local GLOBS=("/tmp/com.apple.launchd.*/Listeners" "/tmp/ssh-*/agent.*");
    for g in "${GLOBS[@]}"; do
        for c in ${g}; do
            SSH_AUTH_SOCK="$c";
            [ ! -S "${SSH_AUTH_SOCK}" ] && continue;
            ssh -T [email protected];
            [ $? -eq 1 ] && return 0;
        done;
    done
}

</ins>

Below is the description of 2016/02/12


github https://github.com/takei-yuya/alt_ssh_auth_sock

I came to Akita on a ski trip, but I was free at the inn so I wrote it.

What is this?

A story to prevent the problem that the reference of SSH_AUTH_SOCK used for ssh-agent transfer by detach / attach is misaligned when using screen at the connection destination of ssh.

In other words, this kind of thing

local $ # ssh-Enable agent forwarding and connect to hostA
local $ ssh -A hostA

hostA $ #Ssh automatically at the connection destination_AUTH_SOCK environment variable set
hostA $ declare -p SSH_AUTH_SOCK
declare -x SSH_AUTH_SOCK="/tmp/ssh-XXXXXXXXXX/agent.11111"
hostA $ #This socket is connected to the sshd for this session
hostA $ ps -p 11111
  PID TTY          TIME CMD
11111 ?        00:00:00 sshd
hostA $ #Even if there is no key on hostA, the key information is transferred through the socket.
hostA $ #You can connect to another host with key authentication
hostA $ ssh hostB  
hostB $ exit

hostA $ #screen saves the environment variables when screen is started
hostA $ screen -S ssh_test
hostA(screen) $ declare -p SSH_AUTH_SOCK
declare -x SSH_AUTH_SOCK="/tmp/ssh-XXXXXXXXXX/agent.11111"
hostA(screen) $ ^A^D  #Detach

hostA $ #This socket becomes invalid when the SSH session expires
hostA $ #Try to reconnect
hostA $ exit
local $ ssh -A hostA
hostA $ #The socket path changes because the session has changed
hostA $ declare -p SSH_AUTH_SOCK
declare -x SSH_AUTH_SOCK="/tmp/ssh-YYYYYYYYYY/agent.33333"
hostA $ #Old session sshd is gone
hostA $ ps -p 11111
  PID TTY          TIME CMD

hostA $ #But in the screen session, the environment variables are still out of date ...
hostA $ screen -x ssh_test
hostA(screen) $ declare -p SSH_AUTH_SOCK
declare -x SSH_AUTH_SOCK="/tmp/ssh-XXXXXXXXXX/agent.11111"
hostA(screen) $ #Old ssh-agent transfer does not connect ...
hostA(screen) $ ssh hostB
Permission denied (publickey).

So, it's been a long time, but this kind of phenomenon. I want to do something about this.

The cause is that the environment variables are not updated in the session on the screen.

what will you do?

Try using a mechanism called LD_PRELOAD.

Roughly speaking, it seems to be a mechanism to forcibly insert a dynamic library when starting a process. It can be used to hook or steal 400,000 system calls and library function calls.

The target function is getenv. In other words, the idea of forcibly tampering with environment variables from the outside.

Check the current SSH_AUTH_SOCK and if you can't connect to the socket, try another socket. The socket candidates to try are specified by the file glob pattern from another environment variable.

In other words

hostA(screen) $ ssh hostB
Permission denied (publickey).
hostA(screen) $ export LD_PRELOAD="/path/to/injection_lib"
hostA(screen) $ export ALT_SSH_AUTH_SOCK="/tmp/ssh-*/agent.*"
hostA(screen) $ ssh hostB

This happens

github: https://github.com/takei-yuya/alt_ssh_auth_sock

#define _GNU_SOURCE  // for RTLD_NEXT
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>

#include <glob.h>

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

//Static variable to save the original getenv
static char* (*_original_getenv)(const char *name) = NULL;

//It seems that you can create a function to execute before the main function by using the GNU extension.
static void _alt_ssh_auth_sock_init() __attribute__((constructor));
static void _alt_ssh_auth_sock_init() {
  //Save the original getenv
  _original_getenv = dlsym(RTLD_NEXT, "getenv");
}

//Make sure the socket is alive. ...... I'm just connecting.
int check_socket(const char* socket_file_path) {
  struct sockaddr_un addr;
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, socket_file_path, sizeof(addr.sun_path) / sizeof(char));

  int fd = socket(AF_UNIX, SOCK_STREAM, 0);
  int ret = connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
  close(fd);
  return ret;
}

//Override the behavior of getenv
char* getenv(const char *name) {
  if (!_original_getenv) {
    //Something__attribute__((constructor))May not work, so just in case
    _alt_ssh_auth_sock_init();
  }

  if (strcmp(name, "SSH_AUTH_SOCK") != 0) {
    // SSH_AUTH_Except for SOCK, the original function is used as it is.
    return _original_getenv(name);
  }

  char* ssh_auth_sock = _original_getenv("SSH_AUTH_SOCK");
  if (!ssh_auth_sock) {
    // SSH_AUTH_If SOCK is not defined, return as it is.
    return ssh_auth_sock; // == NULL
  }

  char* alt_ssh_auth_sock = _original_getenv("ALT_SSH_AUTH_SOCK");
  if (!alt_ssh_auth_sock) {
    //If the alternative socket pattern is not given, nothing can be done, so give up and return as it is
    return ssh_auth_sock;
  }

  if (check_socket(ssh_auth_sock) == 0) {
    //If the current socket is alive, return as it is without glob expansion
    return ssh_auth_sock;
  }

  //Expand glob pattern
  glob_t pglob;
  if (glob(alt_ssh_auth_sock, GLOB_NOSORT, NULL, &pglob) != 0) {
    globfree(&pglob);
  }

  int i;
  for (i = 0; i < pglob.gl_pathc; ++i) {
    if (check_socket(pglob.gl_pathv[i]) == 0) {
      //If you find a living socket
      break;
    }
  }
  if (i < pglob.gl_pathc) {
    // SSH_AUTH_Overwrite SOCK
    setenv("SSH_AUTH_SOCK", pglob.gl_pathv[i], 1);
    ssh_auth_sock = _original_getenv("SSH_AUTH_SOCK");
  }
  globfree(&pglob);

  return ssh_auth_sock;
}

All posted because it is short. The details are as I wrote in the comment, but after saving the original getenv, I am using it from within getenv to overwrite.

How to build

cc -Wall -fPIC -shared -o libaltsshauthsock.so alt_ssh_auth_sock.c -ldl

Maybe you need soname. A little suitable around here.

How to use

For the time being, I think it's okay to set environment variables in bashrc or something.

$ echo 'export LD_PRELOAD="/path/to/libaltsshauthsock.so"' >> ~/.bashrc
$ echo 'export ALT_SSH_AUTH_SOCK="/tmp/ssh-*/agent.*"' >> ~/.bashrc

All you have to do is use screen as usual. You should be able to use ssh and git without doing anything even if you repeat attaching / detaching and disconnecting the session.

What to do next

TODO:

--Compatible with Mac --For Mac, it seems to use the environment variable DYLD_INSERT_LIBRARIES instead of LD_PRELOAD. --In addition, namespace? It seems that it is necessary to set the environment variable DYLD_FORCE_FLAT_NAMESPACE as well. --Make dylib with Makefile ―― …… But I don't have many chances to connect ssh to Mac ……. ――The cost of connecting the socket every time isn't it? ――I think it's huge, so I want to do something about it. ――Maybe the straightforward method is to set up a daemon that pipes one-to-many sockets while managing the life and death of sockets, and set the socket connected to that daemon to SSH_AUTH_SOCK. ――I tried to use gdb to hit a process, kick setenv, and forcibly rewrite the environment variables of that process, but I feel that it is too powerful. --Make it a proper implementation ――Would you like to check for errors? --Skiing ――I came to Akita to ski, so I will ski. Not for development.

Recommended Posts

The story of trying to push SSH_AUTH_SOCK obsolete on screen with LD_PRELOAD
The story of trying to reconnect the client
The story of failing to update "calendar.day_abbr" on the admin screen of django
Story of trying to use tensorboard with pytorch
The story of trying to contribute to COVID-19 analysis with AWS free tier and failing
The story of not being able to run pygame with pycharm
The story of adding MeCab to ubuntu 16.04
The story of trying deep3d and losing
The story of pep8 changing to pycodestyle
A story of a deep learning beginner trying to classify guitars on CNN
The story of doing deep learning with TPU
Introduction to Python with Atom (on the way)
The story of trying Sourcetrail × macOS × VS Code
The story of making soracom_exporter (I tried to monitor SORACOM Air with Prometheus)
The story of moving from Pipenv to Poetry
A story of a high school graduate technician trying to predict the survival of the Titanic
A story that failed when trying to remove the suffix from the string with rstrip
I can't find the clocksource tsc! ?? The story of trying to write a kernel patch
I want to plot the location information of GTFS Realtime on Jupyter! (With balloon)
An easy way to pad the number with zeros depending on the number of digits [Python]
A memo on how to overcome the difficult problem of capturing FX with AI
A memo of misunderstanding when trying to load the entire self-made module with Python3
A story about trying to introduce Linter in the middle of a Python (Flask) project
The story of stopping the production service with the hostname command
The story of replacing Nvidia GTX 1650 with Linux Mint 20.1.
Add information to the bottom of the figure with Matplotlib
The story of sharing the pyenv environment with multiple users
Try to estimate the number of likes on Twitter
Try to get the contents of Word with Golang
Transit to the update screen with the Django a tag
The story of wanting to buy Ring Fit Adventure
The story of using circleci to build manylinux wheels
How to register the same data multiple times with one input on the Django management screen
How to know the number of GPUs from python ~ Notes on using multiprocessing with pytorch ~
I tried to find the entropy of the image with python
Memo to get the value on the html-javascript side with jupyter
The story of implementing the popular Facebook Messenger Bot with python
I tried to find the average of the sequence with TensorFlow
read the tag assigned to you on ec2 with boto3
The story of how the Python bottle worked on Sakura Internet
The story of introducing jedi (python auto-completion package) to emacs
Let's execute the command on time with the bot of discord
Settings to debug the contents of the library with VS Code
The story of rubyist struggling with python :: Dict data with pycall
Try to automate the operation of network devices with Python
Until the start of the django tutorial with pycharm on Windows
A story about how to deal with the CORS problem
The story of copying data from S3 to Google's TeamDrive
When generating a large number of graphs with matplotlib, I do not want to display the graph on the screen (jupyter environment)
Technology that supports jupyter: traitlets (story of trying to decipher)
Save images on the web to Drive with Python (Colab)
The story that the private key is set to 600 with chmod
The story of making a question box bot with discord.py
Get the host name of the host PC with Docker on Linux
Get the source of the page to load infinitely with python.
Try to extract the features of the sensor data with CNN
I tried to unlock the entrance 2 lock sesame with a single push of the AWS IoT button
Two-factor authentication with Cognito + Amplify (Enter ID / PW / "two-factor code value" on the login screen to authenticate)
The story of the algorithm drawing a ridiculous conclusion when trying to solve the traveling salesman problem properly
The story of building Zabbix 4.4
[Apache] The story of prefork