I am working on a legacy C++ project which uses functions from blkid/blkid.h to access files on the local hard disk.
Since the project is quite big and hard to compile/run, I thought it would be a good idea to run it in a container. Creating the dockerfile to create the images went fine, the problems started when actually running the legacy project in a container.
I get errors when trying to access files on the disk using "blkid_devno_to_devname" to get the device id on which to write.
Here is a sample program that reproduces my problem
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <blkid/blkid.h>
int main() {
struct stat st;
std::string path = "my_file_path";
if (stat(path.data(), &st))
{
std::cout << "cannot get file statistics for file " << path << std::endl;
}
char *device_path = blkid_devno_to_devname(st.st_dev);
if(device_path == NULL)
{
std::cout << "cannot get device path for device ID " + std::to_string(st.st_dev) << std::endl;
}
}
On a regular file on my machine the device_path is not null, but running it on a docker always returns null.
What is not clear to me is how a docker container sees the disks it writes to.
To dig in, I looked into the different macros coming with stat (https://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/sys/stat.h), I printed them on files on a container with different type of files (symlink, file, directory).
I modified the above little program to add this:
std::cout << "mode:" << st.st_mode << std::endl;
std::cout << "S_ISREG:" << S_ISREG(st.st_mode) << std::endl;
std::cout << "S_ISDIR:" << S_ISDIR(st.st_mode) << std::endl;
std::cout << "S_ISCHR:" << S_ISCHR(st.st_mode) << std::endl;
std::cout << "S_ISBLK:" << S_ISBLK(st.st_mode) << std::endl;
std::cout << "S_ISFIFO:" << S_ISFIFO(st.st_mode) << std::endl;
std::cout << "S_ISLNK:" << S_ISLNK(st.st_mode) << std::endl;
std::cout << "S_ISSOCK:" << S_ISSOCK(st.st_mode) << std::endl;
I thought there would be a difference depending on the type of layer the docker writes to, but I don't see any.
Be it on a volume, mount, or even on container layer (I mean on files neither on a volume nor mount).
On a mount, I would have expected the device id to be correct since as I understand it the mount is giving the container access to a physical drive (at least in my case - may not be true in general).
So why is blkid_devno_to_devname not returning anything on a docker container?
What am I not understanding properly?
P.S. for the record, blkid or /etc/fstab don't return anything on a container either.
Notes to the readers about my system:
working on a VM on windows (Oracle VM) with ubuntu 18 installed.
docker version: 19.03.6
Driver: overlay2
Related
I am developing a network scanner in C++ with the help of libtins library, I can be able to get MAC addresses and IP but I want to go further to know the vendor(eg: Intel Corporate) and Device Name (eg: DESKTOP-TO5P0BD) in C++
codes to get Mac and IP
// Retrieve the ARP layer info
const ARP& arp = pdu.rfind_pdu<ARP>();
std::cout << "Found :" << arp.sender_ip_addr() << ", " << arp.sender_hw_addr() << std::endl;
// Checking if it is an ARP reply?
if (arp.opcode() == ARP::REPLY) {
// Let's check if there's already an entry for this address
auto iter = addresses.find(arp.sender_ip_addr());
if (iter == addresses.end()) {
std::cout << "saving " << arp.sender_ip_addr() << ", " << arp.sender_hw_addr() << std::endl;
// We haven't seen this address. Save it.
addresses.insert({ arp.sender_ip_addr(), arp.sender_hw_addr() });
IPv4Address ip = arp.sender_ip_addr();
NetworkInterface iface(ip);
//std::cout << iface.name() << std::endl;
}
else {
std::cout << "already seen " << arp.sender_ip_addr() << ", " << arp.sender_hw_addr() << std::endl;
// We've seen this address. If it's not the same HW address, inform it
if (arp.sender_hw_addr() != iter->second) {
std::cout << "[WARNING] " << arp.sender_ip_addr() << " is at "
<< iter->second << " but also at " << arp.sender_hw_addr()
<< std::endl;
}
}
}
In order to get the vendor from the MAC address, you can have a look at this MAC OUI vendor database mantained by Wireshark. It's a text file with a simple format.
In order to get the "device name", you can do a NetBIOS name lookup. This StackOverflow question may help you.
If you'd like the Vendor Name from the MAC, you could get the MAC by reading arp -a (probably via the winapi). Next you need to search a vendor db, the wireshark list is good, there's also this one. As for the "Device Name", you could check this with WMI. Other than the more complex winapi for this, you could use a library like this one, which is much simpler. You'd need to make a request to the Win32_ComputerSystem class, which contains the Device Name and model number, among other things. MAC addresses can also be retrieved by WMI, instead make the query to Win32_NetworkAdapter - it gives all of the interfaces so be sure to find the right one!
i am trying to write a linux program that uses the c++ mount function (code below),
however, the mount operation requires permmissions, and running the program throws the errno 'Operation not permitted' (printed using perror)
tried some SO solutions but non was helpful, the alternative is to use the system("sudo mount..") but i prefer the c++ function.
is ther a way to use this function with permmissions?
IDE: Clion 2020.2.4
relevant code below
int returnValue = mount(sourcePath,targetPath,"", MS_SHARED, ""); //mounting the device
if (returnValue==0){
//mount completed
//somecode
}else{
//mount failed
std::cout<<"mount failed\n";
perror("");
}
output
mount failed
Operation not permitted
After you compile the code, change the ownership of the file to the superuser with chown root filename and add "set user or group ID on execution" to the mode of the executable file with chmod u+s filename.
Some options I see:
Just run the binary as root or under sudo;
Use setcap cap_sys_admin+ep on your binary to grant it the CAP_SYS_ADMIN capability;
If the set of possible targetPaths is fixed, edit /etc/fstab to give these paths the userflag.
#include <fstream>
#include <iostream>
#include <string>
int main(int argc, char *argv[]){
std::ifstream tmpfile;
std::string tmpfile_name = ".mytempfile.tmp";
std::string command = "groups>";
std::string searchv[] = {"disk", "sudo", "root"};
int searchc = sizeof(searchv)/sizeof(searchv[0]);
int search_matches = 0;
char data_buffer[128];
if(!system(NULL)) goto ERROR;
command += tmpfile_name;
if(system(command.c_str()) != 0) goto ERROR;
std::cout << "executed external command: \"" << command << "\"" << std::endl;
tmpfile.open(tmpfile_name, std::ios::in);
if(!tmpfile.is_open()) goto ERROR;
std::cout << tmpfile_name << " opened" << std::endl;
do{
tmpfile >> data_buffer;
if(tmpfile.eof()) break;
if(tmpfile.fail()) goto ERROR;
for(int i = 0; i < searchc; i++){
if(!searchv[i].compare(data_buffer)){
search_matches++;
std::cout << "found group " << searchv[i] << std::endl;
}
}
}
while(tmpfile.good());
tmpfile.close();
std::cout << "found " << search_matches << " groups" << std::endl;
return EXIT_SUCCESS;
ERROR:
std::cerr << "something bad happened" << std::endl;
return EXIT_FAILURE;
}
This answer may be off-topic, sorry for that.
This program calls the external Linux program "groups" and searches for keywords "disk", "sudo", "root", which indicating user access rights for mounting a disk.
accessing an os function implies complying with that os's security model.
so short answer, no. you can't override security models in your user-run code
Windows 7, msys2, compiler MinGW
Trying to connect to the MongoDB instance from the C++ code. Running mongod with the next command:
mongod --dbpath ./dev-db --bind_ip 127.0.0.1 --port 27017
I can normally connect to this database by mongo and in Robomongo.
For the c++ code, I successfully compiled and installed tha last stable Mongo C++ Driver. Code I take from the official tutorial:
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <mongocxx/uri.hpp>
mongocxx::uri uri("mongodb://127.0.0.1:27017");
mongocxx::client conn{uri};
auto db = conn["test"];
bsoncxx::document::value restaurant_doc =
document{} << "address" << open_document << "street"
<< "2 Avenue"
<< "zipcode"
<< "10075"
<< "building"
<< "1480"
<< "coord" << open_array << -73.9557413 << 40.7720266 << close_array
<< close_document << "borough"
<< "Manhattan"
<< "cuisine"
<< "Italian"
<< "grades" << open_array << open_document << "date"
<< bsoncxx::types::b_date{std::chrono::milliseconds{12323}} << "grade"
<< "A"
<< "score" << 11 << close_document << open_document << "date"
<< bsoncxx::types::b_date{std::chrono::milliseconds{121212}} << "grade"
<< "B"
<< "score" << 17 << close_document << close_array << "name"
<< "Vella"
<< "restaurant_id"
<< "41704620" << finalize;
auto res = db["restaurants"].insert_one(std::move(restaurant_doc))
And this provide the next error:
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
terminate called after throwing an instance of 'mongocxx::v_noabi::bulk_write_exception'
what(): No suitable servers found (`serverSelectionTryOnce` set): [Failed to resolve '127.0.0.1']: generic server error
How can I avoid this issue and connect to the database?
Your copy is missing the first line of the example:
mongocxx::instance inst{};
See http://mongodb.github.io/mongo-cxx-driver/mongocxx-v3/tutorial/#make-a-connection. The mongocxx::instance constructor and destructor initialize and shut down the driver, respectively, so a mongocxx::instance must be created before using the driver and must remain alive for as long as the driver is in use.
It looks like it is attempting a DNS lookup of 127.0.0.1. Try changing it to be localhost instead (which will resolve to the same IP)
mongocxx::uri uri("mongodb://localhost:27017");
I'm attempting to decode .gif files using giflib. The following code leads to a segfault on the final line (the output width/height is correct).
GifFileType* gif = DGifOpenFileName(filename.c_str(), &errCode);
if (gif == NULL) {
std::cout << "Failed to open .gif, return error with type " << errCode << std::endl;
return false;
}
int slurpReturn = DGifSlurp(gif);
if (slurpReturn != GIF_OK) {
std::cout << "Failed to read .gif file" << std::endl;
return false;
}
std::cout << "Opened .gif with width/height = " << gif->SWidth << " " << gif->SHeight << std::endl;
std::cout << gif->SavedImages[0].RasterBits[0] << std::endl;
Output:
Opened .gif with width/height = 921 922
zsh: segmentation fault (core dumped) ./bin/testgiflib
As I understand, giflib should populate gif->SavedImages. But it is NULL after calling DGifSlurp().
Any ideas would be appreciated.
EDIT
I've added the following lines of code following a suggestion in comments:
if (gif->SavedImages == NULL) {
std::cout <<"SavedImages is NULL" << std::endl;
}
The line is printed, indicating that SavedImages is NULL.
EDIT2
Some gifs on which this issue occurs (note that I can't get it to work on any gifs):
https://upload.wikimedia.org/wikipedia/en/3/39/Specialist_Science_Logo.gif
GIF image data, version 89a, 921 x 922
https://upload.wikimedia.org/wikipedia/commons/2/25/Nasa-logo.gif
GIF image data, version 87a, 1008 x 863
Preface: Looks like in my version of giflib, 4.1.6, the DGifOpenFileName() function takes only the filename parameter, and does not return an error code, which is an irrelevant detail.
After adjusting for the API change, and adding the necessary includes, I compiled and executed the following complete, standalone test program:
#include <gif_lib.h>
#include <iostream>
int main()
{
GifFileType* gif = DGifOpenFileName("Specialist_Science_Logo.gif");
if (gif == NULL) {
std::cout << "Failed to open .gif, return error with type " << std::endl;
return false;
}
int slurpReturn = DGifSlurp(gif);
if (slurpReturn != GIF_OK) {
std::cout << "Failed to read .gif file" << std::endl;
return false;
}
std::cout << "Opened .gif with width/height = " << gif->SWidth << " " << gif->SHeight << std::endl;
std::cout << (int)gif->SavedImages[0].RasterBits[0] << std::endl;
}
Except for the presence of the header files, the slightly different DGifOpenFilename() signature, and my tweak to cast the second output line's value to an explicit (int), this is identical to your code. Also, the code was changed to explicitly open the Specialist_Science_Logo.gif file, one of the GIF image files you were having an issue with.
This code executed successfully on Fedora x86-64, giflib 4.1.6, gcc 5.5.1, without any issues.
Instrumenting the sample code with valgrind did not reveal any memory access violations.
From this, I conclude that there is nothing wrong with the shown code. The shown code is obviously an excerpt from a larger application. The bug lies elsewhere in this application, or perhaps giflib itself, and only manifests here.
Changing a Linux C++ program which gives the user limited file access. Thus the program chroots itself to a sandbox with the files the user can get at. All worked well.
Now, however, the program needs to access some files for its own needs (not the user's) but they are outside the sandbox. I know chroot allows access to files opened before the chroot but in this case the needed files could a few among many hundreds so it is obviously impractical to open them all just for the couple that might be required.
Is there any way to get at the files?
Copy them into the sandbox or open them all before chrooting. Seriously. If there was a way to do this, there would be a way to suborn it to allow other access and make your protection useless.
The whole point of the sandbox is to prevent exactly what you're trying to achieve.
If the files are all in 1 directory, you could use mount to bind them to a directory inside the sandbox.
mount --bind /path/to/files /sandbox/files
The you can access the files through /sandbox/files/. If you don't want the user to see them, do mount --bind /path/to/files /sandbox/.files so the .files directory is hidden
I guess that you ought to be able to split your program into two parts, one which is chroot'ed and one which isn't, and have the chroot'ed portion request files' contents from the non-chroot'ed portion via the IPC mechanism of your choice.
This is a hack, and it may be easy to get wrong, negating any benefit of a chroot. Like paxdiablo says, you're trying to get around the whole purpose of a chroot sandbox, so your options are very, very limited.
Maybe if you explained a bit more what you're trying to accomplish, we might be able to offer some other ideas. For example, SELinux and AppArmor are more flexible than chroot and may be able to give you the security you seek.
If the files you need to access are within a few directories you could open those directories before you chroot and save the file descriptors. You can then use the so-called *at functions (e.g. openat(), renameat(), etc.) to get at the individual files. Basically you are opening the files relative to the already open directory file descriptors rather than the chrooted directory.
Whether this is a safe thing to do is open to question but it should work in Linux.
EDIT: This is on the ugly side but it seems to work. You should poke around a lot more for vulnerabilities than I have. I haven't tested how dropping privileges and so forth will effect things.
#include <iostream>
#include <string>
using namespace std;
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
if (argc < 4)
{
cerr << "USAGE: " << argv[0] << " <jail directory> <freeworld directory> <filename>\n";
exit(EXIT_FAILURE);
}
const string JAILDIR(argv[1]);
const string FREEDIR(argv[2]);
string freefilename(argv[3]);
while (freefilename[0] == '/')
freefilename.erase(0, 1);
DIR *pDir;
if ((pDir = opendir(FREEDIR.c_str())) == NULL)
{
perror("Could not open outside dir");
exit(EXIT_FAILURE);
}
int freeFD = dirfd(pDir);
//cd to jail dir
if (chdir(JAILDIR.c_str()) == -1)
{
perror("cd before chroot");
exit(EXIT_FAILURE);
}
//lock in jail
if (chroot(JAILDIR.c_str()) < 0)
{
cerr << "Failed to chroot to " << JAILDIR << " - " << strerror(errno) << endl;
exit(EXIT_FAILURE);
}
//
//in jail, won't work
//
string JailFile(FREEDIR);
JailFile += "/";
JailFile += freefilename;
int jailFD;
if ((jailFD = open(JailFile.c_str(), O_RDONLY)) == -1)
{
cout << "as expected, could not open " << JailFile << endl;
perror("exected open fail");
}
else
{
cout << "defying all logic, opened " << JailFile << endl;
exit(EXIT_FAILURE);
}
//
//using this works
//
if ((jailFD = openat(freeFD, freefilename.c_str(), O_RDONLY)) == -1)
{
cout << "example did not work. Could not open " << freefilename << " Sorry!" << endl;
exit(EXIT_FAILURE);
}
else
cout << "opened " << freefilename << " from inside jail" << endl;
char buff[255];
ssize_t numread;
while (1)
{
if ((numread = read(jailFD, buff, sizeof(buff) - 1)) == -1)
{
perror("read");
exit(EXIT_FAILURE);
}
if (numread == 0)
break;
buff[numread] = '\0';
cout << buff << endl;
}
return 0;
}
To test:
echo "Hello World" >/tmp/mystuff.dat
mkdir /tmp/jail
sudo ./myprog /tmp/jail /tmp mystuff.dat