fix: reject forged native descriptor pointers
This commit is contained in:
@@ -6,19 +6,21 @@
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <unordered_set>
|
||||
#ifdef __APPLE__
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
struct Handle { uint64_t magic; int fd; bool directory; bool closed; };
|
||||
static const uint64_t MAGIC=0x5448545746534154ULL;
|
||||
static void finalize(napi_env env, void* data, void*) { Handle* h=(Handle*)data; if(h && h->magic==MAGIC && !h->closed){::close(h->fd);h->closed=true;} delete h; }
|
||||
static bool getHandle(napi_env env,napi_value v,Handle** out){ void* p=nullptr; if(napi_get_value_external(env,v,&p)!=napi_ok||!p)return false; auto*h=(Handle*)p; if(h->magic!=MAGIC||h->closed)return false;*out=h;return true; }
|
||||
static std::unordered_set<void*> handles;
|
||||
static void finalize(napi_env env, void* data, void*) { Handle* h=(Handle*)data; if(h && handles.find(h)!=handles.end() && h->magic==MAGIC && !h->closed){::close(h->fd);h->closed=true;} handles.erase(h); delete h; }
|
||||
static bool getHandle(napi_env env,napi_value v,Handle** out){ void* p=nullptr; if(napi_get_value_external(env,v,&p)!=napi_ok||!p||handles.find(p)==handles.end())return false; auto*h=(Handle*)p; if(h->magic!=MAGIC||h->closed)return false;*out=h;return true; }
|
||||
static const char* errnoName(int e){switch(e){case EINVAL:return "EINVAL";case EBADF:return "EBADF";case EEXIST:return "EEXIST";case ENOENT:return "ENOENT";case ENOTDIR:return "ENOTDIR";case ELOOP:return "ELOOP";case EACCES:return "EACCES";case EPERM:return "EPERM";case EAGAIN:return "EAGAIN";default:return "EIO";}}
|
||||
static napi_value error(napi_env env,const char* syscall,int e){ napi_value msg,err,code,sys; napi_create_string_utf8(env,strerror(e),NAPI_AUTO_LENGTH,&msg); napi_create_error(env,nullptr,msg,&err); napi_create_string_utf8(env,syscall,NAPI_AUTO_LENGTH,&sys); napi_set_named_property(env,err,"syscall",sys); napi_create_string_utf8(env,errnoName(e),NAPI_AUTO_LENGTH,&code); napi_set_named_property(env,err,"code",code); napi_value en; napi_create_int32(env,e,&en); napi_set_named_property(env,err,"errno",en); return err; }
|
||||
static napi_value fail(napi_env env,const char*s,int e){ napi_value x=error(env,s,e); napi_throw(env,x); return nullptr; }
|
||||
static bool str(napi_env env,napi_value v,std::string& out){ size_t n; if(napi_get_value_string_utf8(env,v,nullptr,0,&n)!=napi_ok)return false; out.resize(n); napi_get_value_string_utf8(env,v,out.data(),n+1,&n);return true; }
|
||||
static napi_value statObj(napi_env env,const struct stat& st){ napi_value o,n; napi_create_object(env,&o); napi_create_bigint_uint64(env,(uint64_t)st.st_dev,&n); napi_set_named_property(env,o,"device",n); napi_create_bigint_uint64(env,(uint64_t)st.st_ino,&n); napi_set_named_property(env,o,"inode",n); napi_create_uint32(env,(uint32_t)st.st_mode,&n); napi_set_named_property(env,o,"mode",n); napi_create_uint32(env,(uint32_t)st.st_uid,&n); napi_set_named_property(env,o,"uid",n); napi_create_uint32(env,(uint32_t)st.st_gid,&n); napi_set_named_property(env,o,"gid",n); napi_create_bigint_uint64(env,(uint64_t)st.st_nlink,&n); napi_set_named_property(env,o,"nlink",n); return o; }
|
||||
static napi_value result(napi_env env,int fd,bool dir,const struct stat&st){ auto*h=new Handle{MAGIC,fd,dir,false}; napi_value e,o,s; napi_create_external(env,h,finalize,nullptr,&e); napi_create_object(env,&o); napi_set_named_property(env,o,"handle",e); s=statObj(env,st); napi_set_named_property(env,o,"openedStat",s); return o; }
|
||||
static napi_value result(napi_env env,int fd,bool dir,const struct stat&st){ auto*h=new Handle{MAGIC,fd,dir,false}; handles.insert(h); napi_value e,o,s; napi_create_external(env,h,finalize,nullptr,&e); napi_create_object(env,&o); napi_set_named_property(env,o,"handle",e); s=statObj(env,st); napi_set_named_property(env,o,"openedStat",s); return o; }
|
||||
static napi_value openatFn(napi_env env,napi_callback_info info){ size_t argc=1; napi_value a[1]; if(napi_get_cb_info(env,info,&argc,a,nullptr,nullptr)!=napi_ok||argc!=1)return fail(env,"openat",EINVAL); napi_value pv,nv,kv,mv; if(napi_get_named_property(env,a[0],"parent",&pv)!=napi_ok||napi_get_named_property(env,a[0],"name",&nv)!=napi_ok||napi_get_named_property(env,a[0],"kind",&kv)!=napi_ok||napi_get_named_property(env,a[0],"createMode",&mv)!=napi_ok)return fail(env,"openat",EINVAL); std::string name,kind; if(!str(env,nv,name)||!str(env,kv,kind))return fail(env,"openat",EINVAL); int32_t mode; if(napi_get_value_int32(env,mv,&mode)!=napi_ok)return fail(env,"openat",EINVAL); bool root=name=="/"; if(!root&&(name.empty()||name.size()>255||name=="."||name==".."||name.find('/')!=std::string::npos||name.find('\0')!=std::string::npos))return fail(env,"openat",EINVAL); Handle*ph=nullptr; if(root){if(kind!="directory"||mode!=0)return fail(env,"openat",EINVAL);} else if(!getHandle(env,pv,&ph)||!ph->directory)return fail(env,"openat",EBADF); int fd; if(root)fd=::open("/",O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); else if(kind=="directory"&&mode==0)fd=::openat(ph->fd,name.c_str(),O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); else if(kind=="regular_lock"&&mode==0600)fd=::openat(ph->fd,name.c_str(),O_RDWR|O_CREAT|O_CLOEXEC|O_NOFOLLOW,0600); else return fail(env,"openat",EINVAL); if(fd<0)return fail(env,"openat",errno); struct stat st; if(::fstat(fd,&st)<0){int e=errno;::close(fd);return fail(env,"fstat",e);} if(kind=="directory"&&!S_ISDIR(st.st_mode)){::close(fd);return fail(env,"openat",ENOTDIR);} if(kind=="regular_lock"&&(!S_ISREG(st.st_mode)||(st.st_mode&0777)!=0600)){::close(fd);return fail(env,"openat",EPERM);} return result(env,fd,kind=="directory",st); }
|
||||
static napi_value mkdiratFn(napi_env env,napi_callback_info info){size_t n=3; napi_value a[3]; napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;std::string s;int32_t m;if(n!=3||!getHandle(env,a[0],&h)||!h->directory||!str(env,a[1],s)||napi_get_value_int32(env,a[2],&m)!=napi_ok||m!=0700)return fail(env,"mkdirat",EINVAL);if(s.empty()||s.size()>255||s=="."||s==".."||s.find('/')!=std::string::npos)return fail(env,"mkdirat",EINVAL);if(::mkdirat(h->fd,s.c_str(),0700)<0)return fail(env,"mkdirat",errno);return nullptr;}
|
||||
static napi_value fstatatFn(napi_env env,napi_callback_info info){size_t n=2;napi_value a[2];napi_get_cb_info(env,info,&n,a,nullptr,nullptr);Handle*h;std::string s;if(n!=2||!getHandle(env,a[0],&h)||!h->directory||!str(env,a[1],s)||s.empty()||s.size()>255||s=="."||s==".."||s.find('/')!=std::string::npos)return fail(env,"fstatat",EINVAL);struct stat st;if(::fstatat(h->fd,s.c_str(),&st,AT_SYMLINK_NOFOLLOW)<0)return fail(env,"fstatat",errno);return statObj(env,st);}
|
||||
|
||||
Reference in New Issue
Block a user