How to turn a regular file into a symlink on Linux - linux

I'm writing an (un)archiving tool and the way it is designed it first creates a regular file from the archive before it examines the special attributes and may decide that this item is a symlink, in fact.
Note: Before more people misunderstand me for wanting to make a symlink of a file. No, I write the symlink data, i.e. its path, into the file, and then I want to tell the file system that this is a symlink
I've been developing this on OS X, where it's possible to turn a regular file into a symlink by simply setting its Type and Creator codes accordingly.
Now I like to get this code working on Linux as well. So I like to find a similar way there.
I am aware that the normal way to create a symlink is to call the symlink() function, but I wonder if there is also a way to change a regular file into a symlink, just like it's possible in OSX's BSD system, so that I do not have to refactor my working code too much?
There is lstat(), which returns the file type in st_mode's upmost bits. Now I wonder if there's also an analogous setter function for this mode field.

I don't believe there is a way in Linux to do this as you describe. IIRC, the filesystem stores symlink information in the inode table and not in a regular file so there's no direct way of turning a file into a link.
If the symlink's path is stored inside the file, why not read out the path, delete the file, and create a symlink in its place?

Demonstrating what I wrote as a comment to bmarguiles's answer,
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv) {
char *buffer = 0, *name = 0;
int i;
for (i = 1; i < argc; i++) {
struct stat st;
int fd = open(argv[i], O_RDONLY);
fstat(fd, &st);
buffer = realloc(buffer, st.st_size + 1);
read(fd, buffer, st.st_size);
close(fd);
buffer[st.st_size] = '\0';
name = realloc(name, strlen(argv[i]) + 2);
sprintf(name, "%s~", argv[i]);
symlink(buffer, name);
rename(name, argv[i]);
}
free(buffer);
free(name);
return 0;
}
$ vi f2s.c
...
$ cc -o f2s f2s.c
$ echo -n / > test
$ ./f2s test
$ ls -l test
lrwxrwxrwx 1 me me 1 Feb 24 23:17 test -> /
$ echo -n / > test2
$ strace ./f2s test2
open("test2", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=1, ...}) = 0
read(3, "/", 1) = 1
close(3) = 0
symlink("/", "test2~") = 0
rename("test2~", "test2") = 0
This is just a demonstration; it really needs more error-handling and maybe a better temporary filename.

No, you can't turn one into the other. You have to unlink to kill the file and then symlink to create a symlink as a replacement.

Related

On Linux, why does this library loaded with LD_PRELOAD catch only some openat() calls?

I am trying to intercept openat() calls with the following library comm.c. This is very standard minimal example, nothing special about it. I compile it with
>gcc -shared -Wall -fPIC -Wl,-init,init comm.c -o comm.so
I am pasting this standard minimal example to show that, I thought, I knew what I was doing.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
typedef int (*openat_type)(int, const char *, int, ...);
static openat_type g_orig_openat;
void init() {
g_orig_openat = (openat_type)dlsym(RTLD_NEXT,"openat");
}
int openat(int dirfd, const char* pathname, int flags, ...) {
int fd;
va_list ap;
if (flags & (O_CREAT)) {
va_start(ap, flags);
fd = g_orig_openat(dirfd, pathname, flags, va_arg(ap, mode_t));
}
else
fd = g_orig_openat(dirfd, pathname, flags);
printf("openat dirfd %d pathname %s\n", dirfd, pathname);
return fd;
}
I am running a tar command, again a minimal example, untarring an archive containing a single file foobar, to a pre-existing subdirectory dir:
>strace -f tar xf foobar.tar -C dir 2>&1 | grep openat
openat(AT_FDCWD, "dir", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 4
openat(4, "foobar", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0600) = -1 EEXIST (File exists)
openat(4, "foobar", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0600) = 5
However,
>LD_PRELOAD=./comm.so tar xf foobar.tar -C dir
openat dirfd 4 pathname foobar
openat dirfd 4 pathname foobar
OK, I know how to handle this - I have done this before - the reason for this kind of discrepancy, is that the system call openat() that is shown by strace is not done by the same-named user function openat(). To find out what that other user function is, one gets the sources, rebuilds them, and finds out.
So, I got the sources for my tar:
>$(which tar) --version
tar (GNU tar) 1.26
I got the tar 1.26 sources and rebuilt them myself, and, lo and behold, if I use the binary tar that I built, rather than the above installed one, then comm.so does catch all 3 openat calls!
So that means there is no "other user function".
Please help, what is possibly going on here??
NO, the question is not answered by that previous question. That previous answer simply said, the library call may be differently named, than the underlying system call. Here, that is NOT the case because I recompiled the same code myself, and there are no other library calls in there.
According to the discussion mentioned, openat will probably be called by different symbol or function. The system call dumped by tool such as strace is raw system call. It might be wrapped by user function or glibc. If you want intercept it by LD_PRELOAD, you need to find out those wrapper instead of openat. To my experience, you can try intercept open64 or open, it can redirect to openat which you observe on strace.
The link is one example to wrap openat from open64.

mknod() not creating named pipe

I'm trying to create a FIFO named pipe using the mknod() command:
int main() {
char* file="pipe.txt";
int state;
state = mknod(file, S_IFIFO & 0777, 0);
printf("%d",state);
return 0;
}
But the file is not created in my current directory. I tried listing it by ls -l . State returns -1.
I found similar questions here and on other sites and I've tried the solution that most suggested:
int main() {
char* file="pipe.txt";
int state;
unlink(file);
state = mknod(file, S_IFIFO & 0777, 0);
printf("%d",state);
return 0;
}
This made no difference though and the error remains. Am I doing something wrong here or is there some sort of system intervention which is causing this problem?
Help.. Thanks in advance
You are using & to set the file type instead of |. From the docs:
The file type for path is OR'ed into the mode argument, and the
application shall select one of the following symbolic
constants...
Try this:
state = mknod(file, S_IFIFO | 0777, 0);
Because this works:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
char* file="pipe.txt";
int state;
unlink(file);
state = mknod(file, S_IFIFO | 0777, 0);
printf("state %d\n", state);
return 0;
}
Compile it:
gcc -o fifo fifo.c
Run it:
$ strace -e trace=mknod ./fifo
mknod("pipe.txt", S_IFIFO|0777) = 0
state 0
+++ exited with 0 +++
See the result:
$ ls -l pipe.txt
prwxrwxr-x. 1 lars lars 0 Jul 16 12:54 pipe.txt

Difficulty in using execve

I am trying to execute "word count" command on file given by absolute path - "/home/aaa/xxzz.txt" . I have closed the stdin so as to take input from file but the program doesn't give any output .
Also if I add some statement after "execve" command, it is also getting executed . Shouldn't the program exit after execve ?
int main()
{
char *envp[]={NULL };
int fd=open("/home/aaa/xxzz.txt",O_RDONLY);
close(0);
dup(fd);
char *param[]={ "/bin/wc",NULL } ;
execve("/bin/wc",param,envp);
}
Probably wc does not live in /bin (except for some systems which symlink that to /usr/bin, because wc normally lives in the latter). If I change the path in your example to /usr/bin/wc, it works for me:
#include <unistd.h>
#include <fcntl.h>
int
main()
{
char *envp[] = {NULL};
int fd = open("/home/aaa/xxzz.txt", O_RDONLY);
close(0);
dup(fd);
char *program = "/usr/bin/wc";
char *param[] = {program,NULL};
execve(program, param, envp);
}

Is there a way can view real process cmdline on linux?

Here is a simple code to fake process name and cmdline on linux:
#include <string.h>
#include <sys/prctl.h>
#include <stdio.h>
#include <unistd.h>
#define NewName "bash"
#define ProcNameMaxLen 16
int main(int argc, char **argv){
int oldlen = strlen(*argv);
char procname[ProcNameMaxLen];
memset(*argv, 0, oldlen);
memccpy(*argv, NewName, 0, oldlen); //modify cmdline
memccpy(procname, NewName, 0, ProcNameMaxLen);
prctl(PR_SET_NAME, procname); //modify procname
sleep(60);
return 0;
}
After run this code I can't view real name by ps,
but something can find in /proc/xxx/exe and /proc/xxx/environ, but so cumbersome.
is there a good way can view real information with all process?
I think this is a big security problem because i usually check process by ps on my server.
way 1: lsof -d txt
Wait more answer...
lsof will tell you the original executable name as it is one of the open files of the malicious process. You can inspect a number of processes using the -p option, or query a single user with the -u option.

Read a single sector from a disk

I am trying to read a single specific sector from the disk directly. I've currently run out of ideas and any suggestions how to go about it would be great!
Try something like this to do it from the CLI:
# df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 27G 24G 1.6G 94% /
# dd bs=512 if=/dev/sda2 of=/tmp/sector200 skip=200 count=1
1+0 records in
1+0 records out
From man 4 sd:
FILES
/dev/sd[a-h]: the whole device
/dev/sd[a-h][0-8]: individual block partitions
And if you want to do this from within a program, just use a combination of system calls from man 2 ... like open, lseek,, and read, with the parameters from the dd example.
I'm not sure what the best programmatic approach is, but from the Linux command-line you could use the dd command in combination with the raw device for your disk to directly read from the disk.
You need to sudo this command to get access to the raw disk device (e.g. /dev/rdisk0).
For example, the following will read a single 512-byte block from an offset of 900 blocks from the top of disk0 and output it to stdout.
sudo dd if=/dev/rdisk0 bs=512 skip=900 count=1
See the dd man page to get additional information on the parameters to dd.
In C it is something like the following... It would require root permissions. I think you need to open the file with O_DIRECT if you want to read single sectors. Otherwise you'll get a page. I'm not sure if the aligned buffer is required for a read, but it is for a write.
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SECTOR_SIZE 512
int main(int argc, char *argv[]) {
int offset = 0;
int length = 5;
int rc = -1;
char *sector = aligned_alloc(SECTOR_SIZE, SECTOR_SIZE);
memset(sector, 0, SECTOR_SIZE);
/* replace XXX with the source block device */
int fd=open("/dev/XXX", O_RDWR | O_DIRECT);
lseek(fd, offset, SEEK_SET);
for (int i = 0; i < length; i++) {
rc = read(fd, sector, SECTOR_SIZE);
if (rc < 0)
printf("sector read error at offset = %d + %d\n %s", offset, i, strerror(errno));
printf("Sector: %d\n", i);
for (int j = 0; j < SECTOR_SIZE; j++) {
printf("%x", sector[i]);
if ((j + 1) % 16 == 0)
printf("\n");
}
}
free(sector);
close(fd);
}
The other folks have pretty much covered it. You need to
access to the disk's device file (either be root or, better, change the permissions on it)
use the file IO functions to read sectors = chunks of (usually) 512 bytes from said disk.
Another alternative is to use hdparm
For instance-
hdparm --read-sector 16782858 /dev/sda

Resources